From b1003be1df40f35fbf77fafd1c2ccf0816230995 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 2 Jun 2020 12:29:38 +0200 Subject: [PATCH 01/15] Create package @cosmwasm/sdk38 --- packages/sdk38/.eslintignore | 1 + packages/sdk38/.gitignore | 3 + packages/sdk38/README.md | 12 + packages/sdk38/jasmine-testrunner.js | 26 + packages/sdk38/karma.conf.js | 54 ++ packages/sdk38/nonces/README.txt | 1 + packages/sdk38/package.json | 48 + packages/sdk38/src/address.spec.ts | 27 + packages/sdk38/src/address.ts | 42 + packages/sdk38/src/coins.ts | 14 + .../sdk38/src/cosmosclient.searchtx.spec.ts | 366 +++++++ packages/sdk38/src/cosmosclient.spec.ts | 296 ++++++ packages/sdk38/src/cosmosclient.ts | 439 +++++++++ packages/sdk38/src/decoding.ts | 11 + packages/sdk38/src/encoding.spec.ts | 1 + packages/sdk38/src/encoding.ts | 59 ++ packages/sdk38/src/index.ts | 12 + packages/sdk38/src/logs.spec.ts | 165 ++++ packages/sdk38/src/logs.ts | 86 ++ packages/sdk38/src/pen.spec.ts | 56 ++ packages/sdk38/src/pen.ts | 92 ++ packages/sdk38/src/pubkey.spec.ts | 57 ++ packages/sdk38/src/pubkey.ts | 71 ++ packages/sdk38/src/restclient.spec.ts | 897 ++++++++++++++++++ packages/sdk38/src/restclient.ts | 457 +++++++++ packages/sdk38/src/sequence.spec.ts | 30 + packages/sdk38/src/sequence.ts | 46 + packages/sdk38/src/signature.spec.ts | 69 ++ packages/sdk38/src/signature.ts | 39 + .../sdk38/src/signingcosmosclient.spec.ts | 78 ++ packages/sdk38/src/signingcosmosclient.ts | 145 +++ packages/sdk38/src/testdata/cosmoshub.json | 44 + packages/sdk38/src/testdata/txresponse1.json | 57 ++ packages/sdk38/src/testdata/txresponse2.json | 57 ++ packages/sdk38/src/testdata/txresponse3.json | 57 ++ packages/sdk38/src/testutils.spec.ts | 58 ++ packages/sdk38/src/types.ts | 105 ++ packages/sdk38/tsconfig.json | 12 + packages/sdk38/typedoc.js | 14 + packages/sdk38/types/address.d.ts | 3 + packages/sdk38/types/coins.d.ts | 8 + packages/sdk38/types/cosmosclient.d.ts | 175 ++++ packages/sdk38/types/decoding.d.ts | 2 + packages/sdk38/types/encoding.d.ts | 10 + packages/sdk38/types/index.d.ts | 11 + packages/sdk38/types/logs.d.ts | 28 + packages/sdk38/types/pen.d.ts | 33 + packages/sdk38/types/pubkey.d.ts | 4 + packages/sdk38/types/restclient.d.ts | 231 +++++ packages/sdk38/types/sequence.d.ts | 19 + packages/sdk38/types/signature.d.ts | 14 + packages/sdk38/types/signingcosmosclient.d.ts | 74 ++ packages/sdk38/types/types.d.ts | 66 ++ packages/sdk38/webpack.web.config.js | 19 + 54 files changed, 4801 insertions(+) create mode 120000 packages/sdk38/.eslintignore create mode 100644 packages/sdk38/.gitignore create mode 100644 packages/sdk38/README.md create mode 100755 packages/sdk38/jasmine-testrunner.js create mode 100644 packages/sdk38/karma.conf.js create mode 100644 packages/sdk38/nonces/README.txt create mode 100644 packages/sdk38/package.json create mode 100644 packages/sdk38/src/address.spec.ts create mode 100644 packages/sdk38/src/address.ts create mode 100644 packages/sdk38/src/coins.ts create mode 100644 packages/sdk38/src/cosmosclient.searchtx.spec.ts create mode 100644 packages/sdk38/src/cosmosclient.spec.ts create mode 100644 packages/sdk38/src/cosmosclient.ts create mode 100644 packages/sdk38/src/decoding.ts create mode 100644 packages/sdk38/src/encoding.spec.ts create mode 100644 packages/sdk38/src/encoding.ts create mode 100644 packages/sdk38/src/index.ts create mode 100644 packages/sdk38/src/logs.spec.ts create mode 100644 packages/sdk38/src/logs.ts create mode 100644 packages/sdk38/src/pen.spec.ts create mode 100644 packages/sdk38/src/pen.ts create mode 100644 packages/sdk38/src/pubkey.spec.ts create mode 100644 packages/sdk38/src/pubkey.ts create mode 100644 packages/sdk38/src/restclient.spec.ts create mode 100644 packages/sdk38/src/restclient.ts create mode 100644 packages/sdk38/src/sequence.spec.ts create mode 100644 packages/sdk38/src/sequence.ts create mode 100644 packages/sdk38/src/signature.spec.ts create mode 100644 packages/sdk38/src/signature.ts create mode 100644 packages/sdk38/src/signingcosmosclient.spec.ts create mode 100644 packages/sdk38/src/signingcosmosclient.ts create mode 100644 packages/sdk38/src/testdata/cosmoshub.json create mode 100644 packages/sdk38/src/testdata/txresponse1.json create mode 100644 packages/sdk38/src/testdata/txresponse2.json create mode 100644 packages/sdk38/src/testdata/txresponse3.json create mode 100644 packages/sdk38/src/testutils.spec.ts create mode 100644 packages/sdk38/src/types.ts create mode 100644 packages/sdk38/tsconfig.json create mode 100644 packages/sdk38/typedoc.js create mode 100644 packages/sdk38/types/address.d.ts create mode 100644 packages/sdk38/types/coins.d.ts create mode 100644 packages/sdk38/types/cosmosclient.d.ts create mode 100644 packages/sdk38/types/decoding.d.ts create mode 100644 packages/sdk38/types/encoding.d.ts create mode 100644 packages/sdk38/types/index.d.ts create mode 100644 packages/sdk38/types/logs.d.ts create mode 100644 packages/sdk38/types/pen.d.ts create mode 100644 packages/sdk38/types/pubkey.d.ts create mode 100644 packages/sdk38/types/restclient.d.ts create mode 100644 packages/sdk38/types/sequence.d.ts create mode 100644 packages/sdk38/types/signature.d.ts create mode 100644 packages/sdk38/types/signingcosmosclient.d.ts create mode 100644 packages/sdk38/types/types.d.ts create mode 100644 packages/sdk38/webpack.web.config.js diff --git a/packages/sdk38/.eslintignore b/packages/sdk38/.eslintignore new file mode 120000 index 00000000..86039baf --- /dev/null +++ b/packages/sdk38/.eslintignore @@ -0,0 +1 @@ +../../.eslintignore \ No newline at end of file diff --git a/packages/sdk38/.gitignore b/packages/sdk38/.gitignore new file mode 100644 index 00000000..68bf3735 --- /dev/null +++ b/packages/sdk38/.gitignore @@ -0,0 +1,3 @@ +build/ +dist/ +docs/ diff --git a/packages/sdk38/README.md b/packages/sdk38/README.md new file mode 100644 index 00000000..3a0e693e --- /dev/null +++ b/packages/sdk38/README.md @@ -0,0 +1,12 @@ +# @cosmwasm/sdk38 + +[![npm version](https://img.shields.io/npm/v/@cosmwasm/sdk38.svg)](https://www.npmjs.com/package/@cosmwasm/sdk38) + +A client library for the Cosmos SDK 0.38. + +## License + +This package is part of the cosmwasm-js repository, licensed under the Apache +License 2.0 (see +[NOTICE](https://github.com/confio/cosmwasm-js/blob/master/NOTICE) and +[LICENSE](https://github.com/confio/cosmwasm-js/blob/master/LICENSE)). diff --git a/packages/sdk38/jasmine-testrunner.js b/packages/sdk38/jasmine-testrunner.js new file mode 100755 index 00000000..9fada59b --- /dev/null +++ b/packages/sdk38/jasmine-testrunner.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node + +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 }); + +// initialize and execute +jasmine.env.clearReporters(); +jasmine.addReporter(reporter); +jasmine.execute(); diff --git a/packages/sdk38/karma.conf.js b/packages/sdk38/karma.conf.js new file mode 100644 index 00000000..e68db403 --- /dev/null +++ b/packages/sdk38/karma.conf.js @@ -0,0 +1,54 @@ +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://npmjs.org/browse/keyword/karma-adapter + frameworks: ["jasmine"], + + // list of files / patterns to load in the browser + files: ["dist/web/tests.js"], + + client: { + jasmine: { + random: false, + timeoutInterval: 15000, + }, + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ["progress", "kjhtml"], + + // 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: false, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ["Firefox"], + + browserNoActivityTimeout: 90000, + + // Keep brower open for debugging. This is overridden by yarn scripts + singleRun: false, + + customLaunchers: { + ChromeHeadlessInsecure: { + base: "ChromeHeadless", + flags: ["--disable-web-security"], + }, + }, + }); +}; diff --git a/packages/sdk38/nonces/README.txt b/packages/sdk38/nonces/README.txt new file mode 100644 index 00000000..092fe732 --- /dev/null +++ b/packages/sdk38/nonces/README.txt @@ -0,0 +1 @@ +Directory used to trigger lerna package updates for all packages diff --git a/packages/sdk38/package.json b/packages/sdk38/package.json new file mode 100644 index 00000000..9b91856d --- /dev/null +++ b/packages/sdk38/package.json @@ -0,0 +1,48 @@ +{ + "name": "@cosmwasm/sdk38", + "version": "0.8.0", + "description": "Utilities for Cosmos SDK 0.38", + "author": "Ethan Frey ", + "license": "Apache-2.0", + "main": "build/index.js", + "types": "types/index.d.ts", + "files": [ + "build/", + "types/", + "*.md", + "!*.spec.*", + "!**/testdata/" + ], + "repository": { + "type": "git", + "url": "https://github.com/CosmWasm/cosmwasm-js/tree/master/packages/sdk38" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "docs": "shx rm -rf docs && typedoc --options typedoc.js", + "format": "prettier --write --loglevel warn \"./src/**/*.ts\"", + "lint": "eslint --max-warnings 0 \"**/*.{js,ts}\"", + "lint-fix": "eslint --max-warnings 0 \"**/*.{js,ts}\" --fix", + "move-types": "shx rm -rf ./types/* && shx mv build/types/* ./types && rm -rf ./types/testdata && shx rm -f ./types/*.spec.d.ts", + "format-types": "prettier --write --loglevel warn \"./types/**/*.d.ts\"", + "build": "shx rm -rf ./build && tsc && yarn move-types && yarn format-types", + "build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build", + "test-node": "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 ChromeHeadlessInsecure", + "test": "yarn build-or-skip && yarn test-node", + "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" + }, + "dependencies": { + "@iov/crypto": "^2.1.0", + "@iov/encoding": "^2.1.0", + "@iov/utils": "^2.0.2", + "axios": "^0.19.0", + "fast-deep-equal": "^3.1.1" + }, + "devDependencies": { + "readonly-date": "^1.0.0" + } +} diff --git a/packages/sdk38/src/address.spec.ts b/packages/sdk38/src/address.spec.ts new file mode 100644 index 00000000..713ffd03 --- /dev/null +++ b/packages/sdk38/src/address.spec.ts @@ -0,0 +1,27 @@ +import { Encoding } from "@iov/encoding"; + +import { pubkeyToAddress } from "./address"; + +const { toBase64, fromHex } = Encoding; + +describe("address", () => { + describe("pubkeyToAddress", () => { + it("works for Secp256k1 compressed", () => { + const prefix = "cosmos"; + const pubkey = { + type: "tendermint/PubKeySecp256k1", + value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP", + }; + expect(pubkeyToAddress(pubkey, prefix)).toEqual("cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r"); + }); + + it("works for Ed25519", () => { + const prefix = "cosmos"; + const pubkey = { + type: "tendermint/PubKeyEd25519", + value: toBase64(fromHex("12ee6f581fe55673a1e9e1382a0829e32075a0aa4763c968bc526e1852e78c95")), + }; + expect(pubkeyToAddress(pubkey, prefix)).toEqual("cosmos1pfq05em6sfkls66ut4m2257p7qwlk448h8mysz"); + }); + }); +}); diff --git a/packages/sdk38/src/address.ts b/packages/sdk38/src/address.ts new file mode 100644 index 00000000..126d8fb8 --- /dev/null +++ b/packages/sdk38/src/address.ts @@ -0,0 +1,42 @@ +import { Ripemd160, Sha256 } from "@iov/crypto"; +import { Bech32, Encoding } from "@iov/encoding"; + +import { PubKey, pubkeyType } from "./types"; + +const { fromBase64 } = Encoding; + +export function rawSecp256k1PubkeyToAddress(pubkeyRaw: Uint8Array, prefix: string): string { + if (pubkeyRaw.length !== 33) { + throw new Error(`Invalid Secp256k1 pubkey length (compressed): ${pubkeyRaw.length}`); + } + const hash1 = new Sha256(pubkeyRaw).digest(); + const hash2 = new Ripemd160(hash1).digest(); + return Bech32.encode(prefix, hash2); +} + +// See https://github.com/tendermint/tendermint/blob/f2ada0a604b4c0763bda2f64fac53d506d3beca7/docs/spec/blockchain/encoding.md#public-key-cryptography +// This assumes we already have a cosmos-compressed pubkey +export function pubkeyToAddress(pubkey: PubKey, prefix: string): string { + const pubkeyBytes = fromBase64(pubkey.value); + switch (pubkey.type) { + case pubkeyType.secp256k1: { + return rawSecp256k1PubkeyToAddress(pubkeyBytes, prefix); + } + case pubkeyType.ed25519: { + if (pubkeyBytes.length !== 32) { + throw new Error(`Invalid Ed25519 pubkey length: ${pubkeyBytes.length}`); + } + const hash = new Sha256(pubkeyBytes).digest(); + return Bech32.encode(prefix, hash.slice(0, 20)); + } + case pubkeyType.sr25519: { + if (pubkeyBytes.length !== 32) { + throw new Error(`Invalid Sr25519 pubkey length: ${pubkeyBytes.length}`); + } + const hash = new Sha256(pubkeyBytes).digest(); + return Bech32.encode(prefix, hash.slice(0, 20)); + } + default: + throw new Error("Unrecognized public key algorithm"); + } +} diff --git a/packages/sdk38/src/coins.ts b/packages/sdk38/src/coins.ts new file mode 100644 index 00000000..62f5f9ce --- /dev/null +++ b/packages/sdk38/src/coins.ts @@ -0,0 +1,14 @@ +export interface Coin { + readonly denom: string; + readonly amount: string; +} + +/** Creates a coin */ +export function coin(amount: number, denom: string): Coin { + return { amount: amount.toString(), denom: denom }; +} + +/** Creates a list of coins with one element */ +export function coins(amount: number, denom: string): Coin[] { + return [coin(amount, denom)]; +} diff --git a/packages/sdk38/src/cosmosclient.searchtx.spec.ts b/packages/sdk38/src/cosmosclient.searchtx.spec.ts new file mode 100644 index 00000000..96fe3040 --- /dev/null +++ b/packages/sdk38/src/cosmosclient.searchtx.spec.ts @@ -0,0 +1,366 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { assert, sleep } from "@iov/utils"; + +import { Coin } from "./coins"; +import { CosmosClient } from "./cosmosclient"; +import { makeSignBytes } from "./encoding"; +import { Secp256k1Pen } from "./pen"; +import { RestClient } from "./restclient"; +import { SigningCosmosClient } from "./signingcosmosclient"; +import { + faucet, + fromOneElementArray, + makeRandomAddress, + pendingWithoutWasmd, + wasmd, + wasmdEnabled, +} from "./testutils.spec"; +import { CosmosSdkTx, isMsgSend, MsgSend } from "./types"; + +describe("CosmosClient.searchTx", () => { + let sendSuccessful: + | { + readonly sender: string; + readonly recipient: string; + readonly hash: string; + readonly height: number; + readonly tx: CosmosSdkTx; + } + | undefined; + let sendUnsuccessful: + | { + readonly sender: string; + readonly recipient: string; + readonly hash: string; + readonly height: number; + readonly tx: CosmosSdkTx; + } + | undefined; + + beforeAll(async () => { + if (wasmdEnabled()) { + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmosClient(wasmd.endpoint, faucet.address, (signBytes) => + pen.sign(signBytes), + ); + + { + const recipient = makeRandomAddress(); + const transferAmount: Coin = { + denom: "ucosm", + amount: "1234567", + }; + const result = await client.sendTokens(recipient, [transferAmount]); + await sleep(50); // wait until tx is indexed + const txDetails = await new RestClient(wasmd.endpoint).txById(result.transactionHash); + sendSuccessful = { + sender: faucet.address, + recipient: recipient, + hash: result.transactionHash, + height: Number.parseInt(txDetails.height, 10), + tx: txDetails.tx, + }; + } + + { + const memo = "Sending more than I can afford"; + const recipient = makeRandomAddress(); + const transferAmount = [ + { + denom: "ucosm", + amount: "123456700000000", + }, + ]; + const sendMsg: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + // eslint-disable-next-line @typescript-eslint/camelcase + from_address: faucet.address, + // eslint-disable-next-line @typescript-eslint/camelcase + to_address: recipient, + amount: transferAmount, + }, + }; + const fee = { + amount: [ + { + denom: "ucosm", + amount: "2000", + }, + ], + gas: "80000", // 80k + }; + const { accountNumber, sequence } = await client.getNonce(); + const chainId = await client.getChainId(); + const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); + const signature = await pen.sign(signBytes); + const tx: CosmosSdkTx = { + type: "cosmos-sdk/StdTx", + value: { + msg: [sendMsg], + fee: fee, + memo: memo, + signatures: [signature], + }, + }; + const transactionId = await client.getIdentifier(tx); + const heightBeforeThis = await client.getHeight(); + try { + await client.postTx(tx.value); + } catch (error) { + // postTx() throws on execution failures, which is a questionable design. Ignore for now. + // console.log(error); + } + sendUnsuccessful = { + sender: faucet.address, + recipient: recipient, + hash: transactionId, + height: heightBeforeThis + 1, + tx: tx, + }; + } + } + }); + + describe("with SearchByIdQuery", () => { + it("can search successful tx by ID", async () => { + pendingWithoutWasmd(); + assert(sendSuccessful, "value must be set in beforeAll()"); + const client = new CosmosClient(wasmd.endpoint); + const result = await client.searchTx({ id: sendSuccessful.hash }); + expect(result.length).toEqual(1); + expect(result[0]).toEqual( + jasmine.objectContaining({ + height: sendSuccessful.height, + hash: sendSuccessful.hash, + code: 0, + tx: sendSuccessful.tx, + }), + ); + }); + + it("can search unsuccessful tx by ID", async () => { + pendingWithoutWasmd(); + assert(sendUnsuccessful, "value must be set in beforeAll()"); + const client = new CosmosClient(wasmd.endpoint); + const result = await client.searchTx({ id: sendUnsuccessful.hash }); + expect(result.length).toEqual(1); + expect(result[0]).toEqual( + jasmine.objectContaining({ + height: sendUnsuccessful.height, + hash: sendUnsuccessful.hash, + code: 5, + tx: sendUnsuccessful.tx, + }), + ); + }); + + it("can search by ID (non existent)", async () => { + pendingWithoutWasmd(); + const client = new CosmosClient(wasmd.endpoint); + const nonExistentId = "0000000000000000000000000000000000000000000000000000000000000000"; + const result = await client.searchTx({ id: nonExistentId }); + expect(result.length).toEqual(0); + }); + + it("can search by ID and filter by minHeight", async () => { + pendingWithoutWasmd(); + assert(sendSuccessful); + const client = new CosmosClient(wasmd.endpoint); + const query = { id: sendSuccessful.hash }; + + { + 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); + } + }); + }); + + describe("with SearchByHeightQuery", () => { + it("can search successful tx by height", async () => { + pendingWithoutWasmd(); + assert(sendSuccessful, "value must be set in beforeAll()"); + const client = new CosmosClient(wasmd.endpoint); + const result = await client.searchTx({ height: sendSuccessful.height }); + expect(result.length).toEqual(1); + expect(result[0]).toEqual( + jasmine.objectContaining({ + height: sendSuccessful.height, + hash: sendSuccessful.hash, + code: 0, + tx: sendSuccessful.tx, + }), + ); + }); + + it("can search unsuccessful tx by height", async () => { + pendingWithoutWasmd(); + assert(sendUnsuccessful, "value must be set in beforeAll()"); + const client = new CosmosClient(wasmd.endpoint); + const result = await client.searchTx({ height: sendUnsuccessful.height }); + expect(result.length).toEqual(1); + expect(result[0]).toEqual( + jasmine.objectContaining({ + height: sendUnsuccessful.height, + hash: sendUnsuccessful.hash, + code: 5, + tx: sendUnsuccessful.tx, + }), + ); + }); + }); + + describe("with SearchBySentFromOrToQuery", () => { + it("can search by sender", async () => { + pendingWithoutWasmd(); + assert(sendSuccessful, "value must be set in beforeAll()"); + const client = new CosmosClient(wasmd.endpoint); + 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 containsMsgWithSender = !!result.tx.value.msg.find( + (msg) => isMsgSend(msg) && msg.value.from_address == sendSuccessful!.sender, + ); + const containsMsgWithRecipient = !!result.tx.value.msg.find( + (msg) => isMsgSend(msg) && msg.value.to_address === sendSuccessful!.sender, + ); + expect(containsMsgWithSender || containsMsgWithRecipient).toEqual(true); + } + + // 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 () => { + pendingWithoutWasmd(); + assert(sendSuccessful, "value must be set in beforeAll()"); + const client = new CosmosClient(wasmd.endpoint); + 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 msg = fromOneElementArray(result.tx.value.msg); + assert(isMsgSend(msg), `${result.hash} (height ${result.height}) is not a bank send transaction`); + expect( + msg.value.to_address === sendSuccessful.recipient || + msg.value.from_address == sendSuccessful.recipient, + ).toEqual(true); + } + + // 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 () => { + pendingWithoutWasmd(); + assert(sendSuccessful); + const client = new CosmosClient(wasmd.endpoint); + 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 () => { + pendingWithoutWasmd(); + assert(sendSuccessful); + const client = new CosmosClient(wasmd.endpoint); + 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 () => { + pendingWithoutWasmd(); + assert(sendSuccessful, "value must be set in beforeAll()"); + const client = new CosmosClient(wasmd.endpoint); + 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 msg = fromOneElementArray(result.tx.value.msg); + assert(isMsgSend(msg), `${result.hash} (height ${result.height}) is not a bank send transaction`); + expect(msg.value.to_address).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/sdk38/src/cosmosclient.spec.ts b/packages/sdk38/src/cosmosclient.spec.ts new file mode 100644 index 00000000..28d57a6a --- /dev/null +++ b/packages/sdk38/src/cosmosclient.spec.ts @@ -0,0 +1,296 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { sleep } from "@iov/utils"; +import { ReadonlyDate } from "readonly-date"; + +import { CosmosClient, PrivateCosmWasmClient } from "./cosmosclient"; +import { makeSignBytes } from "./encoding"; +import { findAttribute } from "./logs"; +import { Secp256k1Pen } from "./pen"; +import cosmoshub from "./testdata/cosmoshub.json"; +import { + faucet, + makeRandomAddress, + pendingWithoutWasmd, + tendermintIdMatcher, + unused, + wasmd, +} from "./testutils.spec"; +import { MsgSend, StdFee } from "./types"; + +const guest = { + address: "cosmos17d0jcz59jf68g52vq38tuuncmwwjk42u6mcxej", +}; + +describe("CosmosClient", () => { + describe("constructor", () => { + it("can be constructed", () => { + const client = new CosmosClient(wasmd.endpoint); + expect(client).toBeTruthy(); + }); + }); + + describe("getChainId", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = new CosmosClient(wasmd.endpoint); + expect(await client.getChainId()).toEqual(wasmd.chainId); + }); + + it("caches chain ID", async () => { + pendingWithoutWasmd(); + const client = new CosmosClient(wasmd.endpoint); + const openedClient = (client as unknown) as PrivateCosmWasmClient; + const getCodeSpy = spyOn(openedClient.restClient, "nodeInfo").and.callThrough(); + + expect(await client.getChainId()).toEqual(wasmd.chainId); // from network + expect(await client.getChainId()).toEqual(wasmd.chainId); // from cache + + expect(getCodeSpy).toHaveBeenCalledTimes(1); + }); + }); + + describe("getHeight", () => { + it("gets height via last block", async () => { + pendingWithoutWasmd(); + const client = new CosmosClient(wasmd.endpoint); + const openedClient = (client as unknown) as PrivateCosmWasmClient; + const blockLatestSpy = spyOn(openedClient.restClient, "blocksLatest").and.callThrough(); + + const height1 = await client.getHeight(); + expect(height1).toBeGreaterThan(0); + await sleep(1_000); + const height2 = await client.getHeight(); + expect(height2).toEqual(height1 + 1); + + expect(blockLatestSpy).toHaveBeenCalledTimes(2); + }); + + it("gets height via authAccount once an address is known", async () => { + pendingWithoutWasmd(); + const client = new CosmosClient(wasmd.endpoint); + + const openedClient = (client as unknown) as PrivateCosmWasmClient; + const blockLatestSpy = spyOn(openedClient.restClient, "blocksLatest").and.callThrough(); + const authAccountsSpy = spyOn(openedClient.restClient, "authAccounts").and.callThrough(); + + const height1 = await client.getHeight(); + expect(height1).toBeGreaterThan(0); + + await client.getCodes(); // warm up the client + + const height2 = await client.getHeight(); + expect(height2).toBeGreaterThan(0); + await sleep(1_000); + const height3 = await client.getHeight(); + expect(height3).toEqual(height2 + 1); + + expect(blockLatestSpy).toHaveBeenCalledTimes(1); + expect(authAccountsSpy).toHaveBeenCalledTimes(2); + }); + }); + + describe("getNonce", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = new CosmosClient(wasmd.endpoint); + expect(await client.getNonce(unused.address)).toEqual({ + accountNumber: unused.accountNumber, + sequence: unused.sequence, + }); + }); + + it("throws for missing accounts", async () => { + pendingWithoutWasmd(); + const client = new CosmosClient(wasmd.endpoint); + const missing = makeRandomAddress(); + await client.getNonce(missing).then( + () => fail("this must not succeed"), + (error) => expect(error).toMatch(/account does not exist on chain/i), + ); + }); + }); + + describe("getAccount", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = new CosmosClient(wasmd.endpoint); + expect(await client.getAccount(unused.address)).toEqual({ + address: unused.address, + accountNumber: unused.accountNumber, + sequence: unused.sequence, + pubkey: undefined, + balance: [ + { denom: "ucosm", amount: "1000000000" }, + { denom: "ustake", amount: "1000000000" }, + ], + }); + }); + + it("returns undefined for missing accounts", async () => { + pendingWithoutWasmd(); + const client = new CosmosClient(wasmd.endpoint); + const missing = makeRandomAddress(); + expect(await client.getAccount(missing)).toBeUndefined(); + }); + }); + + describe("getBlock", () => { + it("works for latest block", async () => { + pendingWithoutWasmd(); + const client = new CosmosClient(wasmd.endpoint); + 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 () => { + pendingWithoutWasmd(); + const client = new CosmosClient(wasmd.endpoint); + 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("getIdentifier", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = new CosmosClient(wasmd.endpoint); + expect(await client.getIdentifier(cosmoshub.tx)).toEqual(cosmoshub.id); + }); + }); + + describe("postTx", () => { + it("works", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new CosmosClient(wasmd.endpoint); + + const memo = "My first contract on chain"; + const sendMsg: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: faucet.address, + to_address: makeRandomAddress(), + amount: [ + { + denom: "ucosm", + amount: "1234567", + }, + ], + }, + }; + + const fee: StdFee = { + amount: [ + { + amount: "5000", + denom: "ucosm", + }, + ], + gas: "890000", + }; + + const chainId = await client.getChainId(); + const { accountNumber, sequence } = await client.getNonce(faucet.address); + const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); + const signature = await pen.sign(signBytes); + const signedTx = { + msg: [sendMsg], + fee: fee, + memo: memo, + signatures: [signature], + }; + const { logs, transactionHash } = await client.postTx(signedTx); + const amountAttr = findAttribute(logs, "transfer", "amount"); + expect(amountAttr.value).toEqual("1234567ucosm"); + expect(transactionHash).toMatch(/^[0-9A-F]{64}$/); + }); + }); + + describe("getContracts", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = new CosmosClient(wasmd.endpoint); + const result = await client.getContracts(1); + expect(result.length).toBeGreaterThanOrEqual(3); + const [hash, isa, jade] = result; + expect(hash).toEqual({ + address: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", + codeId: 1, + creator: faucet.address, + label: "HASH", + }); + expect(isa).toEqual({ + address: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd", + codeId: 1, + creator: faucet.address, + label: "ISA", + }); + expect(jade).toEqual({ + address: "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c", + codeId: 1, + creator: faucet.address, + label: "JADE", + }); + }); + }); + + describe("getContract", () => { + it("works for HASH instance", async () => { + pendingWithoutWasmd(); + const client = new CosmosClient(wasmd.endpoint); + const hash = await client.getContract("cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5"); + expect(hash).toEqual({ + address: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", + codeId: 1, + creator: faucet.address, + label: "HASH", + initMsg: { + decimals: 5, + name: "Hash token", + symbol: "HASH", + initial_balances: jasmine.arrayContaining([ + { + address: faucet.address, + amount: "11", + }, + { + address: unused.address, + amount: "12812345", + }, + { + address: guest.address, + amount: "22004000000", + }, + ]), + }, + }); + }); + }); +}); diff --git a/packages/sdk38/src/cosmosclient.ts b/packages/sdk38/src/cosmosclient.ts new file mode 100644 index 00000000..7960c82d --- /dev/null +++ b/packages/sdk38/src/cosmosclient.ts @@ -0,0 +1,439 @@ +import { Sha256 } from "@iov/crypto"; +import { Encoding } from "@iov/encoding"; + +import { Coin } from "./coins"; +import { Log, parseLogs } from "./logs"; +import { decodeBech32Pubkey } from "./pubkey"; +import { BroadcastMode, RestClient } from "./restclient"; +import { CosmosSdkTx, JsonObject, PubKey, StdTx } from "./types"; + +export interface GetNonceResult { + readonly accountNumber: number; + readonly sequence: number; +} + +export interface Account { + /** Bech32 account address */ + readonly address: string; + readonly balance: ReadonlyArray; + readonly pubkey: PubKey | undefined; + readonly accountNumber: number; + readonly sequence: number; +} + +export interface PostTxResult { + readonly logs: readonly Log[]; + readonly rawLog: string; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; +} + +export interface SearchByIdQuery { + readonly id: string; +} + +export interface SearchByHeightQuery { + readonly height: number; +} + +export interface SearchBySentFromOrToQuery { + readonly sentFromOrTo: string; +} + +/** + * This query type allows you to pass arbitrary key/value pairs to the backend. It is + * more powerful and slightly lower level than the other search options. + */ +export interface SearchByTagsQuery { + readonly tags: readonly { readonly key: string; readonly value: string }[]; +} + +export type SearchTxQuery = + | SearchByIdQuery + | SearchByHeightQuery + | SearchBySentFromOrToQuery + | SearchByTagsQuery; + +function isSearchByIdQuery(query: SearchTxQuery): query is SearchByIdQuery { + return (query as SearchByIdQuery).id !== undefined; +} + +function isSearchByHeightQuery(query: SearchTxQuery): query is SearchByHeightQuery { + return (query as SearchByHeightQuery).height !== undefined; +} + +function isSearchBySentFromOrToQuery(query: SearchTxQuery): query is SearchBySentFromOrToQuery { + return (query as SearchBySentFromOrToQuery).sentFromOrTo !== undefined; +} + +function isSearchByTagsQuery(query: SearchTxQuery): query is SearchByTagsQuery { + return (query as SearchByTagsQuery).tags !== undefined; +} + +export interface SearchTxFilter { + readonly minHeight?: number; + readonly maxHeight?: number; +} + +export interface Code { + readonly id: number; + /** Bech32 account address */ + readonly creator: string; + /** Hex-encoded sha256 hash of the code stored here */ + readonly checksum: string; + readonly source?: string; + readonly builder?: string; +} + +export interface CodeDetails extends Code { + /** The original wasm bytes */ + readonly data: Uint8Array; +} + +export interface Contract { + readonly address: string; + readonly codeId: number; + /** Bech32 account address */ + readonly creator: string; + readonly label: string; +} + +export interface ContractDetails extends Contract { + /** Argument passed on initialization of the contract */ + readonly initMsg: object; +} + +/** A transaction that is indexed as part of the transaction history */ +export interface IndexedTx { + readonly height: number; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly hash: string; + /** Transaction execution error code. 0 on success. */ + readonly code: number; + readonly rawLog: string; + readonly logs: readonly Log[]; + readonly tx: CosmosSdkTx; + /** The gas limit as set by the user */ + readonly gasWanted?: number; + /** The gas used by the execution */ + readonly gasUsed?: number; + /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ + readonly timestamp: string; +} + +export interface BlockHeader { + readonly version: { + readonly block: string; + readonly app: string; + }; + readonly height: number; + readonly chainId: string; + /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ + readonly time: string; +} + +export interface Block { + /** The ID is a hash of the block header (uppercase hex) */ + readonly id: string; + readonly header: BlockHeader; + /** Array of raw transactions */ + readonly txs: ReadonlyArray; +} + +/** Use for testing only */ +export interface PrivateCosmWasmClient { + readonly restClient: RestClient; +} + +export class CosmosClient { + protected readonly restClient: RestClient; + /** Any address the chain considers valid (valid bech32 with proper prefix) */ + protected anyValidAddress: string | undefined; + + private readonly codesCache = new Map(); + private chainId: string | undefined; + + /** + * Creates a new client to interact with a CosmWasm blockchain. + * + * This instance does a lot of caching. In order to benefit from that you should try to use one instance + * for the lifetime of your application. When switching backends, a new instance must be created. + * + * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) + * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns + */ + public constructor(apiUrl: string, broadcastMode = BroadcastMode.Block) { + this.restClient = new RestClient(apiUrl, broadcastMode); + } + + public async getChainId(): Promise { + if (!this.chainId) { + const response = await this.restClient.nodeInfo(); + const chainId = response.node_info.network; + if (!chainId) throw new Error("Chain ID must not be empty"); + this.chainId = chainId; + } + + return this.chainId; + } + + public async getHeight(): Promise { + if (this.anyValidAddress) { + const { height } = await this.restClient.authAccounts(this.anyValidAddress); + return parseInt(height, 10); + } else { + // Note: this gets inefficient when blocks contain a lot of transactions since it + // requires downloading and deserializing all transactions in the block. + const latest = await this.restClient.blocksLatest(); + return parseInt(latest.block.header.height, 10); + } + } + + /** + * Returns a 32 byte upper-case hex transaction hash (typically used as the transaction ID) + */ + public async getIdentifier(tx: CosmosSdkTx): Promise { + // We consult the REST API because we don't have a local amino encoder + const bytes = await this.restClient.encodeTx(tx); + const hash = new Sha256(bytes).digest(); + return Encoding.toHex(hash).toUpperCase(); + } + + /** + * Returns account number and sequence. + * + * Throws if the account does not exist on chain. + * + * @param address returns data for this address. When unset, the client's sender adddress is used. + */ + public async getNonce(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 nonces.", + ); + } + return { + accountNumber: account.accountNumber, + sequence: account.sequence, + }; + } + + public async getAccount(address: string): Promise { + const account = await this.restClient.authAccounts(address); + const value = account.result.value; + if (value.address === "") { + return undefined; + } else { + this.anyValidAddress = value.address; + return { + address: value.address, + balance: value.coins, + pubkey: value.public_key ? decodeBech32Pubkey(value.public_key) : undefined, + accountNumber: value.account_number, + sequence: value.sequence, + }; + } + } + + /** + * Gets block header and meta + * + * @param height The height of the block. If undefined, the latest height is used. + */ + public async getBlock(height?: number): Promise { + const response = + height !== undefined ? await this.restClient.blocks(height) : await this.restClient.blocksLatest(); + + return { + id: response.block_id.hash, + header: { + version: response.block.header.version, + time: response.block.header.time, + height: parseInt(response.block.header.height, 10), + chainId: response.block.header.chain_id, + }, + txs: (response.block.data.txs || []).map((encoded) => Encoding.fromBase64(encoded)), + }; + } + + 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}&tx.minheight=${minHeight}&tx.maxheight=${maxHeight}`; + } + + let txs: readonly IndexedTx[]; + if (isSearchByIdQuery(query)) { + txs = await this.txsQuery(`tx.hash=${query.id}`); + } else if (isSearchByHeightQuery(query)) { + // optional optimization to avoid network request + if (query.height < minHeight || query.height > maxHeight) { + txs = []; + } else { + txs = await this.txsQuery(`tx.height=${query.height}`); + } + } else if (isSearchBySentFromOrToQuery(query)) { + // We cannot get both in one request (see https://github.com/cosmos/gaia/issues/75) + const sentQuery = withFilters(`message.module=bank&message.sender=${query.sentFromOrTo}`); + const receivedQuery = withFilters(`message.module=bank&transfer.recipient=${query.sentFromOrTo}`); + const sent = await this.txsQuery(sentQuery); + const received = await this.txsQuery(receivedQuery); + + 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("&")); + txs = await this.txsQuery(rawQuery); + } else { + throw new Error("Unknown query type"); + } + + // backend sometimes messes up with min/max height filtering + const filtered = txs.filter((tx) => tx.height >= minHeight && tx.height <= maxHeight); + + return filtered; + } + + public async postTx(tx: StdTx): Promise { + const result = await this.restClient.postTx(tx); + if (!result.txhash.match(/^([0-9A-F][0-9A-F])+$/)) { + throw new Error("Received ill-formatted txhash. Must be non-empty upper-case hex"); + } + + if (result.code) { + throw new Error( + `Error when posting tx ${result.txhash}. Code: ${result.code}; Raw log: ${result.raw_log}`, + ); + } + + return { + logs: result.logs ? parseLogs(result.logs) : [], + rawLog: result.raw_log || "", + transactionHash: result.txhash, + }; + } + + public async getCodes(): Promise { + const result = await this.restClient.listCodeInfo(); + return result.map( + (entry): Code => { + this.anyValidAddress = entry.creator; + return { + id: entry.id, + creator: entry.creator, + checksum: Encoding.toHex(Encoding.fromHex(entry.data_hash)), + source: entry.source || undefined, + builder: entry.builder || undefined, + }; + }, + ); + } + + public async getCodeDetails(codeId: number): Promise { + const cached = this.codesCache.get(codeId); + if (cached) return cached; + + const getCodeResult = await this.restClient.getCode(codeId); + const codeDetails: CodeDetails = { + id: getCodeResult.id, + creator: getCodeResult.creator, + checksum: Encoding.toHex(Encoding.fromHex(getCodeResult.data_hash)), + source: getCodeResult.source || undefined, + builder: getCodeResult.builder || undefined, + data: Encoding.fromBase64(getCodeResult.data), + }; + this.codesCache.set(codeId, codeDetails); + return codeDetails; + } + + public async getContracts(codeId: number): Promise { + const result = await this.restClient.listContractsByCodeId(codeId); + return result.map( + (entry): Contract => ({ + address: entry.address, + codeId: entry.code_id, + creator: entry.creator, + label: entry.label, + }), + ); + } + + /** + * Throws an error if no contract was found at the address + */ + public async getContract(address: string): Promise { + const result = await this.restClient.getContractInfo(address); + if (!result) throw new Error(`No contract found at address "${address}"`); + return { + address: result.address, + codeId: result.code_id, + creator: result.creator, + label: result.label, + initMsg: result.init_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 + const _info = await this.getContract(address); + + return this.restClient.queryContractRaw(address, key); + } + + /** + * 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: object): Promise { + try { + return await this.restClient.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 { + // TODO: we need proper pagination support + const limit = 100; + const result = await this.restClient.txsQuery(`${query}&limit=${limit}`); + const pages = parseInt(result.page_total, 10); + if (pages > 1) { + throw new Error( + `Found more results on the backend than we can process currently. Results: ${result.total_count}, supported: ${limit}`, + ); + } + return result.txs.map( + (restItem): IndexedTx => ({ + height: parseInt(restItem.height, 10), + hash: restItem.txhash, + code: restItem.code || 0, + rawLog: restItem.raw_log, + logs: parseLogs(restItem.logs || []), + tx: restItem.tx, + timestamp: restItem.timestamp, + }), + ); + } +} diff --git a/packages/sdk38/src/decoding.ts b/packages/sdk38/src/decoding.ts new file mode 100644 index 00000000..78286018 --- /dev/null +++ b/packages/sdk38/src/decoding.ts @@ -0,0 +1,11 @@ +import { Encoding } from "@iov/encoding"; + +import { isStdTx, StdTx } from "./types"; + +export function unmarshalTx(data: Uint8Array): StdTx { + const decoded = JSON.parse(Encoding.fromUtf8(data)); + if (!isStdTx(decoded)) { + throw new Error("Must be json encoded StdTx"); + } + return decoded; +} diff --git a/packages/sdk38/src/encoding.spec.ts b/packages/sdk38/src/encoding.spec.ts new file mode 100644 index 00000000..ccdb8a4a --- /dev/null +++ b/packages/sdk38/src/encoding.spec.ts @@ -0,0 +1 @@ +describe("encoding", () => {}); diff --git a/packages/sdk38/src/encoding.ts b/packages/sdk38/src/encoding.ts new file mode 100644 index 00000000..8a782944 --- /dev/null +++ b/packages/sdk38/src/encoding.ts @@ -0,0 +1,59 @@ +import { Encoding } from "@iov/encoding"; + +import { Msg, StdFee, StdTx } from "./types"; + +const { toUtf8 } = Encoding; + +function sortJson(json: any): any { + if (typeof json !== "object" || json === null) { + return json; + } + if (Array.isArray(json)) { + return json.map(sortJson); + } + const sortedKeys = Object.keys(json).sort(); + const result = sortedKeys.reduce( + (accumulator, key) => ({ + ...accumulator, + [key]: sortJson(json[key]), + }), + {}, + ); + return result; +} + +export function marshalTx(tx: StdTx): Uint8Array { + const json = JSON.stringify(tx); + return Encoding.toUtf8(json); +} + +interface SignJson { + readonly account_number: string; + readonly chain_id: string; + readonly fee: StdFee; + readonly memo: string; + readonly msgs: readonly Msg[]; + readonly sequence: string; +} + +export function makeSignBytes( + msgs: readonly Msg[], + fee: StdFee, + chainId: string, + memo: string, + accountNumber: number, + sequence: number, +): Uint8Array { + const signJson: SignJson = { + // eslint-disable-next-line @typescript-eslint/camelcase + account_number: accountNumber.toString(), + // eslint-disable-next-line @typescript-eslint/camelcase + chain_id: chainId, + fee: fee, + memo: memo, + msgs: msgs, + sequence: sequence.toString(), + }; + const signMsg = sortJson(signJson); + return toUtf8(JSON.stringify(signMsg)); +} diff --git a/packages/sdk38/src/index.ts b/packages/sdk38/src/index.ts new file mode 100644 index 00000000..b17e620d --- /dev/null +++ b/packages/sdk38/src/index.ts @@ -0,0 +1,12 @@ +import * as logs from "./logs"; +import * as types from "./types"; +export { logs, types }; + +export { pubkeyToAddress } from "./address"; +export { Coin, coin, coins } from "./coins"; +export { unmarshalTx } from "./decoding"; +export { makeSignBytes, marshalTx } from "./encoding"; +export { BroadcastMode, RestClient, TxsResponse } from "./restclient"; +export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; +export { findSequenceForSignedTx } from "./sequence"; +export { encodeSecp256k1Signature, decodeSignature } from "./signature"; diff --git a/packages/sdk38/src/logs.spec.ts b/packages/sdk38/src/logs.spec.ts new file mode 100644 index 00000000..584d9ef2 --- /dev/null +++ b/packages/sdk38/src/logs.spec.ts @@ -0,0 +1,165 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { parseAttribute, parseEvent, parseLog, parseLogs } from "./logs"; + +describe("logs", () => { + describe("parseAttribute", () => { + it("works", () => { + const attr = parseAttribute({ key: "a", value: "b" }); + expect(attr).toEqual({ key: "a", value: "b" }); + }); + + it("works for empty value", () => { + const attr = parseAttribute({ key: "foobar", value: "" }); + expect(attr).toEqual({ key: "foobar", value: "" }); + }); + + it("normalized unset value to empty string", () => { + const attr = parseAttribute({ key: "amount" }); + expect(attr).toEqual({ key: "amount", value: "" }); + }); + }); + + describe("parseEvent", () => { + it("works", () => { + const original = { + type: "message", + attributes: [ + { + key: "action", + value: "store-code", + }, + { + key: "module", + value: "wasm", + }, + { + key: "action", + value: "store-code", + }, + { + key: "sender", + value: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + }, + { + key: "code_id", + value: "1", + }, + ], + } as const; + + const event = parseEvent(original); + expect(event).toEqual(original); + }); + + it("works for transfer event", () => { + const original = { + type: "transfer", + attributes: [ + { + key: "recipient", + value: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", + }, + { + key: "amount", + }, + ], + } as const; + const expected = { + type: "transfer", + attributes: [ + { + key: "recipient", + value: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", + }, + { + key: "amount", + value: "", + }, + ], + } as const; + + const event = parseEvent(original); + expect(event).toEqual(expected); + }); + }); + + describe("parseLog", () => { + it("works", () => { + const original = { + msg_index: 0, + log: "", + events: [ + { + type: "message", + attributes: [ + { + key: "action", + value: "store-code", + }, + { + key: "module", + value: "wasm", + }, + { + key: "action", + value: "store-code", + }, + { + key: "sender", + value: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + }, + { + key: "code_id", + value: "1", + }, + ], + }, + ], + } as const; + + const log = parseLog(original); + expect(log).toEqual(original); + }); + }); + + describe("parseLogs", () => { + it("works", () => { + const original = [ + { + msg_index: 0, + log: "", + events: [ + { + type: "message", + attributes: [ + { + key: "action", + value: "store-code", + }, + { + key: "module", + value: "wasm", + }, + { + key: "action", + value: "store-code", + }, + { + key: "sender", + value: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + }, + { + key: "code_id", + value: "1", + }, + ], + }, + ], + }, + ] as const; + + const logs = parseLogs(original); + expect(logs).toEqual(original); + }); + }); +}); diff --git a/packages/sdk38/src/logs.ts b/packages/sdk38/src/logs.ts new file mode 100644 index 00000000..e1eaa1cb --- /dev/null +++ b/packages/sdk38/src/logs.ts @@ -0,0 +1,86 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { isNonNullObject } from "@iov/encoding"; + +export interface Attribute { + readonly key: string; + readonly value: string; +} + +export interface Event { + readonly type: string; + readonly attributes: readonly Attribute[]; +} + +export interface Log { + readonly msg_index: number; + readonly log: string; + readonly events: readonly Event[]; +} + +export function parseAttribute(input: unknown): Attribute { + if (!isNonNullObject(input)) throw new Error("Attribute must be a non-null object"); + const { key, value } = input as any; + if (typeof key !== "string" || !key) throw new Error("Attribute's key must be a non-empty string"); + if (typeof value !== "string" && typeof value !== "undefined") { + throw new Error("Attribute's value must be a string or unset"); + } + + return { + key: key, + value: value || "", + }; +} + +export function parseEvent(input: unknown): Event { + if (!isNonNullObject(input)) throw new Error("Event must be a non-null object"); + const { type, attributes } = input as any; + if (typeof type !== "string" || type === "") { + throw new Error(`Event type must be a non-empty string`); + } + if (!Array.isArray(attributes)) throw new Error("Event's attributes must be an array"); + return { + type: type, + attributes: attributes.map(parseAttribute), + }; +} + +export function parseLog(input: unknown): Log { + if (!isNonNullObject(input)) throw new Error("Log must be a non-null object"); + const { msg_index, log, events } = input as any; + if (typeof msg_index !== "number") throw new Error("Log's msg_index must be a number"); + if (typeof log !== "string") throw new Error("Log's log must be a string"); + if (!Array.isArray(events)) throw new Error("Log's events must be an array"); + return { + msg_index: msg_index, + log: log, + events: events.map(parseEvent), + }; +} + +export function parseLogs(input: unknown): readonly Log[] { + if (!Array.isArray(input)) throw new Error("Logs must be an array"); + return input.map(parseLog); +} + +/** + * Searches in logs for the first event of the given event type and in that event + * for the first first attribute with the given attribute key. + * + * Throws if the attribute was not found. + */ +export function findAttribute( + logs: readonly Log[], + eventType: "message" | "transfer", + attrKey: string, +): Attribute { + const firstLogs = logs.find(() => true); + const out = firstLogs?.events + .find((event) => event.type === eventType) + ?.attributes.find((attr) => attr.key === attrKey); + if (!out) { + throw new Error( + `Could not find attribute '${attrKey}' in first event of type '${eventType}' in first log.`, + ); + } + return out; +} diff --git a/packages/sdk38/src/pen.spec.ts b/packages/sdk38/src/pen.spec.ts new file mode 100644 index 00000000..549177a8 --- /dev/null +++ b/packages/sdk38/src/pen.spec.ts @@ -0,0 +1,56 @@ +import { Secp256k1, Secp256k1Signature, Sha256 } from "@iov/crypto"; +import { Encoding } from "@iov/encoding"; + +import { Secp256k1Pen } from "./pen"; +import { decodeSignature } from "./signature"; + +const { fromHex } = Encoding; + +describe("Sec256k1Pen", () => { + it("can be constructed", async () => { + const pen = await Secp256k1Pen.fromMnemonic( + "zebra slush diet army arrest purpose hawk source west glimpse custom record", + ); + expect(pen).toBeTruthy(); + }); + + describe("pubkey", () => { + it("returns compressed pubkey", async () => { + // special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling + // m/44'/118'/0'/0/0 + // pubkey: 02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6 + const pen = await Secp256k1Pen.fromMnemonic( + "special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling", + ); + expect(pen.pubkey).toEqual( + fromHex("02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6"), + ); + }); + }); + + describe("sign", () => { + it("creates correct signatures", async () => { + const pen = await Secp256k1Pen.fromMnemonic( + "special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling", + ); + const data = Encoding.toAscii("foo bar"); + const { pubkey, signature } = decodeSignature(await pen.sign(data)); + + const valid = await Secp256k1.verifySignature( + Secp256k1Signature.fromFixedLength(signature), + new Sha256(data).digest(), + pubkey, + ); + expect(valid).toEqual(true); + }); + }); + + describe("address", () => { + it("creates same address as Go imlementation", async () => { + const pen = await Secp256k1Pen.fromMnemonic( + "oyster design unusual machine spread century engine gravity focus cave carry slot", + ); + expect(pen.address("cosmos")).toEqual("cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u"); + }); + }); +}); diff --git a/packages/sdk38/src/pen.ts b/packages/sdk38/src/pen.ts new file mode 100644 index 00000000..936abc0e --- /dev/null +++ b/packages/sdk38/src/pen.ts @@ -0,0 +1,92 @@ +import { + Bip39, + EnglishMnemonic, + Secp256k1, + Sha256, + Sha512, + Slip10, + Slip10Curve, + Slip10RawIndex, +} from "@iov/crypto"; + +import { rawSecp256k1PubkeyToAddress } from "./address"; +import { encodeSecp256k1Signature } from "./signature"; +import { StdSignature } from "./types"; + +export type PrehashType = "sha256" | "sha512" | null; + +/** + * A pen is the most basic tool you can think of for signing. It works + * everywhere and can be used intuitively by everyone. However, it does not + * come with a great amount of features. End of semi suitable metaphor. + * + * This wraps a single keypair and allows for signing. + * + * Non-goals of this types are: multi account support, persistency, data migrations, + * obfuscation of sensitive data. + */ +export interface Pen { + readonly pubkey: Uint8Array; + readonly sign: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise; +} + +function prehash(bytes: Uint8Array, type: PrehashType): Uint8Array { + switch (type) { + case null: + return new Uint8Array([...bytes]); + case "sha256": + return new Sha256(bytes).digest(); + case "sha512": + return new Sha512(bytes).digest(); + default: + throw new Error("Unknown prehash type"); + } +} + +/** + * The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a` + * with 0-based account index `a`. + */ +export function makeCosmoshubPath(a: number): readonly Slip10RawIndex[] { + return [ + Slip10RawIndex.hardened(44), + Slip10RawIndex.hardened(118), + Slip10RawIndex.hardened(0), + Slip10RawIndex.normal(0), + Slip10RawIndex.normal(a), + ]; +} + +export class Secp256k1Pen implements Pen { + public static async fromMnemonic( + mnemonic: string, + hdPath: readonly Slip10RawIndex[] = makeCosmoshubPath(0), + ): Promise { + const seed = await Bip39.mnemonicToSeed(new EnglishMnemonic(mnemonic)); + const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, hdPath); + const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey; + return new Secp256k1Pen(privkey, Secp256k1.compressPubkey(uncompressed)); + } + + public readonly pubkey: Uint8Array; + private readonly privkey: Uint8Array; + + private constructor(privkey: Uint8Array, pubkey: Uint8Array) { + this.privkey = privkey; + this.pubkey = pubkey; + } + + /** + * Creates and returns a signature + */ + public async sign(signBytes: Uint8Array, prehashType: PrehashType = "sha256"): Promise { + const message = prehash(signBytes, prehashType); + const signature = await Secp256k1.createSignature(message, this.privkey); + const fixedLengthSignature = new Uint8Array([...signature.r(32), ...signature.s(32)]); + return encodeSecp256k1Signature(this.pubkey, fixedLengthSignature); + } + + public address(prefix: string): string { + return rawSecp256k1PubkeyToAddress(this.pubkey, prefix); + } +} diff --git a/packages/sdk38/src/pubkey.spec.ts b/packages/sdk38/src/pubkey.spec.ts new file mode 100644 index 00000000..cb344f7b --- /dev/null +++ b/packages/sdk38/src/pubkey.spec.ts @@ -0,0 +1,57 @@ +import { Encoding } from "@iov/encoding"; + +import { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; +import { PubKey } from "./types"; + +const { fromBase64 } = Encoding; + +describe("pubkey", () => { + describe("encodeSecp256k1Pubkey", () => { + it("encodes a compresed pubkey", () => { + const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP"); + expect(encodeSecp256k1Pubkey(pubkey)).toEqual({ + type: "tendermint/PubKeySecp256k1", + value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP", + }); + }); + + it("throws for uncompressed public keys", () => { + const pubkey = fromBase64( + "BE8EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQE7WHpoHoNswYeoFkuYpYSKK4mzFzMV/dB0DVAy4lnNU=", + ); + expect(() => encodeSecp256k1Pubkey(pubkey)).toThrowError(/public key must be compressed secp256k1/i); + }); + }); + + describe("decodeBech32Pubkey", () => { + it("works", () => { + expect( + decodeBech32Pubkey("cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5"), + ).toEqual({ + type: "tendermint/PubKeySecp256k1", + value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", + }); + }); + + it("works for enigma pubkey", () => { + expect( + decodeBech32Pubkey("enigmapub1addwnpepqw5k9p439nw0zpg2aundx4umwx4nw233z5prpjqjv5anl5grmnchzp2xwvv"), + ).toEqual({ + type: "tendermint/PubKeySecp256k1", + value: "A6lihrEs3PEFCu8m01ebcas3KjEVAjDIEmU7P9ED3PFx", + }); + }); + }); + + describe("encodeBech32Pubkey", () => { + it("works for secp256k1", () => { + const pubkey: PubKey = { + type: "tendermint/PubKeySecp256k1", + value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", + }; + expect(encodeBech32Pubkey(pubkey, "cosmospub")).toEqual( + "cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5", + ); + }); + }); +}); diff --git a/packages/sdk38/src/pubkey.ts b/packages/sdk38/src/pubkey.ts new file mode 100644 index 00000000..8835b97a --- /dev/null +++ b/packages/sdk38/src/pubkey.ts @@ -0,0 +1,71 @@ +import { Bech32, Encoding } from "@iov/encoding"; +import equal from "fast-deep-equal"; + +import { PubKey, pubkeyType } from "./types"; + +export function encodeSecp256k1Pubkey(pubkey: Uint8Array): PubKey { + if (pubkey.length !== 33 || (pubkey[0] !== 0x02 && pubkey[0] !== 0x03)) { + throw new Error("Public key must be compressed secp256k1, i.e. 33 bytes starting with 0x02 or 0x03"); + } + return { + type: pubkeyType.secp256k1, + value: Encoding.toBase64(pubkey), + }; +} + +// As discussed in https://github.com/binance-chain/javascript-sdk/issues/163 +// Prefixes listed here: https://github.com/tendermint/tendermint/blob/d419fffe18531317c28c29a292ad7d253f6cafdf/docs/spec/blockchain/encoding.md#public-key-cryptography +// Last bytes is varint-encoded length prefix +const pubkeyAminoPrefixSecp256k1 = Encoding.fromHex("eb5ae98721"); +const pubkeyAminoPrefixEd25519 = Encoding.fromHex("1624de6420"); +const pubkeyAminoPrefixSr25519 = Encoding.fromHex("0dfb1005"); +const pubkeyAminoPrefixLength = pubkeyAminoPrefixSecp256k1.length; + +export function decodeBech32Pubkey(bechEncoded: string): PubKey { + const { data } = Bech32.decode(bechEncoded); + + const aminoPrefix = data.slice(0, pubkeyAminoPrefixLength); + const rest = data.slice(pubkeyAminoPrefixLength); + if (equal(aminoPrefix, pubkeyAminoPrefixSecp256k1)) { + if (rest.length !== 33) { + throw new Error("Invalid rest data length. Expected 33 bytes (compressed secp256k1 pubkey)."); + } + return { + type: pubkeyType.secp256k1, + value: Encoding.toBase64(rest), + }; + } else if (equal(aminoPrefix, pubkeyAminoPrefixEd25519)) { + if (rest.length !== 32) { + throw new Error("Invalid rest data length. Expected 32 bytes (Ed25519 pubkey)."); + } + return { + type: pubkeyType.ed25519, + value: Encoding.toBase64(rest), + }; + } else if (equal(aminoPrefix, pubkeyAminoPrefixSr25519)) { + if (rest.length !== 32) { + throw new Error("Invalid rest data length. Expected 32 bytes (Sr25519 pubkey)."); + } + return { + type: pubkeyType.sr25519, + value: Encoding.toBase64(rest), + }; + } else { + throw new Error("Unsupported Pubkey type. Amino prefix: " + Encoding.toHex(aminoPrefix)); + } +} + +export function encodeBech32Pubkey(pubkey: PubKey, prefix: string): string { + let aminoPrefix: Uint8Array; + switch (pubkey.type) { + // Note: please don't add cases here without writing additional unit tests + case pubkeyType.secp256k1: + aminoPrefix = pubkeyAminoPrefixSecp256k1; + break; + default: + throw new Error("Unsupported pubkey type"); + } + + const data = new Uint8Array([...aminoPrefix, ...Encoding.fromBase64(pubkey.value)]); + return Bech32.encode(prefix, data); +} diff --git a/packages/sdk38/src/restclient.spec.ts b/packages/sdk38/src/restclient.spec.ts new file mode 100644 index 00000000..7dc391a4 --- /dev/null +++ b/packages/sdk38/src/restclient.spec.ts @@ -0,0 +1,897 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { Encoding } from "@iov/encoding"; +import { assert, sleep } from "@iov/utils"; +import { ReadonlyDate } from "readonly-date"; + +import { rawSecp256k1PubkeyToAddress } from "./address"; +import { makeSignBytes } from "./encoding"; +import { parseLogs } from "./logs"; +import { makeCosmoshubPath, Secp256k1Pen } from "./pen"; +import { encodeBech32Pubkey } from "./pubkey"; +import { RestClient, TxsResponse } from "./restclient"; +import { SigningCosmosClient } from "./signingcosmosclient"; +import cosmoshub from "./testdata/cosmoshub.json"; +import { + faucet, + makeRandomAddress, + nonNegativeIntegerMatcher, + pendingWithoutWasmd, + semverMatcher, + tendermintAddressMatcher, + tendermintIdMatcher, + tendermintOptionalIdMatcher, + tendermintShortHashMatcher, + unused, + wasmd, + wasmdEnabled, +} from "./testutils.spec"; +import { Msg, MsgSend, StdFee, StdSignature, StdTx } from "./types"; + +const { fromBase64 } = Encoding; + +const emptyAddress = "cosmos1ltkhnmdcqemmd2tkhnx7qx66tq7e0wykw2j85k"; + +function makeSignedTx(firstMsg: Msg, fee: StdFee, memo: string, firstSignature: StdSignature): StdTx { + return { + msg: [firstMsg], + fee: fee, + memo: memo, + signatures: [firstSignature], + }; +} + +describe("RestClient", () => { + it("can be constructed", () => { + const client = new RestClient(wasmd.endpoint); + expect(client).toBeTruthy(); + }); + + // The /auth endpoints + + describe("authAccounts", () => { + it("works for unused account without pubkey", async () => { + pendingWithoutWasmd(); + const client = new RestClient(wasmd.endpoint); + const { height, result } = await client.authAccounts(unused.address); + expect(height).toMatch(nonNegativeIntegerMatcher); + expect(result).toEqual({ + type: "cosmos-sdk/Account", + value: { + address: unused.address, + public_key: "", // not known to the chain + coins: [ + { + amount: "1000000000", + denom: "ucosm", + }, + { + amount: "1000000000", + denom: "ustake", + }, + ], + account_number: unused.accountNumber, + sequence: 0, + }, + }); + }); + + // This fails in the first test run if you forget to run `./scripts/wasmd/init.sh` + it("has correct pubkey for faucet", async () => { + pendingWithoutWasmd(); + const client = new RestClient(wasmd.endpoint); + const { result } = await client.authAccounts(faucet.address); + expect(result.value).toEqual( + jasmine.objectContaining({ + public_key: encodeBech32Pubkey(faucet.pubkey, "cosmospub"), + }), + ); + }); + + // This property is used by CosmWasmClient.getAccount + it("returns empty address for non-existent account", async () => { + pendingWithoutWasmd(); + const client = new RestClient(wasmd.endpoint); + const nonExistentAccount = makeRandomAddress(); + const { result } = await client.authAccounts(nonExistentAccount); + expect(result).toEqual({ + type: "cosmos-sdk/Account", + value: jasmine.objectContaining({ address: "" }), + }); + }); + }); + + // The /blocks endpoints + + describe("blocksLatest", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = new RestClient(wasmd.endpoint); + const response = await client.blocksLatest(); + + // id + expect(response.block_id.hash).toMatch(tendermintIdMatcher); + + // header + expect(response.block.header.version).toEqual({ block: "10", app: "0" }); + expect(parseInt(response.block.header.height, 10)).toBeGreaterThanOrEqual(1); + expect(response.block.header.chain_id).toEqual(wasmd.chainId); + expect(new ReadonlyDate(response.block.header.time).getTime()).toBeLessThan(ReadonlyDate.now()); + expect(new ReadonlyDate(response.block.header.time).getTime()).toBeGreaterThanOrEqual( + ReadonlyDate.now() - 5_000, + ); + expect(response.block.header.last_commit_hash).toMatch(tendermintIdMatcher); + expect(response.block.header.last_block_id.hash).toMatch(tendermintIdMatcher); + expect(response.block.header.data_hash).toMatch(tendermintOptionalIdMatcher); + expect(response.block.header.validators_hash).toMatch(tendermintIdMatcher); + expect(response.block.header.next_validators_hash).toMatch(tendermintIdMatcher); + expect(response.block.header.consensus_hash).toMatch(tendermintIdMatcher); + expect(response.block.header.app_hash).toMatch(tendermintIdMatcher); + expect(response.block.header.last_results_hash).toMatch(tendermintOptionalIdMatcher); + expect(response.block.header.evidence_hash).toMatch(tendermintOptionalIdMatcher); + expect(response.block.header.proposer_address).toMatch(tendermintAddressMatcher); + + // data + expect(response.block.data.txs === null || Array.isArray(response.block.data.txs)).toEqual(true); + }); + }); + + describe("blocks", () => { + it("works for block by height", async () => { + pendingWithoutWasmd(); + const client = new RestClient(wasmd.endpoint); + const height = parseInt((await client.blocksLatest()).block.header.height, 10); + const response = await client.blocks(height - 1); + + // id + expect(response.block_id.hash).toMatch(tendermintIdMatcher); + + // header + expect(response.block.header.version).toEqual({ block: "10", app: "0" }); + expect(response.block.header.height).toEqual(`${height - 1}`); + expect(response.block.header.chain_id).toEqual(wasmd.chainId); + expect(new ReadonlyDate(response.block.header.time).getTime()).toBeLessThan(ReadonlyDate.now()); + expect(new ReadonlyDate(response.block.header.time).getTime()).toBeGreaterThanOrEqual( + ReadonlyDate.now() - 5_000, + ); + expect(response.block.header.last_commit_hash).toMatch(tendermintIdMatcher); + expect(response.block.header.last_block_id.hash).toMatch(tendermintIdMatcher); + expect(response.block.header.data_hash).toMatch(tendermintOptionalIdMatcher); + expect(response.block.header.validators_hash).toMatch(tendermintIdMatcher); + expect(response.block.header.next_validators_hash).toMatch(tendermintIdMatcher); + expect(response.block.header.consensus_hash).toMatch(tendermintIdMatcher); + expect(response.block.header.app_hash).toMatch(tendermintIdMatcher); + expect(response.block.header.last_results_hash).toMatch(tendermintOptionalIdMatcher); + expect(response.block.header.evidence_hash).toMatch(tendermintOptionalIdMatcher); + expect(response.block.header.proposer_address).toMatch(tendermintAddressMatcher); + + // data + expect(response.block.data.txs === null || Array.isArray(response.block.data.txs)).toEqual(true); + }); + }); + + // The /node_info endpoint + + describe("nodeInfo", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = new RestClient(wasmd.endpoint); + const { node_info, application_version } = await client.nodeInfo(); + + expect(node_info).toEqual({ + protocol_version: { p2p: "7", block: "10", app: "0" }, + id: jasmine.stringMatching(tendermintShortHashMatcher), + listen_addr: "tcp://0.0.0.0:26656", + network: wasmd.chainId, + version: jasmine.stringMatching(/^0\.33\.[0-9]+$/), + channels: "4020212223303800", + moniker: wasmd.chainId, + other: { tx_index: "on", rpc_address: "tcp://0.0.0.0:26657" }, + }); + expect(application_version).toEqual({ + name: "wasm", + server_name: "wasmd", + client_name: "wasmcli", + version: jasmine.stringMatching(semverMatcher), + commit: jasmine.stringMatching(tendermintShortHashMatcher), + build_tags: "netgo,ledger", + go: jasmine.stringMatching(/^go version go1\.[0-9]+\.[0-9]+ linux\/amd64$/), + }); + }); + }); + + // The /txs endpoints + + describe("txById", () => { + let successful: + | { + readonly sender: string; + readonly recipient: string; + readonly hash: string; + } + | undefined; + let unsuccessful: + | { + readonly sender: string; + readonly recipient: string; + readonly hash: string; + } + | undefined; + + beforeAll(async () => { + if (wasmdEnabled()) { + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmosClient(wasmd.endpoint, faucet.address, (signBytes) => + pen.sign(signBytes), + ); + + { + const recipient = makeRandomAddress(); + const transferAmount = { + denom: "ucosm", + amount: "1234567", + }; + const result = await client.sendTokens(recipient, [transferAmount]); + successful = { + sender: faucet.address, + recipient: recipient, + hash: result.transactionHash, + }; + } + + { + const memo = "Sending more than I can afford"; + const recipient = makeRandomAddress(); + const transferAmount = [ + { + denom: "ucosm", + amount: "123456700000000", + }, + ]; + const sendMsg: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + // eslint-disable-next-line @typescript-eslint/camelcase + from_address: faucet.address, + // eslint-disable-next-line @typescript-eslint/camelcase + to_address: recipient, + amount: transferAmount, + }, + }; + const fee = { + amount: [ + { + denom: "ucosm", + amount: "2000", + }, + ], + gas: "80000", // 80k + }; + const { accountNumber, sequence } = await client.getNonce(); + const chainId = await client.getChainId(); + const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); + const signature = await pen.sign(signBytes); + const signedTx = { + msg: [sendMsg], + fee: fee, + memo: memo, + signatures: [signature], + }; + const transactionId = await client.getIdentifier({ type: "cosmos-sdk/StdTx", value: signedTx }); + try { + await client.postTx(signedTx); + } catch (error) { + // postTx() throws on execution failures, which is a questionable design. Ignore for now. + // console.log(error); + } + unsuccessful = { + sender: faucet.address, + recipient: recipient, + hash: transactionId, + }; + } + + await sleep(50); // wait until transactions are indexed + } + }); + + it("works for successful transaction", async () => { + pendingWithoutWasmd(); + assert(successful); + const client = new RestClient(wasmd.endpoint); + const result = await client.txById(successful.hash); + expect(result.height).toBeGreaterThanOrEqual(1); + expect(result.txhash).toEqual(successful.hash); + expect(result.codespace).toBeUndefined(); + expect(result.code).toBeUndefined(); + const logs = parseLogs(result.logs); + expect(logs).toEqual([ + { + msg_index: 0, + log: "", + events: [ + { + type: "message", + attributes: [ + { key: "action", value: "send" }, + { key: "sender", value: successful.sender }, + { key: "module", value: "bank" }, + ], + }, + { + type: "transfer", + attributes: [ + { key: "recipient", value: successful.recipient }, + { key: "sender", value: successful.sender }, + { key: "amount", value: "1234567ucosm" }, + ], + }, + ], + }, + ]); + }); + + it("works for unsuccessful transaction", async () => { + pendingWithoutWasmd(); + assert(unsuccessful); + const client = new RestClient(wasmd.endpoint); + const result = await client.txById(unsuccessful.hash); + expect(result.height).toBeGreaterThanOrEqual(1); + expect(result.txhash).toEqual(unsuccessful.hash); + expect(result.codespace).toEqual("sdk"); + expect(result.code).toEqual(5); + expect(result.logs).toBeUndefined(); + expect(result.raw_log).toContain("insufficient funds"); + }); + }); + + describe("txsQuery", () => { + let posted: + | { + readonly sender: string; + readonly recipient: string; + readonly hash: string; + readonly height: number; + readonly tx: TxsResponse; + } + | undefined; + + beforeAll(async () => { + if (wasmdEnabled()) { + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmosClient(wasmd.endpoint, faucet.address, (signBytes) => + pen.sign(signBytes), + ); + + const recipient = makeRandomAddress(); + const transferAmount = [ + { + denom: "ucosm", + amount: "1234567", + }, + ]; + const result = await client.sendTokens(recipient, transferAmount); + + await sleep(50); // wait until tx is indexed + const txDetails = await new RestClient(wasmd.endpoint).txById(result.transactionHash); + posted = { + sender: faucet.address, + recipient: recipient, + hash: result.transactionHash, + height: Number.parseInt(txDetails.height, 10), + tx: txDetails, + }; + } + }); + + it("can query transactions by height", async () => { + pendingWithoutWasmd(); + assert(posted); + const client = new RestClient(wasmd.endpoint); + const result = await client.txsQuery(`tx.height=${posted.height}&limit=26`); + expect(result).toEqual({ + count: "1", + limit: "26", + page_number: "1", + page_total: "1", + total_count: "1", + txs: [posted.tx], + }); + }); + + it("can query transactions by ID", async () => { + pendingWithoutWasmd(); + assert(posted); + const client = new RestClient(wasmd.endpoint); + const result = await client.txsQuery(`tx.hash=${posted.hash}&limit=26`); + expect(result).toEqual({ + count: "1", + limit: "26", + page_number: "1", + page_total: "1", + total_count: "1", + txs: [posted.tx], + }); + }); + + it("can query transactions by sender", async () => { + pendingWithoutWasmd(); + assert(posted); + const client = new RestClient(wasmd.endpoint); + const result = await client.txsQuery(`message.sender=${posted.sender}&limit=200`); + expect(parseInt(result.count, 10)).toBeGreaterThanOrEqual(1); + expect(parseInt(result.limit, 10)).toEqual(200); + expect(parseInt(result.page_number, 10)).toEqual(1); + expect(parseInt(result.page_total, 10)).toEqual(1); + expect(parseInt(result.total_count, 10)).toBeGreaterThanOrEqual(1); + expect(result.txs.length).toBeGreaterThanOrEqual(1); + expect(result.txs[result.txs.length - 1]).toEqual(posted.tx); + }); + + it("can query transactions by recipient", async () => { + pendingWithoutWasmd(); + assert(posted); + const client = new RestClient(wasmd.endpoint); + const result = await client.txsQuery(`transfer.recipient=${posted.recipient}&limit=200`); + expect(parseInt(result.count, 10)).toEqual(1); + expect(parseInt(result.limit, 10)).toEqual(200); + expect(parseInt(result.page_number, 10)).toEqual(1); + expect(parseInt(result.page_total, 10)).toEqual(1); + expect(parseInt(result.total_count, 10)).toEqual(1); + expect(result.txs.length).toBeGreaterThanOrEqual(1); + expect(result.txs[result.txs.length - 1]).toEqual(posted.tx); + }); + + it("can filter by tx.hash and tx.minheight", async () => { + pending("This combination is broken 🤷‍♂️. Handle client-side at higher level."); + pendingWithoutWasmd(); + assert(posted); + const client = new RestClient(wasmd.endpoint); + const hashQuery = `tx.hash=${posted.hash}`; + + { + const { count } = await client.txsQuery(`${hashQuery}&tx.minheight=0`); + expect(count).toEqual("1"); + } + + { + const { count } = await client.txsQuery(`${hashQuery}&tx.minheight=${posted.height - 1}`); + expect(count).toEqual("1"); + } + + { + const { count } = await client.txsQuery(`${hashQuery}&tx.minheight=${posted.height}`); + expect(count).toEqual("1"); + } + + { + const { count } = await client.txsQuery(`${hashQuery}&tx.minheight=${posted.height + 1}`); + expect(count).toEqual("0"); + } + }); + + it("can filter by recipient and tx.minheight", async () => { + pendingWithoutWasmd(); + assert(posted); + const client = new RestClient(wasmd.endpoint); + const recipientQuery = `transfer.recipient=${posted.recipient}`; + + { + const { count } = await client.txsQuery(`${recipientQuery}&tx.minheight=0`); + expect(count).toEqual("1"); + } + + { + const { count } = await client.txsQuery(`${recipientQuery}&tx.minheight=${posted.height - 1}`); + expect(count).toEqual("1"); + } + + { + const { count } = await client.txsQuery(`${recipientQuery}&tx.minheight=${posted.height}`); + expect(count).toEqual("1"); + } + + { + const { count } = await client.txsQuery(`${recipientQuery}&tx.minheight=${posted.height + 1}`); + expect(count).toEqual("0"); + } + }); + + it("can filter by recipient and tx.maxheight", async () => { + pendingWithoutWasmd(); + assert(posted); + const client = new RestClient(wasmd.endpoint); + const recipientQuery = `transfer.recipient=${posted.recipient}`; + + { + const { count } = await client.txsQuery(`${recipientQuery}&tx.maxheight=9999999999999`); + expect(count).toEqual("1"); + } + + { + const { count } = await client.txsQuery(`${recipientQuery}&tx.maxheight=${posted.height + 1}`); + expect(count).toEqual("1"); + } + + { + const { count } = await client.txsQuery(`${recipientQuery}&tx.maxheight=${posted.height}`); + expect(count).toEqual("1"); + } + + { + const { count } = await client.txsQuery(`${recipientQuery}&tx.maxheight=${posted.height - 1}`); + expect(count).toEqual("0"); + } + }); + }); + + describe("encodeTx", () => { + it("works for cosmoshub example", async () => { + pendingWithoutWasmd(); + const client = new RestClient(wasmd.endpoint); + expect(await client.encodeTx(cosmoshub.tx)).toEqual(fromBase64(cosmoshub.tx_data)); + }); + }); + + describe("postTx", () => { + it("can send tokens", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + + const memo = "My first contract on chain"; + const theMsg: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: faucet.address, + to_address: emptyAddress, + amount: [ + { + denom: "ucosm", + amount: "1234567", + }, + ], + }, + }; + + const fee: StdFee = { + amount: [ + { + amount: "5000", + denom: "ucosm", + }, + ], + gas: "890000", + }; + + const client = new RestClient(wasmd.endpoint); + const { account_number, sequence } = (await client.authAccounts(faucet.address)).result.value; + + const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); + const signature = await pen.sign(signBytes); + const signedTx = makeSignedTx(theMsg, fee, memo, signature); + const result = await client.postTx(signedTx); + expect(result.code).toBeUndefined(); + expect(result).toEqual({ + height: jasmine.stringMatching(nonNegativeIntegerMatcher), + txhash: jasmine.stringMatching(tendermintIdMatcher), + // code is not set + raw_log: jasmine.stringMatching(/^\[.+\]$/i), + logs: jasmine.any(Array), + gas_wanted: jasmine.stringMatching(nonNegativeIntegerMatcher), + gas_used: jasmine.stringMatching(nonNegativeIntegerMatcher), + }); + }); + + it("can't send transaction with additional signatures", async () => { + pendingWithoutWasmd(); + const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); + const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); + const account3 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(2)); + const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos"); + const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos"); + const address3 = rawSecp256k1PubkeyToAddress(account3.pubkey, "cosmos"); + + const memo = "My first contract on chain"; + const theMsg: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: address1, + to_address: emptyAddress, + amount: [ + { + denom: "ucosm", + amount: "1234567", + }, + ], + }, + }; + + const fee: StdFee = { + amount: [ + { + amount: "5000", + denom: "ucosm", + }, + ], + gas: "890000", + }; + + const client = new RestClient(wasmd.endpoint); + const { account_number: an1, sequence: sequence1 } = (await client.authAccounts(address1)).result.value; + const { account_number: an2, sequence: sequence2 } = (await client.authAccounts(address2)).result.value; + const { account_number: an3, sequence: sequence3 } = (await client.authAccounts(address3)).result.value; + + const signBytes1 = makeSignBytes([theMsg], fee, wasmd.chainId, memo, an1, sequence1); + const signBytes2 = makeSignBytes([theMsg], fee, wasmd.chainId, memo, an2, sequence2); + const signBytes3 = makeSignBytes([theMsg], fee, wasmd.chainId, memo, an3, sequence3); + const signature1 = await account1.sign(signBytes1); + const signature2 = await account2.sign(signBytes2); + const signature3 = await account3.sign(signBytes3); + const signedTx = { + msg: [theMsg], + fee: fee, + memo: memo, + signatures: [signature1, signature2, signature3], + }; + const postResult = await client.postTx(signedTx); + // console.log(postResult.raw_log); + expect(postResult.code).toEqual(4); + expect(postResult.raw_log).toContain("wrong number of signers"); + }); + + it("can send multiple messages with one signature", async () => { + pendingWithoutWasmd(); + const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); + const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos"); + + const memo = "My first contract on chain"; + const msg1: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: address1, + to_address: emptyAddress, + amount: [ + { + denom: "ucosm", + amount: "1234567", + }, + ], + }, + }; + const msg2: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: address1, + to_address: emptyAddress, + amount: [ + { + denom: "ucosm", + amount: "7654321", + }, + ], + }, + }; + + const fee: StdFee = { + amount: [ + { + amount: "5000", + denom: "ucosm", + }, + ], + gas: "890000", + }; + + const client = new RestClient(wasmd.endpoint); + const { account_number, sequence } = (await client.authAccounts(address1)).result.value; + + const signBytes = makeSignBytes([msg1, msg2], fee, wasmd.chainId, memo, account_number, sequence); + const signature1 = await account1.sign(signBytes); + const signedTx = { + msg: [msg1, msg2], + fee: fee, + memo: memo, + signatures: [signature1], + }; + const postResult = await client.postTx(signedTx); + // console.log(postResult.raw_log); + expect(postResult.code).toBeUndefined(); + }); + + it("can send multiple messages with multiple signatures", async () => { + pendingWithoutWasmd(); + const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); + const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); + const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos"); + const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos"); + + const memo = "My first contract on chain"; + const msg1: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: address1, + to_address: emptyAddress, + amount: [ + { + denom: "ucosm", + amount: "1234567", + }, + ], + }, + }; + const msg2: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: address2, + to_address: emptyAddress, + amount: [ + { + denom: "ucosm", + amount: "7654321", + }, + ], + }, + }; + + const fee: StdFee = { + amount: [ + { + amount: "5000", + denom: "ucosm", + }, + ], + gas: "890000", + }; + + const client = new RestClient(wasmd.endpoint); + const { account_number: an1, sequence: sequence1 } = (await client.authAccounts(address1)).result.value; + const { account_number: an2, sequence: sequence2 } = (await client.authAccounts(address2)).result.value; + + const signBytes1 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an1, sequence1); + const signBytes2 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an2, sequence2); + const signature1 = await account1.sign(signBytes1); + const signature2 = await account2.sign(signBytes2); + const signedTx = { + msg: [msg2, msg1], + fee: fee, + memo: memo, + signatures: [signature2, signature1], + }; + const postResult = await client.postTx(signedTx); + // console.log(postResult.raw_log); + expect(postResult.code).toBeUndefined(); + + await sleep(500); + const searched = await client.txsQuery(`tx.hash=${postResult.txhash}`); + expect(searched.txs.length).toEqual(1); + expect(searched.txs[0].tx.value.signatures).toEqual([signature2, signature1]); + }); + + it("can't send transaction with wrong signature order (1)", async () => { + pendingWithoutWasmd(); + const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); + const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); + const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos"); + const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos"); + + const memo = "My first contract on chain"; + const msg1: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: address1, + to_address: emptyAddress, + amount: [ + { + denom: "ucosm", + amount: "1234567", + }, + ], + }, + }; + const msg2: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: address2, + to_address: emptyAddress, + amount: [ + { + denom: "ucosm", + amount: "7654321", + }, + ], + }, + }; + + const fee: StdFee = { + amount: [ + { + amount: "5000", + denom: "ucosm", + }, + ], + gas: "890000", + }; + + const client = new RestClient(wasmd.endpoint); + const { account_number: an1, sequence: sequence1 } = (await client.authAccounts(address1)).result.value; + const { account_number: an2, sequence: sequence2 } = (await client.authAccounts(address2)).result.value; + + const signBytes1 = makeSignBytes([msg1, msg2], fee, wasmd.chainId, memo, an1, sequence1); + const signBytes2 = makeSignBytes([msg1, msg2], fee, wasmd.chainId, memo, an2, sequence2); + const signature1 = await account1.sign(signBytes1); + const signature2 = await account2.sign(signBytes2); + const signedTx = { + msg: [msg1, msg2], + fee: fee, + memo: memo, + signatures: [signature2, signature1], + }; + const postResult = await client.postTx(signedTx); + // console.log(postResult.raw_log); + expect(postResult.code).toEqual(8); + }); + + it("can't send transaction with wrong signature order (2)", async () => { + pendingWithoutWasmd(); + const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); + const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); + const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos"); + const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos"); + + const memo = "My first contract on chain"; + const msg1: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: address1, + to_address: emptyAddress, + amount: [ + { + denom: "ucosm", + amount: "1234567", + }, + ], + }, + }; + const msg2: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: address2, + to_address: emptyAddress, + amount: [ + { + denom: "ucosm", + amount: "7654321", + }, + ], + }, + }; + + const fee: StdFee = { + amount: [ + { + amount: "5000", + denom: "ucosm", + }, + ], + gas: "890000", + }; + + const client = new RestClient(wasmd.endpoint); + const { account_number: an1, sequence: sequence1 } = (await client.authAccounts(address1)).result.value; + const { account_number: an2, sequence: sequence2 } = (await client.authAccounts(address2)).result.value; + + const signBytes1 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an1, sequence1); + const signBytes2 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an2, sequence2); + const signature1 = await account1.sign(signBytes1); + const signature2 = await account2.sign(signBytes2); + const signedTx = { + msg: [msg2, msg1], + fee: fee, + memo: memo, + signatures: [signature1, signature2], + }; + const postResult = await client.postTx(signedTx); + // console.log(postResult.raw_log); + expect(postResult.code).toEqual(8); + }); + }); +}); diff --git a/packages/sdk38/src/restclient.ts b/packages/sdk38/src/restclient.ts new file mode 100644 index 00000000..cb6316e9 --- /dev/null +++ b/packages/sdk38/src/restclient.ts @@ -0,0 +1,457 @@ +import { Encoding, isNonNullObject } from "@iov/encoding"; +import axios, { AxiosError, AxiosInstance } from "axios"; + +import { Coin } from "./coins"; +import { CosmosSdkTx, JsonObject, Model, parseWasmData, StdTx, WasmData } from "./types"; + +const { fromBase64, fromUtf8, toHex, toUtf8 } = Encoding; + +export interface CosmosSdkAccount { + /** Bech32 account address */ + readonly address: string; + readonly coins: ReadonlyArray; + /** Bech32 encoded pubkey */ + readonly public_key: string; + readonly account_number: number; + readonly sequence: number; +} + +export interface NodeInfo { + readonly protocol_version: { + readonly p2p: string; + readonly block: string; + readonly app: string; + }; + readonly id: string; + readonly listen_addr: string; + readonly network: string; + readonly version: string; + readonly channels: string; + readonly moniker: string; + readonly other: { + readonly tx_index: string; + readonly rpc_address: string; + }; +} + +export interface ApplicationVersion { + readonly name: string; + readonly server_name: string; + readonly client_name: string; + readonly version: string; + readonly commit: string; + readonly build_tags: string; + readonly go: string; +} + +export interface NodeInfoResponse { + readonly node_info: NodeInfo; + readonly application_version: ApplicationVersion; +} + +export interface BlockId { + readonly hash: string; + // TODO: here we also have this + // parts: { + // total: '1', + // hash: '7AF200C78FBF9236944E1AB270F4045CD60972B7C265E3A9DA42973397572931' + // } +} + +export interface BlockHeader { + readonly version: { + readonly block: string; + readonly app: string; + }; + readonly height: string; + readonly chain_id: string; + /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ + readonly time: string; + readonly last_commit_hash: string; + readonly last_block_id: BlockId; + /** Can be empty */ + readonly data_hash: string; + readonly validators_hash: string; + readonly next_validators_hash: string; + readonly consensus_hash: string; + readonly app_hash: string; + /** Can be empty */ + readonly last_results_hash: string; + /** Can be empty */ + readonly evidence_hash: string; + readonly proposer_address: string; +} + +export interface Block { + readonly header: BlockHeader; + readonly data: { + /** Array of base64 encoded transactions */ + readonly txs: ReadonlyArray | null; + }; +} + +export interface BlockResponse { + readonly block_id: BlockId; + readonly block: Block; +} + +interface AuthAccountsResponse { + readonly height: string; + readonly result: { + readonly type: "cosmos-sdk/Account"; + readonly value: CosmosSdkAccount; + }; +} + +// Currently all wasm query responses return json-encoded strings... +// later deprecate this and use the specific types for result +// (assuming it is inlined, no second parse needed) +type WasmResponse = WasmSuccess | WasmError; + +interface WasmSuccess { + readonly height: string; + readonly result: T; +} + +interface WasmError { + readonly error: string; +} + +export interface TxsResponse { + readonly height: string; + readonly txhash: string; + /** 🤷‍♂️ */ + readonly codespace?: string; + /** Falsy when transaction execution succeeded. Contains error code on error. */ + readonly code?: number; + readonly raw_log: string; + readonly logs?: object; + readonly tx: CosmosSdkTx; + /** The gas limit as set by the user */ + readonly gas_wanted?: string; + /** The gas used by the execution */ + readonly gas_used?: string; + readonly timestamp: string; +} + +interface SearchTxsResponse { + readonly total_count: string; + readonly count: string; + readonly page_number: string; + readonly page_total: string; + readonly limit: string; + readonly txs: readonly TxsResponse[]; +} + +export interface PostTxsResponse { + readonly height: string; + readonly txhash: string; + readonly code?: number; + readonly raw_log?: string; + /** The same as `raw_log` but deserialized? */ + readonly logs?: object; + /** The gas limit as set by the user */ + readonly gas_wanted?: string; + /** The gas used by the execution */ + readonly gas_used?: string; +} + +interface EncodeTxResponse { + // base64-encoded amino-binary encoded representation + readonly tx: string; +} + +export interface CodeInfo { + readonly id: number; + /** Bech32 account address */ + readonly creator: string; + /** Hex-encoded sha256 hash of the code stored here */ + readonly data_hash: string; + // TODO: these are not supported in current wasmd + readonly source?: string; + readonly builder?: string; +} + +export interface CodeDetails extends CodeInfo { + /** Base64 encoded raw wasm data */ + readonly data: string; +} + +// This is list view, without contract info +export interface ContractInfo { + readonly address: string; + readonly code_id: number; + /** Bech32 account address */ + readonly creator: string; + readonly label: string; +} + +export interface ContractDetails extends ContractInfo { + /** Argument passed on initialization of the contract */ + readonly init_msg: object; +} + +interface SmartQueryResponse { + // base64 encoded response + readonly smart: string; +} + +type RestClientResponse = + | NodeInfoResponse + | BlockResponse + | AuthAccountsResponse + | TxsResponse + | SearchTxsResponse + | PostTxsResponse + | EncodeTxResponse + | WasmResponse + | WasmResponse + | WasmResponse + | WasmResponse + | WasmResponse; + +/** Unfortunately, Cosmos SDK encodes empty arrays as null */ +type CosmosSdkArray = ReadonlyArray | null; + +function normalizeArray(backend: CosmosSdkArray): ReadonlyArray { + return backend || []; +} + +/** + * The mode used to send transaction + * + * @see https://cosmos.network/rpc/#/Transactions/post_txs + */ +export enum BroadcastMode { + /** Return after tx commit */ + Block = "block", + /** Return afer CheckTx */ + Sync = "sync", + /** Return right away */ + Async = "async", +} + +function isWasmError(resp: WasmResponse): resp is WasmError { + return (resp as WasmError).error !== undefined; +} + +function unwrapWasmResponse(response: WasmResponse): T { + if (isWasmError(response)) { + throw new Error(response.error); + } + return response.result; +} + +// We want to get message data from 500 errors +// https://stackoverflow.com/questions/56577124/how-to-handle-500-error-message-with-axios +// this should be chained to catch one error and throw a more informative one +function parseAxiosError(err: AxiosError): never { + // use the error message sent from server, not default 500 msg + if (err.response?.data) { + let errorText: string; + const data = err.response.data; + // expect { error: string }, but otherwise dump + if (data.error && typeof data.error === "string") { + errorText = data.error; + } else if (typeof data === "string") { + errorText = data; + } else { + errorText = JSON.stringify(data); + } + throw new Error(`${errorText} (HTTP ${err.response.status})`); + } else { + throw err; + } +} + +export class RestClient { + private readonly client: AxiosInstance; + private readonly broadcastMode: BroadcastMode; + + /** + * Creates a new client to interact with a Cosmos SDK light client daemon. + * This class tries to be a direct mapping onto the API. Some basic decoding and normalizatin is done + * but things like caching are done at a higher level. + * + * When building apps, you should not need to use this class directly. If you do, this indicates a missing feature + * in higher level components. Feel free to raise an issue in this case. + * + * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) + * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns + */ + public constructor(apiUrl: string, broadcastMode = BroadcastMode.Block) { + const headers = { + post: { "Content-Type": "application/json" }, + }; + this.client = axios.create({ + baseURL: apiUrl, + headers: headers, + }); + this.broadcastMode = broadcastMode; + } + + public async get(path: string): Promise { + const { data } = await this.client.get(path).catch(parseAxiosError); + if (data === null) { + throw new Error("Received null response from server"); + } + return data; + } + + public async post(path: string, params: any): Promise { + if (!isNonNullObject(params)) throw new Error("Got unexpected type of params. Expected object."); + const { data } = await this.client.post(path, params).catch(parseAxiosError); + if (data === null) { + throw new Error("Received null response from server"); + } + return data; + } + + // The /auth endpoints + + public async authAccounts(address: string): Promise { + const path = `/auth/accounts/${address}`; + const responseData = await this.get(path); + if ((responseData as any).result.type !== "cosmos-sdk/Account") { + throw new Error("Unexpected response data format"); + } + return responseData as AuthAccountsResponse; + } + + // The /blocks endpoints + + public async blocksLatest(): Promise { + const responseData = await this.get("/blocks/latest"); + if (!(responseData as any).block) { + throw new Error("Unexpected response data format"); + } + return responseData as BlockResponse; + } + + public async blocks(height: number): Promise { + const responseData = await this.get(`/blocks/${height}`); + if (!(responseData as any).block) { + throw new Error("Unexpected response data format"); + } + return responseData as BlockResponse; + } + + // The /node_info endpoint + + public async nodeInfo(): Promise { + const responseData = await this.get("/node_info"); + if (!(responseData as any).node_info) { + throw new Error("Unexpected response data format"); + } + return responseData as NodeInfoResponse; + } + + // The /txs endpoints + + public async txById(id: string): Promise { + const responseData = await this.get(`/txs/${id}`); + if (!(responseData as any).tx) { + throw new Error("Unexpected response data format"); + } + return responseData as TxsResponse; + } + + public async txsQuery(query: string): Promise { + const responseData = await this.get(`/txs?${query}`); + if (!(responseData as any).txs) { + throw new Error("Unexpected response data format"); + } + return responseData as SearchTxsResponse; + } + + /** returns the amino-encoding of the transaction performed by the server */ + public async encodeTx(tx: CosmosSdkTx): Promise { + const responseData = await this.post("/txs/encode", tx); + if (!(responseData as any).tx) { + throw new Error("Unexpected response data format"); + } + return Encoding.fromBase64((responseData as EncodeTxResponse).tx); + } + + /** + * Broadcasts a signed transaction to into the transaction pool. + * Depending on the RestClient's broadcast mode, this might or might + * wait for checkTx or deliverTx to be executed before returning. + * + * @param tx a signed transaction as StdTx (i.e. not wrapped in type/value container) + */ + public async postTx(tx: StdTx): Promise { + const params = { + tx: tx, + mode: this.broadcastMode, + }; + const responseData = await this.post("/txs", params); + if (!(responseData as any).txhash) { + throw new Error("Unexpected response data format"); + } + return responseData as PostTxsResponse; + } + + // The /wasm endpoints + + // wasm rest queries are listed here: https://github.com/cosmwasm/wasmd/blob/master/x/wasm/client/rest/query.go#L19-L27 + public async listCodeInfo(): Promise { + const path = `/wasm/code`; + const responseData = (await this.get(path)) as WasmResponse>; + return normalizeArray(unwrapWasmResponse(responseData)); + } + + // this will download the original wasm bytecode by code id + // throws error if no code with this id + public async getCode(id: number): Promise { + const path = `/wasm/code/${id}`; + const responseData = (await this.get(path)) as WasmResponse; + return unwrapWasmResponse(responseData); + } + + public async listContractsByCodeId(id: number): Promise { + const path = `/wasm/code/${id}/contracts`; + const responseData = (await this.get(path)) as WasmResponse>; + return normalizeArray(unwrapWasmResponse(responseData)); + } + + /** + * Returns null when contract was not found at this address. + */ + public async getContractInfo(address: string): Promise { + const path = `/wasm/contract/${address}`; + const response = (await this.get(path)) as WasmResponse; + return unwrapWasmResponse(response); + } + + // Returns all contract state. + // This is an empty array if no such contract, or contract has no data. + public async getAllContractState(address: string): Promise { + const path = `/wasm/contract/${address}/state`; + const responseData = (await this.get(path)) as WasmResponse>; + return normalizeArray(unwrapWasmResponse(responseData)).map(parseWasmData); + } + + // Returns the data at the key if present (unknown decoded json), + // or null if no data at this (contract address, key) pair + public async queryContractRaw(address: string, key: Uint8Array): Promise { + const hexKey = toHex(key); + const path = `/wasm/contract/${address}/raw/${hexKey}?encoding=hex`; + const responseData = (await this.get(path)) as WasmResponse; + const data = unwrapWasmResponse(responseData); + return data.length === 0 ? null : fromBase64(data[0].val); + } + + /** + * Makes a smart query on the contract and parses the reponse as JSON. + * Throws error if no such contract exists, the query format is invalid or the response is invalid. + */ + public async queryContractSmart(address: string, query: object): Promise { + const encoded = toHex(toUtf8(JSON.stringify(query))); + const path = `/wasm/contract/${address}/smart/${encoded}?encoding=hex`; + const responseData = (await this.get(path)) as WasmResponse; + const result = unwrapWasmResponse(responseData); + // By convention, smart queries must return a valid JSON document (see https://github.com/CosmWasm/cosmwasm/issues/144) + return JSON.parse(fromUtf8(fromBase64(result.smart))); + } +} diff --git a/packages/sdk38/src/sequence.spec.ts b/packages/sdk38/src/sequence.spec.ts new file mode 100644 index 00000000..e5d8f4cd --- /dev/null +++ b/packages/sdk38/src/sequence.spec.ts @@ -0,0 +1,30 @@ +import { findSequenceForSignedTx } from "./sequence"; +import response1 from "./testdata/txresponse1.json"; +import response2 from "./testdata/txresponse2.json"; +import response3 from "./testdata/txresponse3.json"; + +// Those values must match ./testdata/txresponse*.json +const chainId = "testing"; +const accountNumber = 4; + +describe("sequence", () => { + describe("findSequenceForSignedTx", () => { + it("works", async () => { + const current = 100; // what we get from GET /auth/accounts/{address} + expect(await findSequenceForSignedTx(response1.tx, chainId, accountNumber, current)).toEqual(10); + // We know response3.height > response1.height, so the sequence must be at least 10+1 + expect(await findSequenceForSignedTx(response3.tx, chainId, accountNumber, current, 11)).toEqual(19); + // We know response3.height > response2.height > response1.height, so the sequence must be at least 10+1 and smaller than 19 + expect(await findSequenceForSignedTx(response2.tx, chainId, accountNumber, 19, 11)).toEqual(13); + }); + + it("returns undefined when sequence is not in range", async () => { + expect(await findSequenceForSignedTx(response1.tx, chainId, accountNumber, 5)).toBeUndefined(); + expect(await findSequenceForSignedTx(response1.tx, chainId, accountNumber, 20, 11)).toBeUndefined(); + expect(await findSequenceForSignedTx(response1.tx, chainId, accountNumber, 20, 50)).toBeUndefined(); + + // upper bound is not included in the possible results + expect(await findSequenceForSignedTx(response1.tx, chainId, accountNumber, 10)).toBeUndefined(); + }); + }); +}); diff --git a/packages/sdk38/src/sequence.ts b/packages/sdk38/src/sequence.ts new file mode 100644 index 00000000..5cf2a738 --- /dev/null +++ b/packages/sdk38/src/sequence.ts @@ -0,0 +1,46 @@ +import { Secp256k1, Secp256k1Signature, Sha256 } from "@iov/crypto"; + +import { makeSignBytes } from "./encoding"; +import { decodeSignature } from "./signature"; +import { CosmosSdkTx } from "./types"; + +/** + * Serach for sequence s with `min` <= `s` < `upperBound` to find the sequence that was used to sign the transaction + * + * @param tx The signed transaction + * @param chainId The chain ID for which this transaction was signed + * @param accountNumber The account number for which this transaction was signed + * @param upperBound The upper bound for the testing, i.e. sequence must be lower than this value + * @param min The lowest sequence that is tested + * + * @returns the sequence if a match was found and undefined otherwise + */ +export async function findSequenceForSignedTx( + tx: CosmosSdkTx, + chainId: string, + accountNumber: number, + upperBound: number, + min = 0, +): Promise { + const firstSignature = tx.value.signatures.find(() => true); + if (!firstSignature) throw new Error("Signature missing in tx"); + + const { pubkey, signature } = decodeSignature(firstSignature); + const secp256keSignature = Secp256k1Signature.fromFixedLength(signature); + + for (let s = min; s < upperBound; s++) { + // console.log(`Trying sequence ${s}`); + const signBytes = makeSignBytes( + tx.value.msg, + tx.value.fee, + chainId, + tx.value.memo || "", + accountNumber, + s, + ); + const prehashed = new Sha256(signBytes).digest(); + const valid = await Secp256k1.verifySignature(secp256keSignature, prehashed, pubkey); + if (valid) return s; + } + return undefined; +} diff --git a/packages/sdk38/src/signature.spec.ts b/packages/sdk38/src/signature.spec.ts new file mode 100644 index 00000000..b0e782c1 --- /dev/null +++ b/packages/sdk38/src/signature.spec.ts @@ -0,0 +1,69 @@ +import { Encoding } from "@iov/encoding"; + +import { decodeSignature, encodeSecp256k1Signature } from "./signature"; +import { StdSignature } from "./types"; + +const { fromBase64 } = Encoding; + +describe("signature", () => { + describe("encodeSecp256k1Signature", () => { + it("encodes a full signature", () => { + const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP"); + const signature = fromBase64( + "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==", + ); + expect(encodeSecp256k1Signature(pubkey, signature)).toEqual({ + // eslint-disable-next-line @typescript-eslint/camelcase + pub_key: { + type: "tendermint/PubKeySecp256k1", + value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP", + }, + signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==", + }); + }); + + it("throws when getting uncompressed public keys", () => { + const pubkey = fromBase64( + "BE8EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQE7WHpoHoNswYeoFkuYpYSKK4mzFzMV/dB0DVAy4lnNU=", + ); + const signature = fromBase64( + "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==", + ); + expect(() => encodeSecp256k1Signature(pubkey, signature)).toThrowError( + /public key must be compressed secp256k1/i, + ); + }); + + it("throws if signature contains recovery byte", () => { + const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP"); + const signature = Uint8Array.from([ + ...fromBase64( + "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==", + ), + 99, + ]); + expect(() => encodeSecp256k1Signature(pubkey, signature)).toThrowError( + /signature must be 64 bytes long/i, + ); + }); + }); + + describe("decodeSignature", () => { + it("works for secp256k1", () => { + const signature: StdSignature = { + // eslint-disable-next-line @typescript-eslint/camelcase + pub_key: { + type: "tendermint/PubKeySecp256k1", + value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP", + }, + signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==", + }; + expect(decodeSignature(signature)).toEqual({ + pubkey: fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP"), + signature: fromBase64( + "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==", + ), + }); + }); + }); +}); diff --git a/packages/sdk38/src/signature.ts b/packages/sdk38/src/signature.ts new file mode 100644 index 00000000..d31eb119 --- /dev/null +++ b/packages/sdk38/src/signature.ts @@ -0,0 +1,39 @@ +import { Encoding } from "@iov/encoding"; + +import { encodeSecp256k1Pubkey } from "./pubkey"; +import { pubkeyType, StdSignature } from "./types"; + +/** + * Takes a binary pubkey and signature to create a signature object + * + * @param pubkey a compressed secp256k1 public key + * @param signature a 64 byte fixed length representation of secp256k1 signature components r and s + */ +export function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature { + if (signature.length !== 64) { + throw new Error( + "Signature must be 64 bytes long. Cosmos SDK uses a 2x32 byte fixed length encoding for the secp256k1 signature integers r and s.", + ); + } + + return { + // eslint-disable-next-line @typescript-eslint/camelcase + pub_key: encodeSecp256k1Pubkey(pubkey), + signature: Encoding.toBase64(signature), + }; +} + +export function decodeSignature( + signature: StdSignature, +): { readonly pubkey: Uint8Array; readonly signature: Uint8Array } { + switch (signature.pub_key.type) { + // Note: please don't add cases here without writing additional unit tests + case pubkeyType.secp256k1: + return { + pubkey: Encoding.fromBase64(signature.pub_key.value), + signature: Encoding.fromBase64(signature.signature), + }; + default: + throw new Error("Unsupported pubkey type"); + } +} diff --git a/packages/sdk38/src/signingcosmosclient.spec.ts b/packages/sdk38/src/signingcosmosclient.spec.ts new file mode 100644 index 00000000..5054d925 --- /dev/null +++ b/packages/sdk38/src/signingcosmosclient.spec.ts @@ -0,0 +1,78 @@ +import { assert } from "@iov/utils"; + +import { Coin } from "./coins"; +import { PrivateCosmWasmClient } from "./cosmosclient"; +import { Secp256k1Pen } from "./pen"; +import { SigningCosmosClient } from "./signingcosmosclient"; +import { makeRandomAddress, pendingWithoutWasmd } from "./testutils.spec"; + +const httpUrl = "http://localhost:1317"; + +const faucet = { + 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", + pubkey: { + type: "tendermint/PubKeySecp256k1", + value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", + }, + address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", +}; + +describe("SigningCosmosClient", () => { + describe("makeReadOnly", () => { + it("can be constructed", async () => { + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmosClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); + expect(client).toBeTruthy(); + }); + }); + + describe("getHeight", () => { + it("always uses authAccount implementation", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmosClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); + + const openedClient = (client as unknown) as PrivateCosmWasmClient; + const blockLatestSpy = spyOn(openedClient.restClient, "blocksLatest").and.callThrough(); + const authAccountsSpy = spyOn(openedClient.restClient, "authAccounts").and.callThrough(); + + const height = await client.getHeight(); + expect(height).toBeGreaterThan(0); + + expect(blockLatestSpy).toHaveBeenCalledTimes(0); + expect(authAccountsSpy).toHaveBeenCalledTimes(1); + }); + }); + + describe("sendTokens", () => { + it("works", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmosClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); + + // instantiate + const transferAmount: readonly Coin[] = [ + { + amount: "7890", + denom: "ucosm", + }, + ]; + const beneficiaryAddress = makeRandomAddress(); + + // no tokens here + const before = await client.getAccount(beneficiaryAddress); + expect(before).toBeUndefined(); + + // send + const result = await client.sendTokens(beneficiaryAddress, transferAmount, "for dinner"); + const [firstLog] = result.logs; + expect(firstLog).toBeTruthy(); + + // got tokens + const after = await client.getAccount(beneficiaryAddress); + assert(after); + expect(after.balance).toEqual(transferAmount); + }); + }); +}); diff --git a/packages/sdk38/src/signingcosmosclient.ts b/packages/sdk38/src/signingcosmosclient.ts new file mode 100644 index 00000000..f20c7fef --- /dev/null +++ b/packages/sdk38/src/signingcosmosclient.ts @@ -0,0 +1,145 @@ +import { Coin, coins } from "./coins"; +import { Account, CosmosClient, GetNonceResult, PostTxResult } from "./cosmosclient"; +import { makeSignBytes } from "./encoding"; +import { Log } from "./logs"; +import { BroadcastMode } from "./restclient"; +import { MsgSend, StdFee, StdSignature } from "./types"; + +export interface SigningCallback { + (signBytes: Uint8Array): Promise; +} + +export interface FeeTable { + readonly upload: StdFee; + readonly init: StdFee; + readonly exec: StdFee; + readonly send: StdFee; +} + +const defaultFees: FeeTable = { + upload: { + amount: coins(25000, "ucosm"), + gas: "1000000", // one million + }, + init: { + amount: coins(12500, "ucosm"), + gas: "500000", // 500k + }, + exec: { + amount: coins(5000, "ucosm"), + gas: "200000", // 200k + }, + send: { + amount: coins(2000, "ucosm"), + gas: "80000", // 80k + }, +}; + +export interface UploadMeta { + /** The source URL */ + readonly source?: string; + /** The builder tag */ + readonly builder?: string; +} + +export interface UploadResult { + /** 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; + /** The ID of the code asigned by the chain */ + readonly codeId: number; + readonly logs: readonly Log[]; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; +} + +export interface InstantiateResult { + /** The address of the newly instantiated contract */ + readonly contractAddress: string; + readonly logs: readonly Log[]; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; +} + +export interface ExecuteResult { + readonly logs: readonly Log[]; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; +} + +export class SigningCosmosClient extends CosmosClient { + public readonly senderAddress: string; + + private readonly signCallback: SigningCallback; + private readonly fees: FeeTable; + + /** + * Creates a new client with signing capability to interact with a CosmWasm blockchain. This is the bigger brother of CosmWasmClient. + * + * This instance does a lot of caching. In order to benefit from that you should try to use one instance + * for the lifetime of your application. When switching backends, a new instance must be created. + * + * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) + * @param senderAddress The address that will sign and send transactions using this instance + * @param signCallback An asynchonous callback to create a signature for a given transaction. This can be implemented using secure key stores that require user interaction. + * @param customFees The fees that are paid for transactions + * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns + */ + public constructor( + apiUrl: string, + senderAddress: string, + signCallback: SigningCallback, + customFees?: Partial, + broadcastMode = BroadcastMode.Block, + ) { + super(apiUrl, broadcastMode); + this.anyValidAddress = senderAddress; + + this.senderAddress = senderAddress; + this.signCallback = signCallback; + this.fees = { ...defaultFees, ...(customFees || {}) }; + } + + public async getNonce(address?: string): Promise { + return super.getNonce(address || this.senderAddress); + } + + public async getAccount(address?: string): Promise { + return super.getAccount(address || this.senderAddress); + } + + public async sendTokens( + recipientAddress: string, + transferAmount: readonly Coin[], + memo = "", + ): Promise { + const sendMsg: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + // eslint-disable-next-line @typescript-eslint/camelcase + from_address: this.senderAddress, + // eslint-disable-next-line @typescript-eslint/camelcase + to_address: recipientAddress, + amount: transferAmount, + }, + }; + const fee = this.fees.send; + const { accountNumber, sequence } = await this.getNonce(); + const chainId = await this.getChainId(); + const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); + const signature = await this.signCallback(signBytes); + const signedTx = { + msg: [sendMsg], + fee: fee, + memo: memo, + signatures: [signature], + }; + + return this.postTx(signedTx); + } +} diff --git a/packages/sdk38/src/testdata/cosmoshub.json b/packages/sdk38/src/testdata/cosmoshub.json new file mode 100644 index 00000000..cb33539c --- /dev/null +++ b/packages/sdk38/src/testdata/cosmoshub.json @@ -0,0 +1,44 @@ +{ + "//source": "https://hubble.figment.network/cosmos/chains/cosmoshub-3/blocks/415777/transactions/2BD600EA6090FC75FD844CA73542CC90A828770F4C01C5B483C3C1C43CCB65F4?format=json", + "tx": { + "type": "cosmos-sdk/StdTx", + "value": { + "msg": [ + { + "type": "cosmos-sdk/MsgSend", + "value": { + "from_address": "cosmos1txqfn5jmcts0x0q7krdxj8tgf98tj0965vqlmq", + "to_address": "cosmos1nynns8ex9fq6sjjfj8k79ymkdz4sqth06xexae", + "amount": [ + { + "denom": "uatom", + "amount": "35997500" + } + ] + } + } + ], + "fee": { + "amount": [ + { + "denom": "uatom", + "amount": "2500" + } + ], + "gas": "100000" + }, + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A5qFcJBJvEK/fOmEAY0DHNWwSRZ9TEfNZyH8VoVvDtAq" + }, + "signature": "NK1Oy4EUGAsoC03c1wi9GG03JC/39LEdautC5Jk643oIbEPqeXHMwaqbdvO/Jws0X/NAXaN8SAy2KNY5Qml+5Q==" + } + ], + "memo": "" + } + }, + "tx_data": "ygEoKBapCkOoo2GaChRZgJnSW8Lg8zwesNppHWhJTrk8uhIUmSc4HyYqQahKSZHt4pN2aKsALu8aEQoFdWF0b20SCDM1OTk3NTAwEhMKDQoFdWF0b20SBDI1MDAQoI0GGmoKJuta6YchA5qFcJBJvEK/fOmEAY0DHNWwSRZ9TEfNZyH8VoVvDtAqEkA0rU7LgRQYCygLTdzXCL0YbTckL/f0sR1q60LkmTrjeghsQ+p5cczBqpt2878nCzRf80Bdo3xIDLYo1jlCaX7l", + "id": "2BD600EA6090FC75FD844CA73542CC90A828770F4C01C5B483C3C1C43CCB65F4" +} diff --git a/packages/sdk38/src/testdata/txresponse1.json b/packages/sdk38/src/testdata/txresponse1.json new file mode 100644 index 00000000..703ea696 --- /dev/null +++ b/packages/sdk38/src/testdata/txresponse1.json @@ -0,0 +1,57 @@ +{ + "height": "15888", + "txhash": "672DEDE8EF4DE8B5818959F417CCA357079D4D7A19C4B65443C7FBF8176AABF9", + "raw_log": "[{\"msg_index\":0,\"log\":\"\",\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2\"},{\"key\":\"amount\",\"value\":\"75000ucosm\"}]}]}]", + "logs": [ + { + "msg_index": 0, + "log": "", + "events": [ + { + "type": "message", + "attributes": [ + { "key": "action", "value": "send" }, + { "key": "sender", "value": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6" }, + { "key": "module", "value": "bank" } + ] + }, + { + "type": "transfer", + "attributes": [ + { "key": "recipient", "value": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2" }, + { "key": "amount", "value": "75000ucosm" } + ] + } + ] + } + ], + "gas_wanted": "200000", + "gas_used": "65407", + "tx": { + "type": "cosmos-sdk/StdTx", + "value": { + "msg": [ + { + "type": "cosmos-sdk/MsgSend", + "value": { + "from_address": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + "to_address": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2", + "amount": [{ "denom": "ucosm", "amount": "75000" }] + } + } + ], + "fee": { "amount": [{ "denom": "ucosm", "amount": "5000" }], "gas": "200000" }, + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ" + }, + "signature": "US7oH8S/8TxVrtBQkOhHxAM+oDB2spNAEawgh6H8CCFLRMOJK+uvQZZ6ceUgUsvDbxwCz7re1RU272fymMYRZQ==" + } + ], + "memo": "My first payment" + } + }, + "timestamp": "2020-02-14T11:25:55Z" +} diff --git a/packages/sdk38/src/testdata/txresponse2.json b/packages/sdk38/src/testdata/txresponse2.json new file mode 100644 index 00000000..0d1a37f9 --- /dev/null +++ b/packages/sdk38/src/testdata/txresponse2.json @@ -0,0 +1,57 @@ +{ + "height": "16456", + "txhash": "7BFE4B93AF190F60132C62D08FDF50BE462FBCE374EB13D3FD0C32461E771EC0", + "raw_log": "[{\"msg_index\":0,\"log\":\"\",\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2\"},{\"key\":\"amount\",\"value\":\"75000ucosm\"}]}]}]", + "logs": [ + { + "msg_index": 0, + "log": "", + "events": [ + { + "type": "message", + "attributes": [ + { "key": "action", "value": "send" }, + { "key": "sender", "value": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6" }, + { "key": "module", "value": "bank" } + ] + }, + { + "type": "transfer", + "attributes": [ + { "key": "recipient", "value": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2" }, + { "key": "amount", "value": "75000ucosm" } + ] + } + ] + } + ], + "gas_wanted": "200000", + "gas_used": "65407", + "tx": { + "type": "cosmos-sdk/StdTx", + "value": { + "msg": [ + { + "type": "cosmos-sdk/MsgSend", + "value": { + "from_address": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + "to_address": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2", + "amount": [{ "denom": "ucosm", "amount": "75000" }] + } + } + ], + "fee": { "amount": [{ "denom": "ucosm", "amount": "5000" }], "gas": "200000" }, + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ" + }, + "signature": "ltvd9Rb3RF4zjbUVrpDpkok34g+py7XR8ZcM0tZUYRxxVdcMEin010x+ZFd/mOuutPj9fDmSENnienc/yi4msw==" + } + ], + "memo": "My first payment" + } + }, + "timestamp": "2020-02-14T11:35:41Z" +} diff --git a/packages/sdk38/src/testdata/txresponse3.json b/packages/sdk38/src/testdata/txresponse3.json new file mode 100644 index 00000000..8ffd0727 --- /dev/null +++ b/packages/sdk38/src/testdata/txresponse3.json @@ -0,0 +1,57 @@ +{ + "height": "20730", + "txhash": "625BC75E697F73DA037387C34002BB2F682E7ACDCC4E015D3E90420516C6D0C8", + "raw_log": "[{\"msg_index\":0,\"log\":\"\",\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2\"},{\"key\":\"amount\",\"value\":\"75000ucosm\"}]}]}]", + "logs": [ + { + "msg_index": 0, + "log": "", + "events": [ + { + "type": "message", + "attributes": [ + { "key": "action", "value": "send" }, + { "key": "sender", "value": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6" }, + { "key": "module", "value": "bank" } + ] + }, + { + "type": "transfer", + "attributes": [ + { "key": "recipient", "value": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2" }, + { "key": "amount", "value": "75000ucosm" } + ] + } + ] + } + ], + "gas_wanted": "200000", + "gas_used": "65407", + "tx": { + "type": "cosmos-sdk/StdTx", + "value": { + "msg": [ + { + "type": "cosmos-sdk/MsgSend", + "value": { + "from_address": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + "to_address": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2", + "amount": [{ "denom": "ucosm", "amount": "75000" }] + } + } + ], + "fee": { "amount": [{ "denom": "ucosm", "amount": "5000" }], "gas": "200000" }, + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ" + }, + "signature": "eOFGl1tIHDMv3JdCK9fRSikVbYUD8+B0ksb3dJFya8MPYgpEpdSA7zZc+5n/cW6LR/BJdib4nqmJQv1yD9lm3g==" + } + ], + "memo": "My first payment" + } + }, + "timestamp": "2020-02-14T12:48:56Z" +} diff --git a/packages/sdk38/src/testutils.spec.ts b/packages/sdk38/src/testutils.spec.ts new file mode 100644 index 00000000..7b8f2330 --- /dev/null +++ b/packages/sdk38/src/testutils.spec.ts @@ -0,0 +1,58 @@ +import { Random } from "@iov/crypto"; +import { Bech32 } from "@iov/encoding"; + +export function makeRandomAddress(): string { + return Bech32.encode("cosmos", Random.getBytes(20)); +} + +export const nonNegativeIntegerMatcher = /^[0-9]+$/; +export const tendermintIdMatcher = /^[0-9A-F]{64}$/; +export const tendermintOptionalIdMatcher = /^([0-9A-F]{64}|)$/; +export const tendermintAddressMatcher = /^[0-9A-F]{40}$/; +export const tendermintShortHashMatcher = /^[0-9a-f]{40}$/; +export const semverMatcher = /^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$/; + +// 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}$/; + +export const wasmd = { + endpoint: "http://localhost:1317", + chainId: "testing", +}; + +export const faucet = { + 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", + pubkey: { + type: "tendermint/PubKeySecp256k1", + value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", + }, + address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", +}; + +/** Unused account */ +export const unused = { + pubkey: { + type: "tendermint/PubKeySecp256k1", + value: "ArkCaFUJ/IH+vKBmNRCdUVl3mCAhbopk9jjW4Ko4OfRQ", + }, + address: "cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u", + accountNumber: 9, + sequence: 0, +}; + +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]; +} diff --git a/packages/sdk38/src/types.ts b/packages/sdk38/src/types.ts new file mode 100644 index 00000000..7d10fe3c --- /dev/null +++ b/packages/sdk38/src/types.ts @@ -0,0 +1,105 @@ +import { Encoding } from "@iov/encoding"; + +import { Coin } from "./coins"; + +const { fromBase64, fromHex } = Encoding; + +/** An Amino/Cosmos SDK StdTx */ +export interface StdTx { + readonly msg: ReadonlyArray; + readonly fee: StdFee; + readonly signatures: ReadonlyArray; + readonly memo: string | undefined; +} + +export function isStdTx(txValue: unknown): txValue is StdTx { + const { memo, msg, fee, signatures } = txValue as StdTx; + return ( + typeof memo === "string" && Array.isArray(msg) && typeof fee === "object" && Array.isArray(signatures) + ); +} + +export interface CosmosSdkTx { + readonly type: string; + readonly value: StdTx; +} + +interface MsgTemplate { + readonly type: string; + readonly value: any; +} + +/** A Cosmos SDK token transfer message */ +export interface MsgSend extends MsgTemplate { + readonly type: "cosmos-sdk/MsgSend"; + readonly value: { + /** Bech32 account address */ + readonly from_address: string; + /** Bech32 account address */ + readonly to_address: string; + readonly amount: ReadonlyArray; + }; +} + +export type Msg = MsgSend | MsgTemplate; + +export function isMsgSend(msg: Msg): msg is MsgSend { + return (msg as MsgSend).type === "cosmos-sdk/MsgSend"; +} + +export interface StdFee { + readonly amount: ReadonlyArray; + readonly gas: string; +} + +export interface StdSignature { + readonly pub_key: PubKey; + readonly signature: string; +} + +export interface PubKey { + // type is one of the strings defined in pubkeyTypes + // I don't use a string literal union here as that makes trouble with json test data: + // https://github.com/confio/cosmwasm-js/pull/44#pullrequestreview-353280504 + readonly type: string; + // Value field is base64-encoded in all cases + // Note: if type is Secp256k1, this must contain a COMPRESSED pubkey - to encode from bcp/keycontrol land, you must compress it first + readonly value: string; +} + +export const pubkeyType = { + /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/ed25519/ed25519.go#L22 */ + secp256k1: "tendermint/PubKeySecp256k1" as const, + /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/secp256k1/secp256k1.go#L23 */ + ed25519: "tendermint/PubKeyEd25519" as const, + /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12 */ + sr25519: "tendermint/PubKeySr25519" as const, +}; + +export const pubkeyTypes: readonly string[] = [pubkeyType.secp256k1, pubkeyType.ed25519, pubkeyType.sr25519]; + +export interface WasmData { + // key is hex-encoded + readonly key: string; + // value is base64 encoded + readonly val: string; +} + +// Model is a parsed WasmData object +export interface Model { + readonly key: Uint8Array; + readonly val: Uint8Array; +} + +export function parseWasmData({ key, val }: WasmData): Model { + return { + key: fromHex(key), + val: fromBase64(val), + }; +} + +/** + * An object containing a parsed JSON document. The result of JSON.parse(). + * This doen't privide any type safety over `any` but expresses intent in the code. + */ +export type JsonObject = any; diff --git a/packages/sdk38/tsconfig.json b/packages/sdk38/tsconfig.json new file mode 100644 index 00000000..167e8c02 --- /dev/null +++ b/packages/sdk38/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "build", + "declarationDir": "build/types", + "rootDir": "src" + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/sdk38/typedoc.js b/packages/sdk38/typedoc.js new file mode 100644 index 00000000..e2387c7d --- /dev/null +++ b/packages/sdk38/typedoc.js @@ -0,0 +1,14 @@ +const packageJson = require("./package.json"); + +module.exports = { + src: ["./src"], + out: "docs", + exclude: "**/*.spec.ts", + target: "es6", + name: `${packageJson.name} Documentation`, + readme: "README.md", + mode: "file", + excludeExternals: true, + excludeNotExported: true, + excludePrivate: true, +}; diff --git a/packages/sdk38/types/address.d.ts b/packages/sdk38/types/address.d.ts new file mode 100644 index 00000000..9eb5af82 --- /dev/null +++ b/packages/sdk38/types/address.d.ts @@ -0,0 +1,3 @@ +import { PubKey } from "./types"; +export declare function rawSecp256k1PubkeyToAddress(pubkeyRaw: Uint8Array, prefix: string): string; +export declare function pubkeyToAddress(pubkey: PubKey, prefix: string): string; diff --git a/packages/sdk38/types/coins.d.ts b/packages/sdk38/types/coins.d.ts new file mode 100644 index 00000000..9208f3e2 --- /dev/null +++ b/packages/sdk38/types/coins.d.ts @@ -0,0 +1,8 @@ +export interface Coin { + readonly denom: string; + readonly amount: string; +} +/** Creates a coin */ +export declare function coin(amount: number, denom: string): Coin; +/** Creates a list of coins with one element */ +export declare function coins(amount: number, denom: string): Coin[]; diff --git a/packages/sdk38/types/cosmosclient.d.ts b/packages/sdk38/types/cosmosclient.d.ts new file mode 100644 index 00000000..0dcc72bb --- /dev/null +++ b/packages/sdk38/types/cosmosclient.d.ts @@ -0,0 +1,175 @@ +import { Coin } from "./coins"; +import { Log } from "./logs"; +import { BroadcastMode, RestClient } from "./restclient"; +import { CosmosSdkTx, JsonObject, PubKey, StdTx } from "./types"; +export interface GetNonceResult { + readonly accountNumber: number; + readonly sequence: number; +} +export interface Account { + /** Bech32 account address */ + readonly address: string; + readonly balance: ReadonlyArray; + readonly pubkey: PubKey | undefined; + readonly accountNumber: number; + readonly sequence: number; +} +export interface PostTxResult { + readonly logs: readonly Log[]; + readonly rawLog: string; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; +} +export interface SearchByIdQuery { + readonly id: string; +} +export interface SearchByHeightQuery { + readonly height: number; +} +export interface SearchBySentFromOrToQuery { + readonly sentFromOrTo: string; +} +/** + * This query type allows you to pass arbitrary key/value pairs to the backend. It is + * more powerful and slightly lower level than the other search options. + */ +export interface SearchByTagsQuery { + readonly tags: readonly { + readonly key: string; + readonly value: string; + }[]; +} +export declare type SearchTxQuery = + | SearchByIdQuery + | SearchByHeightQuery + | SearchBySentFromOrToQuery + | SearchByTagsQuery; +export interface SearchTxFilter { + readonly minHeight?: number; + readonly maxHeight?: number; +} +export interface Code { + readonly id: number; + /** Bech32 account address */ + readonly creator: string; + /** Hex-encoded sha256 hash of the code stored here */ + readonly checksum: string; + readonly source?: string; + readonly builder?: string; +} +export interface CodeDetails extends Code { + /** The original wasm bytes */ + readonly data: Uint8Array; +} +export interface Contract { + readonly address: string; + readonly codeId: number; + /** Bech32 account address */ + readonly creator: string; + readonly label: string; +} +export interface ContractDetails extends Contract { + /** Argument passed on initialization of the contract */ + readonly initMsg: object; +} +/** A transaction that is indexed as part of the transaction history */ +export interface IndexedTx { + readonly height: number; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly hash: string; + /** Transaction execution error code. 0 on success. */ + readonly code: number; + readonly rawLog: string; + readonly logs: readonly Log[]; + readonly tx: CosmosSdkTx; + /** The gas limit as set by the user */ + readonly gasWanted?: number; + /** The gas used by the execution */ + readonly gasUsed?: number; + /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ + readonly timestamp: string; +} +export interface BlockHeader { + readonly version: { + readonly block: string; + readonly app: string; + }; + readonly height: number; + readonly chainId: string; + /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ + readonly time: string; +} +export interface Block { + /** The ID is a hash of the block header (uppercase hex) */ + readonly id: string; + readonly header: BlockHeader; + /** Array of raw transactions */ + readonly txs: ReadonlyArray; +} +/** Use for testing only */ +export interface PrivateCosmWasmClient { + readonly restClient: RestClient; +} +export declare class CosmosClient { + protected readonly restClient: RestClient; + /** Any address the chain considers valid (valid bech32 with proper prefix) */ + protected anyValidAddress: string | undefined; + private readonly codesCache; + private chainId; + /** + * Creates a new client to interact with a CosmWasm blockchain. + * + * This instance does a lot of caching. In order to benefit from that you should try to use one instance + * for the lifetime of your application. When switching backends, a new instance must be created. + * + * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) + * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns + */ + constructor(apiUrl: string, broadcastMode?: BroadcastMode); + getChainId(): Promise; + getHeight(): Promise; + /** + * Returns a 32 byte upper-case hex transaction hash (typically used as the transaction ID) + */ + getIdentifier(tx: CosmosSdkTx): Promise; + /** + * Returns account number and sequence. + * + * Throws if the account does not exist on chain. + * + * @param address returns data for this address. When unset, the client's sender adddress is used. + */ + getNonce(address: string): Promise; + getAccount(address: string): Promise; + /** + * Gets block header and meta + * + * @param height The height of the block. If undefined, the latest height is used. + */ + getBlock(height?: number): Promise; + searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise; + postTx(tx: StdTx): Promise; + getCodes(): Promise; + getCodeDetails(codeId: number): Promise; + getContracts(codeId: number): Promise; + /** + * Throws an error if no contract was found at the address + */ + getContract(address: string): Promise; + /** + * 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. + */ + queryContractRaw(address: string, key: Uint8Array): Promise; + /** + * 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. + */ + queryContractSmart(address: string, queryMsg: object): Promise; + private txsQuery; +} diff --git a/packages/sdk38/types/decoding.d.ts b/packages/sdk38/types/decoding.d.ts new file mode 100644 index 00000000..fd0c5746 --- /dev/null +++ b/packages/sdk38/types/decoding.d.ts @@ -0,0 +1,2 @@ +import { StdTx } from "./types"; +export declare function unmarshalTx(data: Uint8Array): StdTx; diff --git a/packages/sdk38/types/encoding.d.ts b/packages/sdk38/types/encoding.d.ts new file mode 100644 index 00000000..4d32b80b --- /dev/null +++ b/packages/sdk38/types/encoding.d.ts @@ -0,0 +1,10 @@ +import { Msg, StdFee, StdTx } from "./types"; +export declare function marshalTx(tx: StdTx): Uint8Array; +export declare function makeSignBytes( + msgs: readonly Msg[], + fee: StdFee, + chainId: string, + memo: string, + accountNumber: number, + sequence: number, +): Uint8Array; diff --git a/packages/sdk38/types/index.d.ts b/packages/sdk38/types/index.d.ts new file mode 100644 index 00000000..e42bf6c3 --- /dev/null +++ b/packages/sdk38/types/index.d.ts @@ -0,0 +1,11 @@ +import * as logs from "./logs"; +import * as types from "./types"; +export { logs, types }; +export { pubkeyToAddress } from "./address"; +export { Coin, coin, coins } from "./coins"; +export { unmarshalTx } from "./decoding"; +export { makeSignBytes, marshalTx } from "./encoding"; +export { BroadcastMode, RestClient, TxsResponse } from "./restclient"; +export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; +export { findSequenceForSignedTx } from "./sequence"; +export { encodeSecp256k1Signature, decodeSignature } from "./signature"; diff --git a/packages/sdk38/types/logs.d.ts b/packages/sdk38/types/logs.d.ts new file mode 100644 index 00000000..2e1decb3 --- /dev/null +++ b/packages/sdk38/types/logs.d.ts @@ -0,0 +1,28 @@ +export interface Attribute { + readonly key: string; + readonly value: string; +} +export interface Event { + readonly type: string; + readonly attributes: readonly Attribute[]; +} +export interface Log { + readonly msg_index: number; + readonly log: string; + readonly events: readonly Event[]; +} +export declare function parseAttribute(input: unknown): Attribute; +export declare function parseEvent(input: unknown): Event; +export declare function parseLog(input: unknown): Log; +export declare function parseLogs(input: unknown): readonly Log[]; +/** + * Searches in logs for the first event of the given event type and in that event + * for the first first attribute with the given attribute key. + * + * Throws if the attribute was not found. + */ +export declare function findAttribute( + logs: readonly Log[], + eventType: "message" | "transfer", + attrKey: string, +): Attribute; diff --git a/packages/sdk38/types/pen.d.ts b/packages/sdk38/types/pen.d.ts new file mode 100644 index 00000000..068db3cb --- /dev/null +++ b/packages/sdk38/types/pen.d.ts @@ -0,0 +1,33 @@ +import { Slip10RawIndex } from "@iov/crypto"; +import { StdSignature } from "./types"; +export declare type PrehashType = "sha256" | "sha512" | null; +/** + * A pen is the most basic tool you can think of for signing. It works + * everywhere and can be used intuitively by everyone. However, it does not + * come with a great amount of features. End of semi suitable metaphor. + * + * This wraps a single keypair and allows for signing. + * + * Non-goals of this types are: multi account support, persistency, data migrations, + * obfuscation of sensitive data. + */ +export interface Pen { + readonly pubkey: Uint8Array; + readonly sign: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise; +} +/** + * The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a` + * with 0-based account index `a`. + */ +export declare function makeCosmoshubPath(a: number): readonly Slip10RawIndex[]; +export declare class Secp256k1Pen implements Pen { + static fromMnemonic(mnemonic: string, hdPath?: readonly Slip10RawIndex[]): Promise; + readonly pubkey: Uint8Array; + private readonly privkey; + private constructor(); + /** + * Creates and returns a signature + */ + sign(signBytes: Uint8Array, prehashType?: PrehashType): Promise; + address(prefix: string): string; +} diff --git a/packages/sdk38/types/pubkey.d.ts b/packages/sdk38/types/pubkey.d.ts new file mode 100644 index 00000000..b6ebdebc --- /dev/null +++ b/packages/sdk38/types/pubkey.d.ts @@ -0,0 +1,4 @@ +import { PubKey } from "./types"; +export declare function encodeSecp256k1Pubkey(pubkey: Uint8Array): PubKey; +export declare function decodeBech32Pubkey(bechEncoded: string): PubKey; +export declare function encodeBech32Pubkey(pubkey: PubKey, prefix: string): string; diff --git a/packages/sdk38/types/restclient.d.ts b/packages/sdk38/types/restclient.d.ts new file mode 100644 index 00000000..5d5deea4 --- /dev/null +++ b/packages/sdk38/types/restclient.d.ts @@ -0,0 +1,231 @@ +import { Coin } from "./coins"; +import { CosmosSdkTx, JsonObject, Model, StdTx } from "./types"; +export interface CosmosSdkAccount { + /** Bech32 account address */ + readonly address: string; + readonly coins: ReadonlyArray; + /** Bech32 encoded pubkey */ + readonly public_key: string; + readonly account_number: number; + readonly sequence: number; +} +export interface NodeInfo { + readonly protocol_version: { + readonly p2p: string; + readonly block: string; + readonly app: string; + }; + readonly id: string; + readonly listen_addr: string; + readonly network: string; + readonly version: string; + readonly channels: string; + readonly moniker: string; + readonly other: { + readonly tx_index: string; + readonly rpc_address: string; + }; +} +export interface ApplicationVersion { + readonly name: string; + readonly server_name: string; + readonly client_name: string; + readonly version: string; + readonly commit: string; + readonly build_tags: string; + readonly go: string; +} +export interface NodeInfoResponse { + readonly node_info: NodeInfo; + readonly application_version: ApplicationVersion; +} +export interface BlockId { + readonly hash: string; +} +export interface BlockHeader { + readonly version: { + readonly block: string; + readonly app: string; + }; + readonly height: string; + readonly chain_id: string; + /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ + readonly time: string; + readonly last_commit_hash: string; + readonly last_block_id: BlockId; + /** Can be empty */ + readonly data_hash: string; + readonly validators_hash: string; + readonly next_validators_hash: string; + readonly consensus_hash: string; + readonly app_hash: string; + /** Can be empty */ + readonly last_results_hash: string; + /** Can be empty */ + readonly evidence_hash: string; + readonly proposer_address: string; +} +export interface Block { + readonly header: BlockHeader; + readonly data: { + /** Array of base64 encoded transactions */ + readonly txs: ReadonlyArray | null; + }; +} +export interface BlockResponse { + readonly block_id: BlockId; + readonly block: Block; +} +interface AuthAccountsResponse { + readonly height: string; + readonly result: { + readonly type: "cosmos-sdk/Account"; + readonly value: CosmosSdkAccount; + }; +} +declare type WasmResponse = WasmSuccess | WasmError; +interface WasmSuccess { + readonly height: string; + readonly result: T; +} +interface WasmError { + readonly error: string; +} +export interface TxsResponse { + readonly height: string; + readonly txhash: string; + /** 🤷‍♂️ */ + readonly codespace?: string; + /** Falsy when transaction execution succeeded. Contains error code on error. */ + readonly code?: number; + readonly raw_log: string; + readonly logs?: object; + readonly tx: CosmosSdkTx; + /** The gas limit as set by the user */ + readonly gas_wanted?: string; + /** The gas used by the execution */ + readonly gas_used?: string; + readonly timestamp: string; +} +interface SearchTxsResponse { + readonly total_count: string; + readonly count: string; + readonly page_number: string; + readonly page_total: string; + readonly limit: string; + readonly txs: readonly TxsResponse[]; +} +export interface PostTxsResponse { + readonly height: string; + readonly txhash: string; + readonly code?: number; + readonly raw_log?: string; + /** The same as `raw_log` but deserialized? */ + readonly logs?: object; + /** The gas limit as set by the user */ + readonly gas_wanted?: string; + /** The gas used by the execution */ + readonly gas_used?: string; +} +interface EncodeTxResponse { + readonly tx: string; +} +export interface CodeInfo { + readonly id: number; + /** Bech32 account address */ + readonly creator: string; + /** Hex-encoded sha256 hash of the code stored here */ + readonly data_hash: string; + readonly source?: string; + readonly builder?: string; +} +export interface CodeDetails extends CodeInfo { + /** Base64 encoded raw wasm data */ + readonly data: string; +} +export interface ContractInfo { + readonly address: string; + readonly code_id: number; + /** Bech32 account address */ + readonly creator: string; + readonly label: string; +} +export interface ContractDetails extends ContractInfo { + /** Argument passed on initialization of the contract */ + readonly init_msg: object; +} +declare type RestClientResponse = + | NodeInfoResponse + | BlockResponse + | AuthAccountsResponse + | TxsResponse + | SearchTxsResponse + | PostTxsResponse + | EncodeTxResponse + | WasmResponse + | WasmResponse + | WasmResponse + | WasmResponse + | WasmResponse; +/** + * The mode used to send transaction + * + * @see https://cosmos.network/rpc/#/Transactions/post_txs + */ +export declare enum BroadcastMode { + /** Return after tx commit */ + Block = "block", + /** Return afer CheckTx */ + Sync = "sync", + /** Return right away */ + Async = "async", +} +export declare class RestClient { + private readonly client; + private readonly broadcastMode; + /** + * Creates a new client to interact with a Cosmos SDK light client daemon. + * This class tries to be a direct mapping onto the API. Some basic decoding and normalizatin is done + * but things like caching are done at a higher level. + * + * When building apps, you should not need to use this class directly. If you do, this indicates a missing feature + * in higher level components. Feel free to raise an issue in this case. + * + * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) + * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns + */ + constructor(apiUrl: string, broadcastMode?: BroadcastMode); + get(path: string): Promise; + post(path: string, params: any): Promise; + authAccounts(address: string): Promise; + blocksLatest(): Promise; + blocks(height: number): Promise; + nodeInfo(): Promise; + txById(id: string): Promise; + txsQuery(query: string): Promise; + /** returns the amino-encoding of the transaction performed by the server */ + encodeTx(tx: CosmosSdkTx): Promise; + /** + * Broadcasts a signed transaction to into the transaction pool. + * Depending on the RestClient's broadcast mode, this might or might + * wait for checkTx or deliverTx to be executed before returning. + * + * @param tx a signed transaction as StdTx (i.e. not wrapped in type/value container) + */ + postTx(tx: StdTx): Promise; + listCodeInfo(): Promise; + getCode(id: number): Promise; + listContractsByCodeId(id: number): Promise; + /** + * Returns null when contract was not found at this address. + */ + getContractInfo(address: string): Promise; + getAllContractState(address: string): Promise; + queryContractRaw(address: string, key: Uint8Array): Promise; + /** + * Makes a smart query on the contract and parses the reponse as JSON. + * Throws error if no such contract exists, the query format is invalid or the response is invalid. + */ + queryContractSmart(address: string, query: object): Promise; +} +export {}; diff --git a/packages/sdk38/types/sequence.d.ts b/packages/sdk38/types/sequence.d.ts new file mode 100644 index 00000000..70d38469 --- /dev/null +++ b/packages/sdk38/types/sequence.d.ts @@ -0,0 +1,19 @@ +import { CosmosSdkTx } from "./types"; +/** + * Serach for sequence s with `min` <= `s` < `upperBound` to find the sequence that was used to sign the transaction + * + * @param tx The signed transaction + * @param chainId The chain ID for which this transaction was signed + * @param accountNumber The account number for which this transaction was signed + * @param upperBound The upper bound for the testing, i.e. sequence must be lower than this value + * @param min The lowest sequence that is tested + * + * @returns the sequence if a match was found and undefined otherwise + */ +export declare function findSequenceForSignedTx( + tx: CosmosSdkTx, + chainId: string, + accountNumber: number, + upperBound: number, + min?: number, +): Promise; diff --git a/packages/sdk38/types/signature.d.ts b/packages/sdk38/types/signature.d.ts new file mode 100644 index 00000000..c23e7167 --- /dev/null +++ b/packages/sdk38/types/signature.d.ts @@ -0,0 +1,14 @@ +import { StdSignature } from "./types"; +/** + * Takes a binary pubkey and signature to create a signature object + * + * @param pubkey a compressed secp256k1 public key + * @param signature a 64 byte fixed length representation of secp256k1 signature components r and s + */ +export declare function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature; +export declare function decodeSignature( + signature: StdSignature, +): { + readonly pubkey: Uint8Array; + readonly signature: Uint8Array; +}; diff --git a/packages/sdk38/types/signingcosmosclient.d.ts b/packages/sdk38/types/signingcosmosclient.d.ts new file mode 100644 index 00000000..00202b7c --- /dev/null +++ b/packages/sdk38/types/signingcosmosclient.d.ts @@ -0,0 +1,74 @@ +import { Coin } from "./coins"; +import { Account, CosmosClient, GetNonceResult, PostTxResult } from "./cosmosclient"; +import { Log } from "./logs"; +import { BroadcastMode } from "./restclient"; +import { StdFee, StdSignature } from "./types"; +export interface SigningCallback { + (signBytes: Uint8Array): Promise; +} +export interface FeeTable { + readonly upload: StdFee; + readonly init: StdFee; + readonly exec: StdFee; + readonly send: StdFee; +} +export interface UploadMeta { + /** The source URL */ + readonly source?: string; + /** The builder tag */ + readonly builder?: string; +} +export interface UploadResult { + /** 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; + /** The ID of the code asigned by the chain */ + readonly codeId: number; + readonly logs: readonly Log[]; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; +} +export interface InstantiateResult { + /** The address of the newly instantiated contract */ + readonly contractAddress: string; + readonly logs: readonly Log[]; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; +} +export interface ExecuteResult { + readonly logs: readonly Log[]; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; +} +export declare class SigningCosmosClient extends CosmosClient { + readonly senderAddress: string; + private readonly signCallback; + private readonly fees; + /** + * Creates a new client with signing capability to interact with a CosmWasm blockchain. This is the bigger brother of CosmWasmClient. + * + * This instance does a lot of caching. In order to benefit from that you should try to use one instance + * for the lifetime of your application. When switching backends, a new instance must be created. + * + * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) + * @param senderAddress The address that will sign and send transactions using this instance + * @param signCallback An asynchonous callback to create a signature for a given transaction. This can be implemented using secure key stores that require user interaction. + * @param customFees The fees that are paid for transactions + * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns + */ + constructor( + apiUrl: string, + senderAddress: string, + signCallback: SigningCallback, + customFees?: Partial, + broadcastMode?: BroadcastMode, + ); + getNonce(address?: string): Promise; + getAccount(address?: string): Promise; + sendTokens(recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise; +} diff --git a/packages/sdk38/types/types.d.ts b/packages/sdk38/types/types.d.ts new file mode 100644 index 00000000..a38796a2 --- /dev/null +++ b/packages/sdk38/types/types.d.ts @@ -0,0 +1,66 @@ +import { Coin } from "./coins"; +/** An Amino/Cosmos SDK StdTx */ +export interface StdTx { + readonly msg: ReadonlyArray; + readonly fee: StdFee; + readonly signatures: ReadonlyArray; + readonly memo: string | undefined; +} +export declare function isStdTx(txValue: unknown): txValue is StdTx; +export interface CosmosSdkTx { + readonly type: string; + readonly value: StdTx; +} +interface MsgTemplate { + readonly type: string; + readonly value: any; +} +/** A Cosmos SDK token transfer message */ +export interface MsgSend extends MsgTemplate { + readonly type: "cosmos-sdk/MsgSend"; + readonly value: { + /** Bech32 account address */ + readonly from_address: string; + /** Bech32 account address */ + readonly to_address: string; + readonly amount: ReadonlyArray; + }; +} +export declare type Msg = MsgSend | MsgTemplate; +export declare function isMsgSend(msg: Msg): msg is MsgSend; +export interface StdFee { + readonly amount: ReadonlyArray; + readonly gas: string; +} +export interface StdSignature { + readonly pub_key: PubKey; + readonly signature: string; +} +export interface PubKey { + readonly type: string; + readonly value: string; +} +export declare const pubkeyType: { + /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/ed25519/ed25519.go#L22 */ + secp256k1: "tendermint/PubKeySecp256k1"; + /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/secp256k1/secp256k1.go#L23 */ + ed25519: "tendermint/PubKeyEd25519"; + /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12 */ + sr25519: "tendermint/PubKeySr25519"; +}; +export declare const pubkeyTypes: readonly string[]; +export interface WasmData { + readonly key: string; + readonly val: string; +} +export interface Model { + readonly key: Uint8Array; + readonly val: Uint8Array; +} +export declare function parseWasmData({ key, val }: WasmData): Model; +/** + * An object containing a parsed JSON document. The result of JSON.parse(). + * This doen't privide any type safety over `any` but expresses intent in the code. + */ +export declare type JsonObject = any; +export {}; diff --git a/packages/sdk38/webpack.web.config.js b/packages/sdk38/webpack.web.config.js new file mode 100644 index 00000000..7373cace --- /dev/null +++ b/packages/sdk38/webpack.web.config.js @@ -0,0 +1,19 @@ +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(["WASMD_ENABLED"])], + }, +]; From a0cb8011e15f926f162016c3ce0cb8869a9aeb8e Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 2 Jun 2020 13:18:31 +0200 Subject: [PATCH 02/15] Export rawSecp256k1PubkeyToAddress, Pen, Secp256k1Pen, makeCosmoshubPath --- packages/sdk38/src/index.ts | 3 ++- packages/sdk38/types/index.d.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/sdk38/src/index.ts b/packages/sdk38/src/index.ts index b17e620d..8ba8ea86 100644 --- a/packages/sdk38/src/index.ts +++ b/packages/sdk38/src/index.ts @@ -2,11 +2,12 @@ import * as logs from "./logs"; import * as types from "./types"; export { logs, types }; -export { pubkeyToAddress } from "./address"; +export { pubkeyToAddress, rawSecp256k1PubkeyToAddress } from "./address"; export { Coin, coin, coins } from "./coins"; export { unmarshalTx } from "./decoding"; export { makeSignBytes, marshalTx } from "./encoding"; export { BroadcastMode, RestClient, TxsResponse } from "./restclient"; +export { Pen, Secp256k1Pen, makeCosmoshubPath } from "./pen"; export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; export { findSequenceForSignedTx } from "./sequence"; export { encodeSecp256k1Signature, decodeSignature } from "./signature"; diff --git a/packages/sdk38/types/index.d.ts b/packages/sdk38/types/index.d.ts index e42bf6c3..6f9a672b 100644 --- a/packages/sdk38/types/index.d.ts +++ b/packages/sdk38/types/index.d.ts @@ -1,11 +1,12 @@ import * as logs from "./logs"; import * as types from "./types"; export { logs, types }; -export { pubkeyToAddress } from "./address"; +export { pubkeyToAddress, rawSecp256k1PubkeyToAddress } from "./address"; export { Coin, coin, coins } from "./coins"; export { unmarshalTx } from "./decoding"; export { makeSignBytes, marshalTx } from "./encoding"; export { BroadcastMode, RestClient, TxsResponse } from "./restclient"; +export { Pen, Secp256k1Pen, makeCosmoshubPath } from "./pen"; export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; export { findSequenceForSignedTx } from "./sequence"; export { encodeSecp256k1Signature, decodeSignature } from "./signature"; From dd54e7f5af0f9b615707a8e4ac248d2fc97ff617 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 2 Jun 2020 13:20:07 +0200 Subject: [PATCH 03/15] Add package @cosmwasm/cosmwasm --- packages/cosmwasm/.eslintignore | 1 + packages/cosmwasm/.gitignore | 3 + packages/cosmwasm/README.md | 12 + packages/cosmwasm/jasmine-testrunner.js | 26 + packages/cosmwasm/karma.conf.js | 54 + packages/cosmwasm/nonces/README.txt | 1 + packages/cosmwasm/package.json | 51 + packages/cosmwasm/src/builder.spec.ts | 63 + packages/cosmwasm/src/builder.ts | 20 + .../src/cosmwasmclient.searchtx.spec.ts | 465 ++++++ packages/cosmwasm/src/cosmwasmclient.spec.ts | 466 ++++++ packages/cosmwasm/src/cosmwasmclient.ts | 439 ++++++ packages/cosmwasm/src/index.ts | 34 + packages/cosmwasm/src/logs.spec.ts | 165 ++ packages/cosmwasm/src/logs.ts | 86 ++ packages/cosmwasm/src/pubkey.spec.ts | 57 + packages/cosmwasm/src/pubkey.ts | 71 + packages/cosmwasm/src/restclient.spec.ts | 1375 +++++++++++++++++ packages/cosmwasm/src/restclient.ts | 457 ++++++ .../src/signingcosmwasmclient.spec.ts | 229 +++ .../cosmwasm/src/signingcosmwasmclient.ts | 283 ++++ packages/cosmwasm/src/testdata/contract.json | 4 + packages/cosmwasm/src/testdata/cosmoshub.json | 44 + .../cosmwasm/src/testdata/txresponse1.json | 57 + .../cosmwasm/src/testdata/txresponse2.json | 57 + .../cosmwasm/src/testdata/txresponse3.json | 57 + packages/cosmwasm/src/testutils.spec.ts | 77 + packages/cosmwasm/src/types.ts | 173 +++ packages/cosmwasm/tsconfig.json | 12 + packages/cosmwasm/typedoc.js | 14 + packages/cosmwasm/types/builder.d.ts | 1 + packages/cosmwasm/types/cosmwasmclient.d.ts | 175 +++ packages/cosmwasm/types/index.d.ts | 33 + packages/cosmwasm/types/logs.d.ts | 28 + packages/cosmwasm/types/pubkey.d.ts | 4 + packages/cosmwasm/types/restclient.d.ts | 231 +++ .../cosmwasm/types/signingcosmwasmclient.d.ts | 89 ++ packages/cosmwasm/types/types.d.ts | 123 ++ packages/cosmwasm/webpack.web.config.js | 19 + 39 files changed, 5556 insertions(+) create mode 120000 packages/cosmwasm/.eslintignore create mode 100644 packages/cosmwasm/.gitignore create mode 100644 packages/cosmwasm/README.md create mode 100755 packages/cosmwasm/jasmine-testrunner.js create mode 100644 packages/cosmwasm/karma.conf.js create mode 100644 packages/cosmwasm/nonces/README.txt create mode 100644 packages/cosmwasm/package.json create mode 100644 packages/cosmwasm/src/builder.spec.ts create mode 100644 packages/cosmwasm/src/builder.ts create mode 100644 packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts create mode 100644 packages/cosmwasm/src/cosmwasmclient.spec.ts create mode 100644 packages/cosmwasm/src/cosmwasmclient.ts create mode 100644 packages/cosmwasm/src/index.ts create mode 100644 packages/cosmwasm/src/logs.spec.ts create mode 100644 packages/cosmwasm/src/logs.ts create mode 100644 packages/cosmwasm/src/pubkey.spec.ts create mode 100644 packages/cosmwasm/src/pubkey.ts create mode 100644 packages/cosmwasm/src/restclient.spec.ts create mode 100644 packages/cosmwasm/src/restclient.ts create mode 100644 packages/cosmwasm/src/signingcosmwasmclient.spec.ts create mode 100644 packages/cosmwasm/src/signingcosmwasmclient.ts create mode 100644 packages/cosmwasm/src/testdata/contract.json create mode 100644 packages/cosmwasm/src/testdata/cosmoshub.json create mode 100644 packages/cosmwasm/src/testdata/txresponse1.json create mode 100644 packages/cosmwasm/src/testdata/txresponse2.json create mode 100644 packages/cosmwasm/src/testdata/txresponse3.json create mode 100644 packages/cosmwasm/src/testutils.spec.ts create mode 100644 packages/cosmwasm/src/types.ts create mode 100644 packages/cosmwasm/tsconfig.json create mode 100644 packages/cosmwasm/typedoc.js create mode 100644 packages/cosmwasm/types/builder.d.ts create mode 100644 packages/cosmwasm/types/cosmwasmclient.d.ts create mode 100644 packages/cosmwasm/types/index.d.ts create mode 100644 packages/cosmwasm/types/logs.d.ts create mode 100644 packages/cosmwasm/types/pubkey.d.ts create mode 100644 packages/cosmwasm/types/restclient.d.ts create mode 100644 packages/cosmwasm/types/signingcosmwasmclient.d.ts create mode 100644 packages/cosmwasm/types/types.d.ts create mode 100644 packages/cosmwasm/webpack.web.config.js diff --git a/packages/cosmwasm/.eslintignore b/packages/cosmwasm/.eslintignore new file mode 120000 index 00000000..86039baf --- /dev/null +++ b/packages/cosmwasm/.eslintignore @@ -0,0 +1 @@ +../../.eslintignore \ No newline at end of file diff --git a/packages/cosmwasm/.gitignore b/packages/cosmwasm/.gitignore new file mode 100644 index 00000000..68bf3735 --- /dev/null +++ b/packages/cosmwasm/.gitignore @@ -0,0 +1,3 @@ +build/ +dist/ +docs/ diff --git a/packages/cosmwasm/README.md b/packages/cosmwasm/README.md new file mode 100644 index 00000000..ab2208fb --- /dev/null +++ b/packages/cosmwasm/README.md @@ -0,0 +1,12 @@ +# @cosmwasm/cosmwasm + +[![npm version](https://img.shields.io/npm/v/@cosmwasm/cosmwasm.svg)](https://www.npmjs.com/package/@cosmwasm/cosmwasm) + +An SDK to build CosmWasm clients. + +## License + +This package is part of the cosmwasm-js repository, licensed under the Apache +License 2.0 (see +[NOTICE](https://github.com/confio/cosmwasm-js/blob/master/NOTICE) and +[LICENSE](https://github.com/confio/cosmwasm-js/blob/master/LICENSE)). diff --git a/packages/cosmwasm/jasmine-testrunner.js b/packages/cosmwasm/jasmine-testrunner.js new file mode 100755 index 00000000..9fada59b --- /dev/null +++ b/packages/cosmwasm/jasmine-testrunner.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node + +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 }); + +// initialize and execute +jasmine.env.clearReporters(); +jasmine.addReporter(reporter); +jasmine.execute(); diff --git a/packages/cosmwasm/karma.conf.js b/packages/cosmwasm/karma.conf.js new file mode 100644 index 00000000..e68db403 --- /dev/null +++ b/packages/cosmwasm/karma.conf.js @@ -0,0 +1,54 @@ +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://npmjs.org/browse/keyword/karma-adapter + frameworks: ["jasmine"], + + // list of files / patterns to load in the browser + files: ["dist/web/tests.js"], + + client: { + jasmine: { + random: false, + timeoutInterval: 15000, + }, + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ["progress", "kjhtml"], + + // 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: false, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ["Firefox"], + + browserNoActivityTimeout: 90000, + + // Keep brower open for debugging. This is overridden by yarn scripts + singleRun: false, + + customLaunchers: { + ChromeHeadlessInsecure: { + base: "ChromeHeadless", + flags: ["--disable-web-security"], + }, + }, + }); +}; diff --git a/packages/cosmwasm/nonces/README.txt b/packages/cosmwasm/nonces/README.txt new file mode 100644 index 00000000..092fe732 --- /dev/null +++ b/packages/cosmwasm/nonces/README.txt @@ -0,0 +1 @@ +Directory used to trigger lerna package updates for all packages diff --git a/packages/cosmwasm/package.json b/packages/cosmwasm/package.json new file mode 100644 index 00000000..082cca20 --- /dev/null +++ b/packages/cosmwasm/package.json @@ -0,0 +1,51 @@ +{ + "name": "@cosmwasm/cosmwasm", + "version": "0.8.0", + "description": "CosmWasm SDK", + "author": "Ethan Frey ", + "license": "Apache-2.0", + "main": "build/index.js", + "types": "types/index.d.ts", + "files": [ + "build/", + "types/", + "*.md", + "!*.spec.*", + "!**/testdata/" + ], + "repository": { + "type": "git", + "url": "https://github.com/confio/cosmwasm-js/tree/master/packages/cosmwasm" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "docs": "shx rm -rf docs && typedoc --options typedoc.js", + "format": "prettier --write --loglevel warn \"./src/**/*.ts\"", + "lint": "eslint --max-warnings 0 \"**/*.{js,ts}\"", + "lint-fix": "eslint --max-warnings 0 \"**/*.{js,ts}\" --fix", + "move-types": "shx rm -rf ./types/* && shx mv build/types/* ./types && rm -rf ./types/testdata && shx rm -f ./types/*.spec.d.ts", + "format-types": "prettier --write --loglevel warn \"./types/**/*.d.ts\"", + "build": "shx rm -rf ./build && tsc && yarn move-types && yarn format-types", + "build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build", + "test-node": "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 ChromeHeadlessInsecure", + "test": "yarn build-or-skip && yarn test-node", + "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" + }, + "dependencies": { + "@cosmwasm/sdk38": "^0.8.0", + "@iov/crypto": "^2.1.0", + "@iov/encoding": "^2.1.0", + "@iov/utils": "^2.0.2", + "axios": "^0.19.0", + "fast-deep-equal": "^3.1.1", + "pako": "^1.0.11" + }, + "devDependencies": { + "@types/pako": "^1.0.1", + "readonly-date": "^1.0.0" + } +} diff --git a/packages/cosmwasm/src/builder.spec.ts b/packages/cosmwasm/src/builder.spec.ts new file mode 100644 index 00000000..20926f4c --- /dev/null +++ b/packages/cosmwasm/src/builder.spec.ts @@ -0,0 +1,63 @@ +import { isValidBuilder } from "./builder"; + +describe("builder", () => { + describe("isValidBuilder", () => { + // Valid cases + + it("returns true for simple examples", () => { + expect(isValidBuilder("myorg/super-optimizer:0.1.2")).toEqual(true); + expect(isValidBuilder("myorg/super-optimizer:42")).toEqual(true); + }); + + it("supports images with multi level names", () => { + expect(isValidBuilder("myorg/department-x/office-y/technology-z/super-optimizer:0.1.2")).toEqual(true); + }); + + it("returns true for tags with lower and upper chars", () => { + expect(isValidBuilder("myorg/super-optimizer:0.1.2-alpha")).toEqual(true); + expect(isValidBuilder("myorg/super-optimizer:0.1.2-Alpha")).toEqual(true); + }); + + // Invalid cases + + it("returns false for missing or empty tag", () => { + expect(isValidBuilder("myorg/super-optimizer")).toEqual(false); + expect(isValidBuilder("myorg/super-optimizer:")).toEqual(false); + }); + + it("returns false for name components starting or ending with a separator", () => { + expect(isValidBuilder(".myorg/super-optimizer:42")).toEqual(false); + expect(isValidBuilder("-myorg/super-optimizer:42")).toEqual(false); + expect(isValidBuilder("_myorg/super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg./super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg-/super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg_/super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg/.super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg/-super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg/_super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg/super-optimizer.:42")).toEqual(false); + expect(isValidBuilder("myorg/super-optimizer-:42")).toEqual(false); + expect(isValidBuilder("myorg/super-optimizer_:42")).toEqual(false); + }); + + it("returns false for upper case character in name component", () => { + expect(isValidBuilder("mYorg/super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg/super-Optimizer:42")).toEqual(false); + }); + + it("returns false for long images", () => { + expect( + isValidBuilder( + "myorgisnicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenice/super-optimizer:42", + ), + ).toEqual(false); + }); + + it("returns false for images with no organization", () => { + // Those are valid dockerhub images from https://hub.docker.com/_/ubuntu and https://hub.docker.com/_/rust + // but not valid in the context of CosmWasm Verify + expect(isValidBuilder("ubuntu:xenial-20200212")).toEqual(false); + expect(isValidBuilder("rust:1.40.0")).toEqual(false); + }); + }); +}); diff --git a/packages/cosmwasm/src/builder.ts b/packages/cosmwasm/src/builder.ts new file mode 100644 index 00000000..31a790c7 --- /dev/null +++ b/packages/cosmwasm/src/builder.ts @@ -0,0 +1,20 @@ +// A docker image regexp. We remove support for non-standard registries for simplicity. +// https://docs.docker.com/engine/reference/commandline/tag/#extended-description +// +// An image name is made up of slash-separated name components (optionally prefixed by a registry hostname). +// Name components may contain lowercase characters, digits and separators. +// A separator is defined as a period, one or two underscores, or one or more dashes. A name component may not start or end with a separator. +// +// A tag name must be valid ASCII and may contain lowercase and uppercase letters, digits, underscores, periods and dashes. +// A tag name may not start with a period or a dash and may contain a maximum of 128 characters. +const dockerImagePattern = new RegExp( + "^[a-z0-9][a-z0-9._-]*[a-z0-9](/[a-z0-9][a-z0-9._-]*[a-z0-9])+:[a-zA-Z0-9_][a-zA-Z0-9_.-]{0,127}$", +); + +/** Max length in bytes/characters (regexp enforces all ASCII, even if that is not required by the standard) */ +const builderMaxLength = 128; + +export function isValidBuilder(builder: string): boolean { + if (builder.length > builderMaxLength) return false; + return !!builder.match(dockerImagePattern); +} diff --git a/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts b/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts new file mode 100644 index 00000000..1b92ecba --- /dev/null +++ b/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts @@ -0,0 +1,465 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { Coin, Secp256k1Pen, makeSignBytes } from "@cosmwasm/sdk38"; +import { assert, sleep } from "@iov/utils"; + +import { CosmWasmClient } from "./cosmwasmclient"; +import { RestClient } from "./restclient"; +import { SigningCosmWasmClient } from "./signingcosmwasmclient"; +import { + deployedErc20, + faucet, + fromOneElementArray, + makeRandomAddress, + pendingWithoutWasmd, + wasmd, + wasmdEnabled, +} from "./testutils.spec"; +import { CosmosSdkTx, isMsgExecuteContract, isMsgInstantiateContract, isMsgSend, MsgSend } from "./types"; + +describe("CosmWasmClient.searchTx", () => { + let sendSuccessful: + | { + readonly sender: string; + readonly recipient: string; + readonly hash: string; + readonly height: number; + readonly tx: CosmosSdkTx; + } + | undefined; + let sendUnsuccessful: + | { + readonly sender: string; + readonly recipient: string; + readonly hash: string; + readonly height: number; + readonly tx: CosmosSdkTx; + } + | undefined; + let postedExecute: + | { + readonly sender: string; + readonly contract: string; + readonly hash: string; + readonly height: number; + readonly tx: CosmosSdkTx; + } + | undefined; + + beforeAll(async () => { + if (wasmdEnabled()) { + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmWasmClient(wasmd.endpoint, faucet.address, (signBytes) => + pen.sign(signBytes), + ); + + { + const recipient = makeRandomAddress(); + const transferAmount: Coin = { + denom: "ucosm", + amount: "1234567", + }; + const result = await client.sendTokens(recipient, [transferAmount]); + await sleep(50); // wait until tx is indexed + const txDetails = await new RestClient(wasmd.endpoint).txById(result.transactionHash); + sendSuccessful = { + sender: faucet.address, + recipient: recipient, + hash: result.transactionHash, + height: Number.parseInt(txDetails.height, 10), + tx: txDetails.tx, + }; + } + + { + const memo = "Sending more than I can afford"; + const recipient = makeRandomAddress(); + const transferAmount = [ + { + denom: "ucosm", + amount: "123456700000000", + }, + ]; + const sendMsg: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + // eslint-disable-next-line @typescript-eslint/camelcase + from_address: faucet.address, + // eslint-disable-next-line @typescript-eslint/camelcase + to_address: recipient, + amount: transferAmount, + }, + }; + const fee = { + amount: [ + { + denom: "ucosm", + amount: "2000", + }, + ], + gas: "80000", // 80k + }; + const { accountNumber, sequence } = await client.getNonce(); + const chainId = await client.getChainId(); + const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); + const signature = await pen.sign(signBytes); + const tx: CosmosSdkTx = { + type: "cosmos-sdk/StdTx", + value: { + msg: [sendMsg], + fee: fee, + memo: memo, + signatures: [signature], + }, + }; + const transactionId = await client.getIdentifier(tx); + const heightBeforeThis = await client.getHeight(); + try { + await client.postTx(tx.value); + } catch (error) { + // postTx() throws on execution failures, which is a questionable design. Ignore for now. + // console.log(error); + } + sendUnsuccessful = { + sender: faucet.address, + recipient: recipient, + hash: transactionId, + height: heightBeforeThis + 1, + tx: tx, + }; + } + + { + const hashInstance = deployedErc20.instances[0]; + const msg = { + approve: { + spender: makeRandomAddress(), + amount: "12", + }, + }; + const result = await client.execute(hashInstance, msg); + await sleep(50); // wait until tx is indexed + const txDetails = await new RestClient(wasmd.endpoint).txById(result.transactionHash); + postedExecute = { + sender: faucet.address, + contract: hashInstance, + hash: result.transactionHash, + height: Number.parseInt(txDetails.height, 10), + tx: txDetails.tx, + }; + } + } + }); + + describe("with SearchByIdQuery", () => { + it("can search successful tx by ID", async () => { + pendingWithoutWasmd(); + assert(sendSuccessful, "value must be set in beforeAll()"); + const client = new CosmWasmClient(wasmd.endpoint); + const result = await client.searchTx({ id: sendSuccessful.hash }); + expect(result.length).toEqual(1); + expect(result[0]).toEqual( + jasmine.objectContaining({ + height: sendSuccessful.height, + hash: sendSuccessful.hash, + code: 0, + tx: sendSuccessful.tx, + }), + ); + }); + + it("can search unsuccessful tx by ID", async () => { + pendingWithoutWasmd(); + assert(sendUnsuccessful, "value must be set in beforeAll()"); + const client = new CosmWasmClient(wasmd.endpoint); + const result = await client.searchTx({ id: sendUnsuccessful.hash }); + expect(result.length).toEqual(1); + expect(result[0]).toEqual( + jasmine.objectContaining({ + height: sendUnsuccessful.height, + hash: sendUnsuccessful.hash, + code: 5, + tx: sendUnsuccessful.tx, + }), + ); + }); + + it("can search by ID (non existent)", async () => { + pendingWithoutWasmd(); + const client = new CosmWasmClient(wasmd.endpoint); + const nonExistentId = "0000000000000000000000000000000000000000000000000000000000000000"; + const result = await client.searchTx({ id: nonExistentId }); + expect(result.length).toEqual(0); + }); + + it("can search by ID and filter by minHeight", async () => { + pendingWithoutWasmd(); + assert(sendSuccessful); + const client = new CosmWasmClient(wasmd.endpoint); + const query = { id: sendSuccessful.hash }; + + { + 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); + } + }); + }); + + describe("with SearchByHeightQuery", () => { + it("can search successful tx by height", async () => { + pendingWithoutWasmd(); + assert(sendSuccessful, "value must be set in beforeAll()"); + const client = new CosmWasmClient(wasmd.endpoint); + const result = await client.searchTx({ height: sendSuccessful.height }); + expect(result.length).toEqual(1); + expect(result[0]).toEqual( + jasmine.objectContaining({ + height: sendSuccessful.height, + hash: sendSuccessful.hash, + code: 0, + tx: sendSuccessful.tx, + }), + ); + }); + + it("can search unsuccessful tx by height", async () => { + pendingWithoutWasmd(); + assert(sendUnsuccessful, "value must be set in beforeAll()"); + const client = new CosmWasmClient(wasmd.endpoint); + const result = await client.searchTx({ height: sendUnsuccessful.height }); + expect(result.length).toEqual(1); + expect(result[0]).toEqual( + jasmine.objectContaining({ + height: sendUnsuccessful.height, + hash: sendUnsuccessful.hash, + code: 5, + tx: sendUnsuccessful.tx, + }), + ); + }); + }); + + describe("with SearchBySentFromOrToQuery", () => { + it("can search by sender", async () => { + pendingWithoutWasmd(); + assert(sendSuccessful, "value must be set in beforeAll()"); + const client = new CosmWasmClient(wasmd.endpoint); + 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 containsMsgWithSender = !!result.tx.value.msg.find( + (msg) => isMsgSend(msg) && msg.value.from_address == sendSuccessful!.sender, + ); + const containsMsgWithRecipient = !!result.tx.value.msg.find( + (msg) => isMsgSend(msg) && msg.value.to_address === sendSuccessful!.sender, + ); + expect(containsMsgWithSender || containsMsgWithRecipient).toEqual(true); + } + + // 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 () => { + pendingWithoutWasmd(); + assert(sendSuccessful, "value must be set in beforeAll()"); + const client = new CosmWasmClient(wasmd.endpoint); + 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 msg = fromOneElementArray(result.tx.value.msg); + assert(isMsgSend(msg), `${result.hash} (height ${result.height}) is not a bank send transaction`); + expect( + msg.value.to_address === sendSuccessful.recipient || + msg.value.from_address == sendSuccessful.recipient, + ).toEqual(true); + } + + // 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 () => { + pendingWithoutWasmd(); + assert(sendSuccessful); + const client = new CosmWasmClient(wasmd.endpoint); + 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 () => { + pendingWithoutWasmd(); + assert(sendSuccessful); + const client = new CosmWasmClient(wasmd.endpoint); + 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 () => { + pendingWithoutWasmd(); + assert(sendSuccessful, "value must be set in beforeAll()"); + const client = new CosmWasmClient(wasmd.endpoint); + 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 msg = fromOneElementArray(result.tx.value.msg); + assert(isMsgSend(msg), `${result.hash} (height ${result.height}) is not a bank send transaction`); + expect(msg.value.to_address).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, + }), + ); + }); + + it("can search by message.contract_address", async () => { + pendingWithoutWasmd(); + assert(postedExecute, "value must be set in beforeAll()"); + const client = new CosmWasmClient(wasmd.endpoint); + const results = await client.searchTx({ + tags: [{ key: "message.contract_address", value: postedExecute.contract }], + }); + expect(results.length).toBeGreaterThanOrEqual(1); + + // Check basic structure of all results + for (const result of results) { + const msg = fromOneElementArray(result.tx.value.msg); + assert( + isMsgExecuteContract(msg) || isMsgInstantiateContract(msg), + `${result.hash} (at ${result.height}) not an execute or instantiate msg`, + ); + } + + // Check that the first result is the instantiation + const first = fromOneElementArray(results[0].tx.value.msg); + assert(isMsgInstantiateContract(first), "First contract search result must be an instantiation"); + expect(first).toEqual({ + type: "wasm/instantiate", + value: { + sender: faucet.address, + code_id: deployedErc20.codeId.toString(), + label: "HASH", + init_msg: jasmine.objectContaining({ symbol: "HASH" }), + init_funds: [], + }, + }); + + // Check details of most recent result + expect(results[results.length - 1]).toEqual( + jasmine.objectContaining({ + height: postedExecute.height, + hash: postedExecute.hash, + tx: postedExecute.tx, + }), + ); + }); + + it("can search by message.contract_address + message.action", async () => { + pendingWithoutWasmd(); + assert(postedExecute, "value must be set in beforeAll()"); + const client = new CosmWasmClient(wasmd.endpoint); + const results = await client.searchTx({ + tags: [ + { key: "message.contract_address", value: postedExecute.contract }, + { key: "message.action", value: "execute" }, + ], + }); + expect(results.length).toBeGreaterThanOrEqual(1); + + // Check basic structure of all results + for (const result of results) { + const msg = fromOneElementArray(result.tx.value.msg); + assert(isMsgExecuteContract(msg), `${result.hash} (at ${result.height}) not an execute msg`); + expect(msg.value.contract).toEqual(postedExecute.contract); + } + + // Check details of most recent result + expect(results[results.length - 1]).toEqual( + jasmine.objectContaining({ + height: postedExecute.height, + hash: postedExecute.hash, + tx: postedExecute.tx, + }), + ); + }); + }); +}); diff --git a/packages/cosmwasm/src/cosmwasmclient.spec.ts b/packages/cosmwasm/src/cosmwasmclient.spec.ts new file mode 100644 index 00000000..7c4904ed --- /dev/null +++ b/packages/cosmwasm/src/cosmwasmclient.spec.ts @@ -0,0 +1,466 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { Secp256k1Pen, makeSignBytes } from "@cosmwasm/sdk38"; +import { Sha256 } from "@iov/crypto"; +import { Bech32, Encoding } from "@iov/encoding"; +import { assert, sleep } from "@iov/utils"; +import { ReadonlyDate } from "readonly-date"; + +import { Code, CosmWasmClient, PrivateCosmWasmClient } from "./cosmwasmclient"; +import { findAttribute } from "./logs"; +import { SigningCosmWasmClient } from "./signingcosmwasmclient"; +import cosmoshub from "./testdata/cosmoshub.json"; +import { + deployedErc20, + faucet, + getHackatom, + makeRandomAddress, + pendingWithoutWasmd, + tendermintIdMatcher, + unused, + wasmd, + wasmdEnabled, +} from "./testutils.spec"; +import { MsgSend, StdFee } from "./types"; + +const { fromHex, fromUtf8, toAscii, toBase64 } = Encoding; + +const guest = { + address: "cosmos17d0jcz59jf68g52vq38tuuncmwwjk42u6mcxej", +}; + +interface HackatomInstance { + readonly initMsg: { + readonly verifier: string; + readonly beneficiary: string; + }; + readonly address: string; +} + +describe("CosmWasmClient", () => { + describe("makeReadOnly", () => { + it("can be constructed", () => { + const client = new CosmWasmClient(wasmd.endpoint); + expect(client).toBeTruthy(); + }); + }); + + describe("getChainId", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = new CosmWasmClient(wasmd.endpoint); + expect(await client.getChainId()).toEqual(wasmd.chainId); + }); + + it("caches chain ID", async () => { + pendingWithoutWasmd(); + const client = new CosmWasmClient(wasmd.endpoint); + const openedClient = (client as unknown) as PrivateCosmWasmClient; + const getCodeSpy = spyOn(openedClient.restClient, "nodeInfo").and.callThrough(); + + expect(await client.getChainId()).toEqual(wasmd.chainId); // from network + expect(await client.getChainId()).toEqual(wasmd.chainId); // from cache + + expect(getCodeSpy).toHaveBeenCalledTimes(1); + }); + }); + + describe("getHeight", () => { + it("gets height via last block", async () => { + pendingWithoutWasmd(); + const client = new CosmWasmClient(wasmd.endpoint); + const openedClient = (client as unknown) as PrivateCosmWasmClient; + const blockLatestSpy = spyOn(openedClient.restClient, "blocksLatest").and.callThrough(); + + const height1 = await client.getHeight(); + expect(height1).toBeGreaterThan(0); + await sleep(1_000); + const height2 = await client.getHeight(); + expect(height2).toEqual(height1 + 1); + + expect(blockLatestSpy).toHaveBeenCalledTimes(2); + }); + + it("gets height via authAccount once an address is known", async () => { + pendingWithoutWasmd(); + const client = new CosmWasmClient(wasmd.endpoint); + + const openedClient = (client as unknown) as PrivateCosmWasmClient; + const blockLatestSpy = spyOn(openedClient.restClient, "blocksLatest").and.callThrough(); + const authAccountsSpy = spyOn(openedClient.restClient, "authAccounts").and.callThrough(); + + const height1 = await client.getHeight(); + expect(height1).toBeGreaterThan(0); + + await client.getCodes(); // warm up the client + + const height2 = await client.getHeight(); + expect(height2).toBeGreaterThan(0); + await sleep(1_000); + const height3 = await client.getHeight(); + expect(height3).toEqual(height2 + 1); + + expect(blockLatestSpy).toHaveBeenCalledTimes(1); + expect(authAccountsSpy).toHaveBeenCalledTimes(2); + }); + }); + + describe("getNonce", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = new CosmWasmClient(wasmd.endpoint); + expect(await client.getNonce(unused.address)).toEqual({ + accountNumber: unused.accountNumber, + sequence: unused.sequence, + }); + }); + + it("throws for missing accounts", async () => { + pendingWithoutWasmd(); + const client = new CosmWasmClient(wasmd.endpoint); + const missing = makeRandomAddress(); + await client.getNonce(missing).then( + () => fail("this must not succeed"), + (error) => expect(error).toMatch(/account does not exist on chain/i), + ); + }); + }); + + describe("getAccount", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = new CosmWasmClient(wasmd.endpoint); + expect(await client.getAccount(unused.address)).toEqual({ + address: unused.address, + accountNumber: unused.accountNumber, + sequence: unused.sequence, + pubkey: undefined, + balance: [ + { denom: "ucosm", amount: "1000000000" }, + { denom: "ustake", amount: "1000000000" }, + ], + }); + }); + + it("returns undefined for missing accounts", async () => { + pendingWithoutWasmd(); + const client = new CosmWasmClient(wasmd.endpoint); + const missing = makeRandomAddress(); + expect(await client.getAccount(missing)).toBeUndefined(); + }); + }); + + describe("getBlock", () => { + it("works for latest block", async () => { + pendingWithoutWasmd(); + const client = new CosmWasmClient(wasmd.endpoint); + 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 () => { + pendingWithoutWasmd(); + const client = new CosmWasmClient(wasmd.endpoint); + 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("getIdentifier", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = new CosmWasmClient(wasmd.endpoint); + expect(await client.getIdentifier(cosmoshub.tx)).toEqual(cosmoshub.id); + }); + }); + + describe("postTx", () => { + it("works", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new CosmWasmClient(wasmd.endpoint); + + const memo = "My first contract on chain"; + const sendMsg: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: faucet.address, + to_address: makeRandomAddress(), + amount: [ + { + denom: "ucosm", + amount: "1234567", + }, + ], + }, + }; + + const fee: StdFee = { + amount: [ + { + amount: "5000", + denom: "ucosm", + }, + ], + gas: "890000", + }; + + const chainId = await client.getChainId(); + const { accountNumber, sequence } = await client.getNonce(faucet.address); + const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); + const signature = await pen.sign(signBytes); + const signedTx = { + msg: [sendMsg], + fee: fee, + memo: memo, + signatures: [signature], + }; + const { logs, transactionHash } = await client.postTx(signedTx); + const amountAttr = findAttribute(logs, "transfer", "amount"); + expect(amountAttr.value).toEqual("1234567ucosm"); + expect(transactionHash).toMatch(/^[0-9A-F]{64}$/); + }); + }); + + describe("getCodes", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = new CosmWasmClient(wasmd.endpoint); + const result = await client.getCodes(); + expect(result.length).toBeGreaterThanOrEqual(1); + const [first] = result; + expect(first).toEqual({ + id: deployedErc20.codeId, + source: deployedErc20.source, + builder: deployedErc20.builder, + checksum: deployedErc20.checksum, + creator: faucet.address, + }); + }); + }); + + describe("getCodeDetails", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = new CosmWasmClient(wasmd.endpoint); + const result = await client.getCodeDetails(1); + + const expectedInfo: Code = { + id: deployedErc20.codeId, + source: deployedErc20.source, + builder: deployedErc20.builder, + checksum: deployedErc20.checksum, + creator: faucet.address, + }; + + // check info + expect(result).toEqual(jasmine.objectContaining(expectedInfo)); + // check data + expect(new Sha256(result.data).digest()).toEqual(fromHex(expectedInfo.checksum)); + }); + + it("caches downloads", async () => { + pendingWithoutWasmd(); + const client = new CosmWasmClient(wasmd.endpoint); + const openedClient = (client as unknown) as PrivateCosmWasmClient; + const getCodeSpy = spyOn(openedClient.restClient, "getCode").and.callThrough(); + + const result1 = await client.getCodeDetails(deployedErc20.codeId); // from network + const result2 = await client.getCodeDetails(deployedErc20.codeId); // from cache + expect(result2).toEqual(result1); + + expect(getCodeSpy).toHaveBeenCalledTimes(1); + }); + }); + + describe("getContracts", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = new CosmWasmClient(wasmd.endpoint); + const result = await client.getContracts(1); + expect(result.length).toBeGreaterThanOrEqual(3); + const [hash, isa, jade] = result; + expect(hash).toEqual({ + address: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", + codeId: 1, + creator: faucet.address, + label: "HASH", + }); + expect(isa).toEqual({ + address: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd", + codeId: 1, + creator: faucet.address, + label: "ISA", + }); + expect(jade).toEqual({ + address: "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c", + codeId: 1, + creator: faucet.address, + label: "JADE", + }); + }); + }); + + describe("getContract", () => { + it("works for HASH instance", async () => { + pendingWithoutWasmd(); + const client = new CosmWasmClient(wasmd.endpoint); + const hash = await client.getContract("cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5"); + expect(hash).toEqual({ + address: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", + codeId: 1, + creator: faucet.address, + label: "HASH", + initMsg: { + decimals: 5, + name: "Hash token", + symbol: "HASH", + initial_balances: jasmine.arrayContaining([ + { + address: faucet.address, + amount: "11", + }, + { + address: unused.address, + amount: "12812345", + }, + { + address: guest.address, + amount: "22004000000", + }, + ]), + }, + }); + }); + }); + + describe("queryContractRaw", () => { + const configKey = toAscii("config"); + const otherKey = toAscii("this_does_not_exist"); + let contract: HackatomInstance | undefined; + + beforeAll(async () => { + if (wasmdEnabled()) { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmWasmClient(wasmd.endpoint, faucet.address, (signBytes) => + pen.sign(signBytes), + ); + const { codeId } = await client.upload(getHackatom()); + const initMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() }; + const { contractAddress } = await client.instantiate(codeId, initMsg, "random hackatom"); + contract = { initMsg: initMsg, address: contractAddress }; + } + }); + + it("can query existing key", async () => { + pendingWithoutWasmd(); + assert(contract); + + const client = new CosmWasmClient(wasmd.endpoint); + const raw = await client.queryContractRaw(contract.address, configKey); + assert(raw, "must get result"); + expect(JSON.parse(fromUtf8(raw))).toEqual({ + verifier: toBase64(Bech32.decode(contract.initMsg.verifier).data), + beneficiary: toBase64(Bech32.decode(contract.initMsg.beneficiary).data), + funder: toBase64(Bech32.decode(faucet.address).data), + }); + }); + + it("can query non-existent key", async () => { + pendingWithoutWasmd(); + assert(contract); + + const client = new CosmWasmClient(wasmd.endpoint); + const raw = await client.queryContractRaw(contract.address, otherKey); + expect(raw).toBeNull(); + }); + + it("errors for non-existent contract", async () => { + pendingWithoutWasmd(); + assert(contract); + + const nonExistentAddress = makeRandomAddress(); + const client = new CosmWasmClient(wasmd.endpoint); + await client.queryContractRaw(nonExistentAddress, configKey).then( + () => fail("must not succeed"), + (error) => expect(error).toMatch(`No contract found at address "${nonExistentAddress}"`), + ); + }); + }); + + describe("queryContractSmart", () => { + let contract: HackatomInstance | undefined; + + beforeAll(async () => { + if (wasmdEnabled()) { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmWasmClient(wasmd.endpoint, faucet.address, (signBytes) => + pen.sign(signBytes), + ); + const { codeId } = await client.upload(getHackatom()); + const initMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() }; + const { contractAddress } = await client.instantiate(codeId, initMsg, "a different hackatom"); + contract = { initMsg: initMsg, address: contractAddress }; + } + }); + + it("works", async () => { + pendingWithoutWasmd(); + assert(contract); + + const client = new CosmWasmClient(wasmd.endpoint); + const resultDocument = await client.queryContractSmart(contract.address, { verifier: {} }); + expect(resultDocument).toEqual({ verifier: contract.initMsg.verifier }); + }); + + it("errors for malformed query message", async () => { + pendingWithoutWasmd(); + assert(contract); + + const client = new CosmWasmClient(wasmd.endpoint); + await client.queryContractSmart(contract.address, { broken: {} }).then( + () => fail("must not succeed"), + (error) => expect(error).toMatch(/query wasm contract failed: parsing hackatom::contract::QueryMsg/i), + ); + }); + + it("errors for non-existent contract", async () => { + pendingWithoutWasmd(); + + const nonExistentAddress = makeRandomAddress(); + const client = new CosmWasmClient(wasmd.endpoint); + await client.queryContractSmart(nonExistentAddress, { verifier: {} }).then( + () => fail("must not succeed"), + (error) => expect(error).toMatch(`No contract found at address "${nonExistentAddress}"`), + ); + }); + }); +}); diff --git a/packages/cosmwasm/src/cosmwasmclient.ts b/packages/cosmwasm/src/cosmwasmclient.ts new file mode 100644 index 00000000..04c30d3c --- /dev/null +++ b/packages/cosmwasm/src/cosmwasmclient.ts @@ -0,0 +1,439 @@ +import { Coin } from "@cosmwasm/sdk38"; +import { Sha256 } from "@iov/crypto"; +import { Encoding } from "@iov/encoding"; + +import { Log, parseLogs } from "./logs"; +import { decodeBech32Pubkey } from "./pubkey"; +import { BroadcastMode, RestClient } from "./restclient"; +import { CosmosSdkTx, JsonObject, PubKey, StdTx } from "./types"; + +export interface GetNonceResult { + readonly accountNumber: number; + readonly sequence: number; +} + +export interface Account { + /** Bech32 account address */ + readonly address: string; + readonly balance: ReadonlyArray; + readonly pubkey: PubKey | undefined; + readonly accountNumber: number; + readonly sequence: number; +} + +export interface PostTxResult { + readonly logs: readonly Log[]; + readonly rawLog: string; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; +} + +export interface SearchByIdQuery { + readonly id: string; +} + +export interface SearchByHeightQuery { + readonly height: number; +} + +export interface SearchBySentFromOrToQuery { + readonly sentFromOrTo: string; +} + +/** + * This query type allows you to pass arbitrary key/value pairs to the backend. It is + * more powerful and slightly lower level than the other search options. + */ +export interface SearchByTagsQuery { + readonly tags: readonly { readonly key: string; readonly value: string }[]; +} + +export type SearchTxQuery = + | SearchByIdQuery + | SearchByHeightQuery + | SearchBySentFromOrToQuery + | SearchByTagsQuery; + +function isSearchByIdQuery(query: SearchTxQuery): query is SearchByIdQuery { + return (query as SearchByIdQuery).id !== undefined; +} + +function isSearchByHeightQuery(query: SearchTxQuery): query is SearchByHeightQuery { + return (query as SearchByHeightQuery).height !== undefined; +} + +function isSearchBySentFromOrToQuery(query: SearchTxQuery): query is SearchBySentFromOrToQuery { + return (query as SearchBySentFromOrToQuery).sentFromOrTo !== undefined; +} + +function isSearchByTagsQuery(query: SearchTxQuery): query is SearchByTagsQuery { + return (query as SearchByTagsQuery).tags !== undefined; +} + +export interface SearchTxFilter { + readonly minHeight?: number; + readonly maxHeight?: number; +} + +export interface Code { + readonly id: number; + /** Bech32 account address */ + readonly creator: string; + /** Hex-encoded sha256 hash of the code stored here */ + readonly checksum: string; + readonly source?: string; + readonly builder?: string; +} + +export interface CodeDetails extends Code { + /** The original wasm bytes */ + readonly data: Uint8Array; +} + +export interface Contract { + readonly address: string; + readonly codeId: number; + /** Bech32 account address */ + readonly creator: string; + readonly label: string; +} + +export interface ContractDetails extends Contract { + /** Argument passed on initialization of the contract */ + readonly initMsg: object; +} + +/** A transaction that is indexed as part of the transaction history */ +export interface IndexedTx { + readonly height: number; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly hash: string; + /** Transaction execution error code. 0 on success. */ + readonly code: number; + readonly rawLog: string; + readonly logs: readonly Log[]; + readonly tx: CosmosSdkTx; + /** The gas limit as set by the user */ + readonly gasWanted?: number; + /** The gas used by the execution */ + readonly gasUsed?: number; + /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ + readonly timestamp: string; +} + +export interface BlockHeader { + readonly version: { + readonly block: string; + readonly app: string; + }; + readonly height: number; + readonly chainId: string; + /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ + readonly time: string; +} + +export interface Block { + /** The ID is a hash of the block header (uppercase hex) */ + readonly id: string; + readonly header: BlockHeader; + /** Array of raw transactions */ + readonly txs: ReadonlyArray; +} + +/** Use for testing only */ +export interface PrivateCosmWasmClient { + readonly restClient: RestClient; +} + +export class CosmWasmClient { + protected readonly restClient: RestClient; + /** Any address the chain considers valid (valid bech32 with proper prefix) */ + protected anyValidAddress: string | undefined; + + private readonly codesCache = new Map(); + private chainId: string | undefined; + + /** + * Creates a new client to interact with a CosmWasm blockchain. + * + * This instance does a lot of caching. In order to benefit from that you should try to use one instance + * for the lifetime of your application. When switching backends, a new instance must be created. + * + * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) + * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns + */ + public constructor(apiUrl: string, broadcastMode = BroadcastMode.Block) { + this.restClient = new RestClient(apiUrl, broadcastMode); + } + + public async getChainId(): Promise { + if (!this.chainId) { + const response = await this.restClient.nodeInfo(); + const chainId = response.node_info.network; + if (!chainId) throw new Error("Chain ID must not be empty"); + this.chainId = chainId; + } + + return this.chainId; + } + + public async getHeight(): Promise { + if (this.anyValidAddress) { + const { height } = await this.restClient.authAccounts(this.anyValidAddress); + return parseInt(height, 10); + } else { + // Note: this gets inefficient when blocks contain a lot of transactions since it + // requires downloading and deserializing all transactions in the block. + const latest = await this.restClient.blocksLatest(); + return parseInt(latest.block.header.height, 10); + } + } + + /** + * Returns a 32 byte upper-case hex transaction hash (typically used as the transaction ID) + */ + public async getIdentifier(tx: CosmosSdkTx): Promise { + // We consult the REST API because we don't have a local amino encoder + const bytes = await this.restClient.encodeTx(tx); + const hash = new Sha256(bytes).digest(); + return Encoding.toHex(hash).toUpperCase(); + } + + /** + * Returns account number and sequence. + * + * Throws if the account does not exist on chain. + * + * @param address returns data for this address. When unset, the client's sender adddress is used. + */ + public async getNonce(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 nonces.", + ); + } + return { + accountNumber: account.accountNumber, + sequence: account.sequence, + }; + } + + public async getAccount(address: string): Promise { + const account = await this.restClient.authAccounts(address); + const value = account.result.value; + if (value.address === "") { + return undefined; + } else { + this.anyValidAddress = value.address; + return { + address: value.address, + balance: value.coins, + pubkey: value.public_key ? decodeBech32Pubkey(value.public_key) : undefined, + accountNumber: value.account_number, + sequence: value.sequence, + }; + } + } + + /** + * Gets block header and meta + * + * @param height The height of the block. If undefined, the latest height is used. + */ + public async getBlock(height?: number): Promise { + const response = + height !== undefined ? await this.restClient.blocks(height) : await this.restClient.blocksLatest(); + + return { + id: response.block_id.hash, + header: { + version: response.block.header.version, + time: response.block.header.time, + height: parseInt(response.block.header.height, 10), + chainId: response.block.header.chain_id, + }, + txs: (response.block.data.txs || []).map((encoded) => Encoding.fromBase64(encoded)), + }; + } + + 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}&tx.minheight=${minHeight}&tx.maxheight=${maxHeight}`; + } + + let txs: readonly IndexedTx[]; + if (isSearchByIdQuery(query)) { + txs = await this.txsQuery(`tx.hash=${query.id}`); + } else if (isSearchByHeightQuery(query)) { + // optional optimization to avoid network request + if (query.height < minHeight || query.height > maxHeight) { + txs = []; + } else { + txs = await this.txsQuery(`tx.height=${query.height}`); + } + } else if (isSearchBySentFromOrToQuery(query)) { + // We cannot get both in one request (see https://github.com/cosmos/gaia/issues/75) + const sentQuery = withFilters(`message.module=bank&message.sender=${query.sentFromOrTo}`); + const receivedQuery = withFilters(`message.module=bank&transfer.recipient=${query.sentFromOrTo}`); + const sent = await this.txsQuery(sentQuery); + const received = await this.txsQuery(receivedQuery); + + 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("&")); + txs = await this.txsQuery(rawQuery); + } else { + throw new Error("Unknown query type"); + } + + // backend sometimes messes up with min/max height filtering + const filtered = txs.filter((tx) => tx.height >= minHeight && tx.height <= maxHeight); + + return filtered; + } + + public async postTx(tx: StdTx): Promise { + const result = await this.restClient.postTx(tx); + if (!result.txhash.match(/^([0-9A-F][0-9A-F])+$/)) { + throw new Error("Received ill-formatted txhash. Must be non-empty upper-case hex"); + } + + if (result.code) { + throw new Error( + `Error when posting tx ${result.txhash}. Code: ${result.code}; Raw log: ${result.raw_log}`, + ); + } + + return { + logs: result.logs ? parseLogs(result.logs) : [], + rawLog: result.raw_log || "", + transactionHash: result.txhash, + }; + } + + public async getCodes(): Promise { + const result = await this.restClient.listCodeInfo(); + return result.map( + (entry): Code => { + this.anyValidAddress = entry.creator; + return { + id: entry.id, + creator: entry.creator, + checksum: Encoding.toHex(Encoding.fromHex(entry.data_hash)), + source: entry.source || undefined, + builder: entry.builder || undefined, + }; + }, + ); + } + + public async getCodeDetails(codeId: number): Promise { + const cached = this.codesCache.get(codeId); + if (cached) return cached; + + const getCodeResult = await this.restClient.getCode(codeId); + const codeDetails: CodeDetails = { + id: getCodeResult.id, + creator: getCodeResult.creator, + checksum: Encoding.toHex(Encoding.fromHex(getCodeResult.data_hash)), + source: getCodeResult.source || undefined, + builder: getCodeResult.builder || undefined, + data: Encoding.fromBase64(getCodeResult.data), + }; + this.codesCache.set(codeId, codeDetails); + return codeDetails; + } + + public async getContracts(codeId: number): Promise { + const result = await this.restClient.listContractsByCodeId(codeId); + return result.map( + (entry): Contract => ({ + address: entry.address, + codeId: entry.code_id, + creator: entry.creator, + label: entry.label, + }), + ); + } + + /** + * Throws an error if no contract was found at the address + */ + public async getContract(address: string): Promise { + const result = await this.restClient.getContractInfo(address); + if (!result) throw new Error(`No contract found at address "${address}"`); + return { + address: result.address, + codeId: result.code_id, + creator: result.creator, + label: result.label, + initMsg: result.init_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 + const _info = await this.getContract(address); + + return this.restClient.queryContractRaw(address, key); + } + + /** + * 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: object): Promise { + try { + return await this.restClient.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 { + // TODO: we need proper pagination support + const limit = 100; + const result = await this.restClient.txsQuery(`${query}&limit=${limit}`); + const pages = parseInt(result.page_total, 10); + if (pages > 1) { + throw new Error( + `Found more results on the backend than we can process currently. Results: ${result.total_count}, supported: ${limit}`, + ); + } + return result.txs.map( + (restItem): IndexedTx => ({ + height: parseInt(restItem.height, 10), + hash: restItem.txhash, + code: restItem.code || 0, + rawLog: restItem.raw_log, + logs: parseLogs(restItem.logs || []), + tx: restItem.tx, + timestamp: restItem.timestamp, + }), + ); + } +} diff --git a/packages/cosmwasm/src/index.ts b/packages/cosmwasm/src/index.ts new file mode 100644 index 00000000..1a189237 --- /dev/null +++ b/packages/cosmwasm/src/index.ts @@ -0,0 +1,34 @@ +import * as logs from "./logs"; +import * as types from "./types"; +export { logs, types }; + +export { BroadcastMode, RestClient, TxsResponse } from "./restclient"; +export { + Account, + Block, + BlockHeader, + Code, + CodeDetails, + Contract, + ContractDetails, + CosmWasmClient, + GetNonceResult, + IndexedTx, + PostTxResult, + SearchByHeightQuery, + SearchByIdQuery, + SearchBySentFromOrToQuery, + SearchByTagsQuery, + SearchTxQuery, + SearchTxFilter, +} from "./cosmwasmclient"; +export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; +export { + ExecuteResult, + FeeTable, + InstantiateResult, + SigningCallback, + SigningCosmWasmClient, + UploadMeta, + UploadResult, +} from "./signingcosmwasmclient"; diff --git a/packages/cosmwasm/src/logs.spec.ts b/packages/cosmwasm/src/logs.spec.ts new file mode 100644 index 00000000..584d9ef2 --- /dev/null +++ b/packages/cosmwasm/src/logs.spec.ts @@ -0,0 +1,165 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { parseAttribute, parseEvent, parseLog, parseLogs } from "./logs"; + +describe("logs", () => { + describe("parseAttribute", () => { + it("works", () => { + const attr = parseAttribute({ key: "a", value: "b" }); + expect(attr).toEqual({ key: "a", value: "b" }); + }); + + it("works for empty value", () => { + const attr = parseAttribute({ key: "foobar", value: "" }); + expect(attr).toEqual({ key: "foobar", value: "" }); + }); + + it("normalized unset value to empty string", () => { + const attr = parseAttribute({ key: "amount" }); + expect(attr).toEqual({ key: "amount", value: "" }); + }); + }); + + describe("parseEvent", () => { + it("works", () => { + const original = { + type: "message", + attributes: [ + { + key: "action", + value: "store-code", + }, + { + key: "module", + value: "wasm", + }, + { + key: "action", + value: "store-code", + }, + { + key: "sender", + value: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + }, + { + key: "code_id", + value: "1", + }, + ], + } as const; + + const event = parseEvent(original); + expect(event).toEqual(original); + }); + + it("works for transfer event", () => { + const original = { + type: "transfer", + attributes: [ + { + key: "recipient", + value: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", + }, + { + key: "amount", + }, + ], + } as const; + const expected = { + type: "transfer", + attributes: [ + { + key: "recipient", + value: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", + }, + { + key: "amount", + value: "", + }, + ], + } as const; + + const event = parseEvent(original); + expect(event).toEqual(expected); + }); + }); + + describe("parseLog", () => { + it("works", () => { + const original = { + msg_index: 0, + log: "", + events: [ + { + type: "message", + attributes: [ + { + key: "action", + value: "store-code", + }, + { + key: "module", + value: "wasm", + }, + { + key: "action", + value: "store-code", + }, + { + key: "sender", + value: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + }, + { + key: "code_id", + value: "1", + }, + ], + }, + ], + } as const; + + const log = parseLog(original); + expect(log).toEqual(original); + }); + }); + + describe("parseLogs", () => { + it("works", () => { + const original = [ + { + msg_index: 0, + log: "", + events: [ + { + type: "message", + attributes: [ + { + key: "action", + value: "store-code", + }, + { + key: "module", + value: "wasm", + }, + { + key: "action", + value: "store-code", + }, + { + key: "sender", + value: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + }, + { + key: "code_id", + value: "1", + }, + ], + }, + ], + }, + ] as const; + + const logs = parseLogs(original); + expect(logs).toEqual(original); + }); + }); +}); diff --git a/packages/cosmwasm/src/logs.ts b/packages/cosmwasm/src/logs.ts new file mode 100644 index 00000000..e1eaa1cb --- /dev/null +++ b/packages/cosmwasm/src/logs.ts @@ -0,0 +1,86 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { isNonNullObject } from "@iov/encoding"; + +export interface Attribute { + readonly key: string; + readonly value: string; +} + +export interface Event { + readonly type: string; + readonly attributes: readonly Attribute[]; +} + +export interface Log { + readonly msg_index: number; + readonly log: string; + readonly events: readonly Event[]; +} + +export function parseAttribute(input: unknown): Attribute { + if (!isNonNullObject(input)) throw new Error("Attribute must be a non-null object"); + const { key, value } = input as any; + if (typeof key !== "string" || !key) throw new Error("Attribute's key must be a non-empty string"); + if (typeof value !== "string" && typeof value !== "undefined") { + throw new Error("Attribute's value must be a string or unset"); + } + + return { + key: key, + value: value || "", + }; +} + +export function parseEvent(input: unknown): Event { + if (!isNonNullObject(input)) throw new Error("Event must be a non-null object"); + const { type, attributes } = input as any; + if (typeof type !== "string" || type === "") { + throw new Error(`Event type must be a non-empty string`); + } + if (!Array.isArray(attributes)) throw new Error("Event's attributes must be an array"); + return { + type: type, + attributes: attributes.map(parseAttribute), + }; +} + +export function parseLog(input: unknown): Log { + if (!isNonNullObject(input)) throw new Error("Log must be a non-null object"); + const { msg_index, log, events } = input as any; + if (typeof msg_index !== "number") throw new Error("Log's msg_index must be a number"); + if (typeof log !== "string") throw new Error("Log's log must be a string"); + if (!Array.isArray(events)) throw new Error("Log's events must be an array"); + return { + msg_index: msg_index, + log: log, + events: events.map(parseEvent), + }; +} + +export function parseLogs(input: unknown): readonly Log[] { + if (!Array.isArray(input)) throw new Error("Logs must be an array"); + return input.map(parseLog); +} + +/** + * Searches in logs for the first event of the given event type and in that event + * for the first first attribute with the given attribute key. + * + * Throws if the attribute was not found. + */ +export function findAttribute( + logs: readonly Log[], + eventType: "message" | "transfer", + attrKey: string, +): Attribute { + const firstLogs = logs.find(() => true); + const out = firstLogs?.events + .find((event) => event.type === eventType) + ?.attributes.find((attr) => attr.key === attrKey); + if (!out) { + throw new Error( + `Could not find attribute '${attrKey}' in first event of type '${eventType}' in first log.`, + ); + } + return out; +} diff --git a/packages/cosmwasm/src/pubkey.spec.ts b/packages/cosmwasm/src/pubkey.spec.ts new file mode 100644 index 00000000..cb344f7b --- /dev/null +++ b/packages/cosmwasm/src/pubkey.spec.ts @@ -0,0 +1,57 @@ +import { Encoding } from "@iov/encoding"; + +import { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; +import { PubKey } from "./types"; + +const { fromBase64 } = Encoding; + +describe("pubkey", () => { + describe("encodeSecp256k1Pubkey", () => { + it("encodes a compresed pubkey", () => { + const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP"); + expect(encodeSecp256k1Pubkey(pubkey)).toEqual({ + type: "tendermint/PubKeySecp256k1", + value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP", + }); + }); + + it("throws for uncompressed public keys", () => { + const pubkey = fromBase64( + "BE8EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQE7WHpoHoNswYeoFkuYpYSKK4mzFzMV/dB0DVAy4lnNU=", + ); + expect(() => encodeSecp256k1Pubkey(pubkey)).toThrowError(/public key must be compressed secp256k1/i); + }); + }); + + describe("decodeBech32Pubkey", () => { + it("works", () => { + expect( + decodeBech32Pubkey("cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5"), + ).toEqual({ + type: "tendermint/PubKeySecp256k1", + value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", + }); + }); + + it("works for enigma pubkey", () => { + expect( + decodeBech32Pubkey("enigmapub1addwnpepqw5k9p439nw0zpg2aundx4umwx4nw233z5prpjqjv5anl5grmnchzp2xwvv"), + ).toEqual({ + type: "tendermint/PubKeySecp256k1", + value: "A6lihrEs3PEFCu8m01ebcas3KjEVAjDIEmU7P9ED3PFx", + }); + }); + }); + + describe("encodeBech32Pubkey", () => { + it("works for secp256k1", () => { + const pubkey: PubKey = { + type: "tendermint/PubKeySecp256k1", + value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", + }; + expect(encodeBech32Pubkey(pubkey, "cosmospub")).toEqual( + "cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5", + ); + }); + }); +}); diff --git a/packages/cosmwasm/src/pubkey.ts b/packages/cosmwasm/src/pubkey.ts new file mode 100644 index 00000000..8835b97a --- /dev/null +++ b/packages/cosmwasm/src/pubkey.ts @@ -0,0 +1,71 @@ +import { Bech32, Encoding } from "@iov/encoding"; +import equal from "fast-deep-equal"; + +import { PubKey, pubkeyType } from "./types"; + +export function encodeSecp256k1Pubkey(pubkey: Uint8Array): PubKey { + if (pubkey.length !== 33 || (pubkey[0] !== 0x02 && pubkey[0] !== 0x03)) { + throw new Error("Public key must be compressed secp256k1, i.e. 33 bytes starting with 0x02 or 0x03"); + } + return { + type: pubkeyType.secp256k1, + value: Encoding.toBase64(pubkey), + }; +} + +// As discussed in https://github.com/binance-chain/javascript-sdk/issues/163 +// Prefixes listed here: https://github.com/tendermint/tendermint/blob/d419fffe18531317c28c29a292ad7d253f6cafdf/docs/spec/blockchain/encoding.md#public-key-cryptography +// Last bytes is varint-encoded length prefix +const pubkeyAminoPrefixSecp256k1 = Encoding.fromHex("eb5ae98721"); +const pubkeyAminoPrefixEd25519 = Encoding.fromHex("1624de6420"); +const pubkeyAminoPrefixSr25519 = Encoding.fromHex("0dfb1005"); +const pubkeyAminoPrefixLength = pubkeyAminoPrefixSecp256k1.length; + +export function decodeBech32Pubkey(bechEncoded: string): PubKey { + const { data } = Bech32.decode(bechEncoded); + + const aminoPrefix = data.slice(0, pubkeyAminoPrefixLength); + const rest = data.slice(pubkeyAminoPrefixLength); + if (equal(aminoPrefix, pubkeyAminoPrefixSecp256k1)) { + if (rest.length !== 33) { + throw new Error("Invalid rest data length. Expected 33 bytes (compressed secp256k1 pubkey)."); + } + return { + type: pubkeyType.secp256k1, + value: Encoding.toBase64(rest), + }; + } else if (equal(aminoPrefix, pubkeyAminoPrefixEd25519)) { + if (rest.length !== 32) { + throw new Error("Invalid rest data length. Expected 32 bytes (Ed25519 pubkey)."); + } + return { + type: pubkeyType.ed25519, + value: Encoding.toBase64(rest), + }; + } else if (equal(aminoPrefix, pubkeyAminoPrefixSr25519)) { + if (rest.length !== 32) { + throw new Error("Invalid rest data length. Expected 32 bytes (Sr25519 pubkey)."); + } + return { + type: pubkeyType.sr25519, + value: Encoding.toBase64(rest), + }; + } else { + throw new Error("Unsupported Pubkey type. Amino prefix: " + Encoding.toHex(aminoPrefix)); + } +} + +export function encodeBech32Pubkey(pubkey: PubKey, prefix: string): string { + let aminoPrefix: Uint8Array; + switch (pubkey.type) { + // Note: please don't add cases here without writing additional unit tests + case pubkeyType.secp256k1: + aminoPrefix = pubkeyAminoPrefixSecp256k1; + break; + default: + throw new Error("Unsupported pubkey type"); + } + + const data = new Uint8Array([...aminoPrefix, ...Encoding.fromBase64(pubkey.value)]); + return Bech32.encode(prefix, data); +} diff --git a/packages/cosmwasm/src/restclient.spec.ts b/packages/cosmwasm/src/restclient.spec.ts new file mode 100644 index 00000000..dfe67396 --- /dev/null +++ b/packages/cosmwasm/src/restclient.spec.ts @@ -0,0 +1,1375 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { Coin, makeCosmoshubPath, Pen, rawSecp256k1PubkeyToAddress, Secp256k1Pen, makeSignBytes } from "@cosmwasm/sdk38"; +import { Sha256 } from "@iov/crypto"; +import { Encoding } from "@iov/encoding"; +import { assert, sleep } from "@iov/utils"; +import { ReadonlyDate } from "readonly-date"; + +import { findAttribute, parseLogs } from "./logs"; +import { encodeBech32Pubkey } from "./pubkey"; +import { PostTxsResponse, RestClient, TxsResponse } from "./restclient"; +import { SigningCosmWasmClient } from "./signingcosmwasmclient"; +import cosmoshub from "./testdata/cosmoshub.json"; +import { + bech32AddressMatcher, + deployedErc20, + faucet, + fromOneElementArray, + getHackatom, + makeRandomAddress, + nonNegativeIntegerMatcher, + pendingWithoutWasmd, + semverMatcher, + tendermintAddressMatcher, + tendermintIdMatcher, + tendermintOptionalIdMatcher, + tendermintShortHashMatcher, + unused, + wasmd, + wasmdEnabled, +} from "./testutils.spec"; +import { + isMsgInstantiateContract, + isMsgStoreCode, + Msg, + MsgExecuteContract, + MsgInstantiateContract, + MsgSend, + MsgStoreCode, + StdFee, + StdSignature, + StdTx, +} from "./types"; + +const { fromAscii, fromBase64, fromHex, toAscii, toBase64, toHex } = Encoding; + +const emptyAddress = "cosmos1ltkhnmdcqemmd2tkhnx7qx66tq7e0wykw2j85k"; + +function makeSignedTx(firstMsg: Msg, fee: StdFee, memo: string, firstSignature: StdSignature): StdTx { + return { + msg: [firstMsg], + fee: fee, + memo: memo, + signatures: [firstSignature], + }; +} + +async function uploadCustomContract( + client: RestClient, + pen: Pen, + wasmCode: Uint8Array, +): Promise { + const memo = "My first contract on chain"; + const theMsg: MsgStoreCode = { + type: "wasm/store-code", + value: { + sender: faucet.address, + wasm_byte_code: toBase64(wasmCode), + source: "https://github.com/confio/cosmwasm/raw/0.7/lib/vm/testdata/contract_0.6.wasm", + builder: "confio/cosmwasm-opt:0.6.2", + }, + }; + const fee: StdFee = { + amount: [ + { + amount: "5000000", + denom: "ucosm", + }, + ], + gas: "89000000", + }; + + const { account_number, sequence } = (await client.authAccounts(faucet.address)).result.value; + const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); + const signature = await pen.sign(signBytes); + const signedTx = makeSignedTx(theMsg, fee, memo, signature); + return client.postTx(signedTx); +} + +async function uploadContract(client: RestClient, pen: Pen): Promise { + return uploadCustomContract(client, pen, getHackatom()); +} + +async function instantiateContract( + client: RestClient, + pen: Pen, + codeId: number, + beneficiaryAddress: string, + transferAmount?: readonly Coin[], +): Promise { + const memo = "Create an escrow instance"; + const theMsg: MsgInstantiateContract = { + type: "wasm/instantiate", + value: { + sender: faucet.address, + code_id: codeId.toString(), + label: "my escrow", + init_msg: { + verifier: faucet.address, + beneficiary: beneficiaryAddress, + }, + init_funds: transferAmount || [], + }, + }; + const fee: StdFee = { + amount: [ + { + amount: "5000000", + denom: "ucosm", + }, + ], + gas: "89000000", + }; + + const { account_number, sequence } = (await client.authAccounts(faucet.address)).result.value; + const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); + const signature = await pen.sign(signBytes); + const signedTx = makeSignedTx(theMsg, fee, memo, signature); + return client.postTx(signedTx); +} + +async function executeContract( + client: RestClient, + pen: Pen, + contractAddress: string, +): Promise { + const memo = "Time for action"; + const theMsg: MsgExecuteContract = { + type: "wasm/execute", + value: { + sender: faucet.address, + contract: contractAddress, + msg: { release: {} }, + sent_funds: [], + }, + }; + const fee: StdFee = { + amount: [ + { + amount: "5000000", + denom: "ucosm", + }, + ], + gas: "89000000", + }; + + const { account_number, sequence } = (await client.authAccounts(faucet.address)).result.value; + const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); + const signature = await pen.sign(signBytes); + const signedTx = makeSignedTx(theMsg, fee, memo, signature); + return client.postTx(signedTx); +} + +describe("RestClient", () => { + it("can be constructed", () => { + const client = new RestClient(wasmd.endpoint); + expect(client).toBeTruthy(); + }); + + // The /auth endpoints + + describe("authAccounts", () => { + it("works for unused account without pubkey", async () => { + pendingWithoutWasmd(); + const client = new RestClient(wasmd.endpoint); + const { height, result } = await client.authAccounts(unused.address); + expect(height).toMatch(nonNegativeIntegerMatcher); + expect(result).toEqual({ + type: "cosmos-sdk/Account", + value: { + address: unused.address, + public_key: "", // not known to the chain + coins: [ + { + amount: "1000000000", + denom: "ucosm", + }, + { + amount: "1000000000", + denom: "ustake", + }, + ], + account_number: unused.accountNumber, + sequence: 0, + }, + }); + }); + + // This fails in the first test run if you forget to run `./scripts/wasmd/init.sh` + it("has correct pubkey for faucet", async () => { + pendingWithoutWasmd(); + const client = new RestClient(wasmd.endpoint); + const { result } = await client.authAccounts(faucet.address); + expect(result.value).toEqual( + jasmine.objectContaining({ + public_key: encodeBech32Pubkey(faucet.pubkey, "cosmospub"), + }), + ); + }); + + // This property is used by CosmWasmClient.getAccount + it("returns empty address for non-existent account", async () => { + pendingWithoutWasmd(); + const client = new RestClient(wasmd.endpoint); + const nonExistentAccount = makeRandomAddress(); + const { result } = await client.authAccounts(nonExistentAccount); + expect(result).toEqual({ + type: "cosmos-sdk/Account", + value: jasmine.objectContaining({ address: "" }), + }); + }); + }); + + // The /blocks endpoints + + describe("blocksLatest", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = new RestClient(wasmd.endpoint); + const response = await client.blocksLatest(); + + // id + expect(response.block_id.hash).toMatch(tendermintIdMatcher); + + // header + expect(response.block.header.version).toEqual({ block: "10", app: "0" }); + expect(parseInt(response.block.header.height, 10)).toBeGreaterThanOrEqual(1); + expect(response.block.header.chain_id).toEqual(wasmd.chainId); + expect(new ReadonlyDate(response.block.header.time).getTime()).toBeLessThan(ReadonlyDate.now()); + expect(new ReadonlyDate(response.block.header.time).getTime()).toBeGreaterThanOrEqual( + ReadonlyDate.now() - 5_000, + ); + expect(response.block.header.last_commit_hash).toMatch(tendermintIdMatcher); + expect(response.block.header.last_block_id.hash).toMatch(tendermintIdMatcher); + expect(response.block.header.data_hash).toMatch(tendermintOptionalIdMatcher); + expect(response.block.header.validators_hash).toMatch(tendermintIdMatcher); + expect(response.block.header.next_validators_hash).toMatch(tendermintIdMatcher); + expect(response.block.header.consensus_hash).toMatch(tendermintIdMatcher); + expect(response.block.header.app_hash).toMatch(tendermintIdMatcher); + expect(response.block.header.last_results_hash).toMatch(tendermintOptionalIdMatcher); + expect(response.block.header.evidence_hash).toMatch(tendermintOptionalIdMatcher); + expect(response.block.header.proposer_address).toMatch(tendermintAddressMatcher); + + // data + expect(response.block.data.txs === null || Array.isArray(response.block.data.txs)).toEqual(true); + }); + }); + + describe("blocks", () => { + it("works for block by height", async () => { + pendingWithoutWasmd(); + const client = new RestClient(wasmd.endpoint); + const height = parseInt((await client.blocksLatest()).block.header.height, 10); + const response = await client.blocks(height - 1); + + // id + expect(response.block_id.hash).toMatch(tendermintIdMatcher); + + // header + expect(response.block.header.version).toEqual({ block: "10", app: "0" }); + expect(response.block.header.height).toEqual(`${height - 1}`); + expect(response.block.header.chain_id).toEqual(wasmd.chainId); + expect(new ReadonlyDate(response.block.header.time).getTime()).toBeLessThan(ReadonlyDate.now()); + expect(new ReadonlyDate(response.block.header.time).getTime()).toBeGreaterThanOrEqual( + ReadonlyDate.now() - 5_000, + ); + expect(response.block.header.last_commit_hash).toMatch(tendermintIdMatcher); + expect(response.block.header.last_block_id.hash).toMatch(tendermintIdMatcher); + expect(response.block.header.data_hash).toMatch(tendermintOptionalIdMatcher); + expect(response.block.header.validators_hash).toMatch(tendermintIdMatcher); + expect(response.block.header.next_validators_hash).toMatch(tendermintIdMatcher); + expect(response.block.header.consensus_hash).toMatch(tendermintIdMatcher); + expect(response.block.header.app_hash).toMatch(tendermintIdMatcher); + expect(response.block.header.last_results_hash).toMatch(tendermintOptionalIdMatcher); + expect(response.block.header.evidence_hash).toMatch(tendermintOptionalIdMatcher); + expect(response.block.header.proposer_address).toMatch(tendermintAddressMatcher); + + // data + expect(response.block.data.txs === null || Array.isArray(response.block.data.txs)).toEqual(true); + }); + }); + + // The /node_info endpoint + + describe("nodeInfo", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = new RestClient(wasmd.endpoint); + const { node_info, application_version } = await client.nodeInfo(); + + expect(node_info).toEqual({ + protocol_version: { p2p: "7", block: "10", app: "0" }, + id: jasmine.stringMatching(tendermintShortHashMatcher), + listen_addr: "tcp://0.0.0.0:26656", + network: wasmd.chainId, + version: jasmine.stringMatching(/^0\.33\.[0-9]+$/), + channels: "4020212223303800", + moniker: wasmd.chainId, + other: { tx_index: "on", rpc_address: "tcp://0.0.0.0:26657" }, + }); + expect(application_version).toEqual({ + name: "wasm", + server_name: "wasmd", + client_name: "wasmcli", + version: jasmine.stringMatching(semverMatcher), + commit: jasmine.stringMatching(tendermintShortHashMatcher), + build_tags: "netgo,ledger", + go: jasmine.stringMatching(/^go version go1\.[0-9]+\.[0-9]+ linux\/amd64$/), + }); + }); + }); + + // The /txs endpoints + + describe("txById", () => { + let successful: + | { + readonly sender: string; + readonly recipient: string; + readonly hash: string; + } + | undefined; + let unsuccessful: + | { + readonly sender: string; + readonly recipient: string; + readonly hash: string; + } + | undefined; + + beforeAll(async () => { + if (wasmdEnabled()) { + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmWasmClient(wasmd.endpoint, faucet.address, (signBytes) => + pen.sign(signBytes), + ); + + { + const recipient = makeRandomAddress(); + const transferAmount = { + denom: "ucosm", + amount: "1234567", + }; + const result = await client.sendTokens(recipient, [transferAmount]); + successful = { + sender: faucet.address, + recipient: recipient, + hash: result.transactionHash, + }; + } + + { + const memo = "Sending more than I can afford"; + const recipient = makeRandomAddress(); + const transferAmount = [ + { + denom: "ucosm", + amount: "123456700000000", + }, + ]; + const sendMsg: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + // eslint-disable-next-line @typescript-eslint/camelcase + from_address: faucet.address, + // eslint-disable-next-line @typescript-eslint/camelcase + to_address: recipient, + amount: transferAmount, + }, + }; + const fee = { + amount: [ + { + denom: "ucosm", + amount: "2000", + }, + ], + gas: "80000", // 80k + }; + const { accountNumber, sequence } = await client.getNonce(); + const chainId = await client.getChainId(); + const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); + const signature = await pen.sign(signBytes); + const signedTx = { + msg: [sendMsg], + fee: fee, + memo: memo, + signatures: [signature], + }; + const transactionId = await client.getIdentifier({ type: "cosmos-sdk/StdTx", value: signedTx }); + try { + await client.postTx(signedTx); + } catch (error) { + // postTx() throws on execution failures, which is a questionable design. Ignore for now. + // console.log(error); + } + unsuccessful = { + sender: faucet.address, + recipient: recipient, + hash: transactionId, + }; + } + + await sleep(50); // wait until transactions are indexed + } + }); + + it("works for successful transaction", async () => { + pendingWithoutWasmd(); + assert(successful); + const client = new RestClient(wasmd.endpoint); + const result = await client.txById(successful.hash); + expect(result.height).toBeGreaterThanOrEqual(1); + expect(result.txhash).toEqual(successful.hash); + expect(result.codespace).toBeUndefined(); + expect(result.code).toBeUndefined(); + const logs = parseLogs(result.logs); + expect(logs).toEqual([ + { + msg_index: 0, + log: "", + events: [ + { + type: "message", + attributes: [ + { key: "action", value: "send" }, + { key: "sender", value: successful.sender }, + { key: "module", value: "bank" }, + ], + }, + { + type: "transfer", + attributes: [ + { key: "recipient", value: successful.recipient }, + { key: "sender", value: successful.sender }, + { key: "amount", value: "1234567ucosm" }, + ], + }, + ], + }, + ]); + }); + + it("works for unsuccessful transaction", async () => { + pendingWithoutWasmd(); + assert(unsuccessful); + const client = new RestClient(wasmd.endpoint); + const result = await client.txById(unsuccessful.hash); + expect(result.height).toBeGreaterThanOrEqual(1); + expect(result.txhash).toEqual(unsuccessful.hash); + expect(result.codespace).toEqual("sdk"); + expect(result.code).toEqual(5); + expect(result.logs).toBeUndefined(); + expect(result.raw_log).toContain("insufficient funds"); + }); + }); + + describe("txsQuery", () => { + let posted: + | { + readonly sender: string; + readonly recipient: string; + readonly hash: string; + readonly height: number; + readonly tx: TxsResponse; + } + | undefined; + + beforeAll(async () => { + if (wasmdEnabled()) { + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmWasmClient(wasmd.endpoint, faucet.address, (signBytes) => + pen.sign(signBytes), + ); + + const recipient = makeRandomAddress(); + const transferAmount = [ + { + denom: "ucosm", + amount: "1234567", + }, + ]; + const result = await client.sendTokens(recipient, transferAmount); + + await sleep(50); // wait until tx is indexed + const txDetails = await new RestClient(wasmd.endpoint).txById(result.transactionHash); + posted = { + sender: faucet.address, + recipient: recipient, + hash: result.transactionHash, + height: Number.parseInt(txDetails.height, 10), + tx: txDetails, + }; + } + }); + + it("can query transactions by height", async () => { + pendingWithoutWasmd(); + assert(posted); + const client = new RestClient(wasmd.endpoint); + const result = await client.txsQuery(`tx.height=${posted.height}&limit=26`); + expect(result).toEqual({ + count: "1", + limit: "26", + page_number: "1", + page_total: "1", + total_count: "1", + txs: [posted.tx], + }); + }); + + it("can query transactions by ID", async () => { + pendingWithoutWasmd(); + assert(posted); + const client = new RestClient(wasmd.endpoint); + const result = await client.txsQuery(`tx.hash=${posted.hash}&limit=26`); + expect(result).toEqual({ + count: "1", + limit: "26", + page_number: "1", + page_total: "1", + total_count: "1", + txs: [posted.tx], + }); + }); + + it("can query transactions by sender", async () => { + pendingWithoutWasmd(); + assert(posted); + const client = new RestClient(wasmd.endpoint); + const result = await client.txsQuery(`message.sender=${posted.sender}&limit=200`); + expect(parseInt(result.count, 10)).toBeGreaterThanOrEqual(1); + expect(parseInt(result.limit, 10)).toEqual(200); + expect(parseInt(result.page_number, 10)).toEqual(1); + expect(parseInt(result.page_total, 10)).toEqual(1); + expect(parseInt(result.total_count, 10)).toBeGreaterThanOrEqual(1); + expect(result.txs.length).toBeGreaterThanOrEqual(1); + expect(result.txs[result.txs.length - 1]).toEqual(posted.tx); + }); + + it("can query transactions by recipient", async () => { + pendingWithoutWasmd(); + assert(posted); + const client = new RestClient(wasmd.endpoint); + const result = await client.txsQuery(`transfer.recipient=${posted.recipient}&limit=200`); + expect(parseInt(result.count, 10)).toEqual(1); + expect(parseInt(result.limit, 10)).toEqual(200); + expect(parseInt(result.page_number, 10)).toEqual(1); + expect(parseInt(result.page_total, 10)).toEqual(1); + expect(parseInt(result.total_count, 10)).toEqual(1); + expect(result.txs.length).toBeGreaterThanOrEqual(1); + expect(result.txs[result.txs.length - 1]).toEqual(posted.tx); + }); + + it("can filter by tx.hash and tx.minheight", async () => { + pending("This combination is broken 🤷‍♂️. Handle client-side at higher level."); + pendingWithoutWasmd(); + assert(posted); + const client = new RestClient(wasmd.endpoint); + const hashQuery = `tx.hash=${posted.hash}`; + + { + const { count } = await client.txsQuery(`${hashQuery}&tx.minheight=0`); + expect(count).toEqual("1"); + } + + { + const { count } = await client.txsQuery(`${hashQuery}&tx.minheight=${posted.height - 1}`); + expect(count).toEqual("1"); + } + + { + const { count } = await client.txsQuery(`${hashQuery}&tx.minheight=${posted.height}`); + expect(count).toEqual("1"); + } + + { + const { count } = await client.txsQuery(`${hashQuery}&tx.minheight=${posted.height + 1}`); + expect(count).toEqual("0"); + } + }); + + it("can filter by recipient and tx.minheight", async () => { + pendingWithoutWasmd(); + assert(posted); + const client = new RestClient(wasmd.endpoint); + const recipientQuery = `transfer.recipient=${posted.recipient}`; + + { + const { count } = await client.txsQuery(`${recipientQuery}&tx.minheight=0`); + expect(count).toEqual("1"); + } + + { + const { count } = await client.txsQuery(`${recipientQuery}&tx.minheight=${posted.height - 1}`); + expect(count).toEqual("1"); + } + + { + const { count } = await client.txsQuery(`${recipientQuery}&tx.minheight=${posted.height}`); + expect(count).toEqual("1"); + } + + { + const { count } = await client.txsQuery(`${recipientQuery}&tx.minheight=${posted.height + 1}`); + expect(count).toEqual("0"); + } + }); + + it("can filter by recipient and tx.maxheight", async () => { + pendingWithoutWasmd(); + assert(posted); + const client = new RestClient(wasmd.endpoint); + const recipientQuery = `transfer.recipient=${posted.recipient}`; + + { + const { count } = await client.txsQuery(`${recipientQuery}&tx.maxheight=9999999999999`); + expect(count).toEqual("1"); + } + + { + const { count } = await client.txsQuery(`${recipientQuery}&tx.maxheight=${posted.height + 1}`); + expect(count).toEqual("1"); + } + + { + const { count } = await client.txsQuery(`${recipientQuery}&tx.maxheight=${posted.height}`); + expect(count).toEqual("1"); + } + + { + const { count } = await client.txsQuery(`${recipientQuery}&tx.maxheight=${posted.height - 1}`); + expect(count).toEqual("0"); + } + }); + + it("can query by tags (module + code_id)", async () => { + pendingWithoutWasmd(); + assert(posted); + const client = new RestClient(wasmd.endpoint); + const result = await client.txsQuery(`message.module=wasm&message.code_id=${deployedErc20.codeId}`); + expect(parseInt(result.count, 10)).toBeGreaterThanOrEqual(4); + + // Check first 4 results + const [store, hash, isa, jade] = result.txs.map((tx) => fromOneElementArray(tx.tx.value.msg)); + assert(isMsgStoreCode(store)); + assert(isMsgInstantiateContract(hash)); + assert(isMsgInstantiateContract(isa)); + assert(isMsgInstantiateContract(jade)); + expect(store.value).toEqual( + jasmine.objectContaining({ + sender: faucet.address, + source: deployedErc20.source, + builder: deployedErc20.builder, + }), + ); + expect(hash.value).toEqual({ + code_id: deployedErc20.codeId.toString(), + init_funds: [], + init_msg: jasmine.objectContaining({ + symbol: "HASH", + }), + label: "HASH", + sender: faucet.address, + }); + expect(isa.value).toEqual({ + code_id: deployedErc20.codeId.toString(), + init_funds: [], + init_msg: jasmine.objectContaining({ symbol: "ISA" }), + label: "ISA", + sender: faucet.address, + }); + expect(jade.value).toEqual({ + code_id: deployedErc20.codeId.toString(), + init_funds: [], + init_msg: jasmine.objectContaining({ symbol: "JADE" }), + label: "JADE", + sender: faucet.address, + }); + }); + + // Like previous test but filtered by message.action=store-code and message.action=instantiate + it("can query by tags (module + code_id + action)", async () => { + pendingWithoutWasmd(); + assert(posted); + const client = new RestClient(wasmd.endpoint); + + { + const uploads = await client.txsQuery( + `message.module=wasm&message.code_id=${deployedErc20.codeId}&message.action=store-code`, + ); + expect(parseInt(uploads.count, 10)).toEqual(1); + const store = fromOneElementArray(uploads.txs[0].tx.value.msg); + assert(isMsgStoreCode(store)); + expect(store.value).toEqual( + jasmine.objectContaining({ + sender: faucet.address, + source: deployedErc20.source, + builder: deployedErc20.builder, + }), + ); + } + + { + const instantiations = await client.txsQuery( + `message.module=wasm&message.code_id=${deployedErc20.codeId}&message.action=instantiate`, + ); + expect(parseInt(instantiations.count, 10)).toBeGreaterThanOrEqual(3); + const [hash, isa, jade] = instantiations.txs.map((tx) => fromOneElementArray(tx.tx.value.msg)); + assert(isMsgInstantiateContract(hash)); + assert(isMsgInstantiateContract(isa)); + assert(isMsgInstantiateContract(jade)); + expect(hash.value).toEqual({ + code_id: deployedErc20.codeId.toString(), + init_funds: [], + init_msg: jasmine.objectContaining({ + symbol: "HASH", + }), + label: "HASH", + sender: faucet.address, + }); + expect(isa.value).toEqual({ + code_id: deployedErc20.codeId.toString(), + init_funds: [], + init_msg: jasmine.objectContaining({ symbol: "ISA" }), + label: "ISA", + sender: faucet.address, + }); + expect(jade.value).toEqual({ + code_id: deployedErc20.codeId.toString(), + init_funds: [], + init_msg: jasmine.objectContaining({ symbol: "JADE" }), + label: "JADE", + sender: faucet.address, + }); + } + }); + }); + + describe("encodeTx", () => { + it("works for cosmoshub example", async () => { + pendingWithoutWasmd(); + const client = new RestClient(wasmd.endpoint); + expect(await client.encodeTx(cosmoshub.tx)).toEqual(fromBase64(cosmoshub.tx_data)); + }); + }); + + describe("postTx", () => { + it("can send tokens", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + + const memo = "My first contract on chain"; + const theMsg: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: faucet.address, + to_address: emptyAddress, + amount: [ + { + denom: "ucosm", + amount: "1234567", + }, + ], + }, + }; + + const fee: StdFee = { + amount: [ + { + amount: "5000", + denom: "ucosm", + }, + ], + gas: "890000", + }; + + const client = new RestClient(wasmd.endpoint); + const { account_number, sequence } = (await client.authAccounts(faucet.address)).result.value; + + const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); + const signature = await pen.sign(signBytes); + const signedTx = makeSignedTx(theMsg, fee, memo, signature); + const result = await client.postTx(signedTx); + expect(result.code).toBeUndefined(); + expect(result).toEqual({ + height: jasmine.stringMatching(nonNegativeIntegerMatcher), + txhash: jasmine.stringMatching(tendermintIdMatcher), + // code is not set + raw_log: jasmine.stringMatching(/^\[.+\]$/i), + logs: jasmine.any(Array), + gas_wanted: jasmine.stringMatching(nonNegativeIntegerMatcher), + gas_used: jasmine.stringMatching(nonNegativeIntegerMatcher), + }); + }); + + it("can't send transaction with additional signatures", async () => { + pendingWithoutWasmd(); + const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); + const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); + const account3 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(2)); + const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos"); + const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos"); + const address3 = rawSecp256k1PubkeyToAddress(account3.pubkey, "cosmos"); + + const memo = "My first contract on chain"; + const theMsg: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: address1, + to_address: emptyAddress, + amount: [ + { + denom: "ucosm", + amount: "1234567", + }, + ], + }, + }; + + const fee: StdFee = { + amount: [ + { + amount: "5000", + denom: "ucosm", + }, + ], + gas: "890000", + }; + + const client = new RestClient(wasmd.endpoint); + const { account_number: an1, sequence: sequence1 } = (await client.authAccounts(address1)).result.value; + const { account_number: an2, sequence: sequence2 } = (await client.authAccounts(address2)).result.value; + const { account_number: an3, sequence: sequence3 } = (await client.authAccounts(address3)).result.value; + + const signBytes1 = makeSignBytes([theMsg], fee, wasmd.chainId, memo, an1, sequence1); + const signBytes2 = makeSignBytes([theMsg], fee, wasmd.chainId, memo, an2, sequence2); + const signBytes3 = makeSignBytes([theMsg], fee, wasmd.chainId, memo, an3, sequence3); + const signature1 = await account1.sign(signBytes1); + const signature2 = await account2.sign(signBytes2); + const signature3 = await account3.sign(signBytes3); + const signedTx = { + msg: [theMsg], + fee: fee, + memo: memo, + signatures: [signature1, signature2, signature3], + }; + const postResult = await client.postTx(signedTx); + // console.log(postResult.raw_log); + expect(postResult.code).toEqual(4); + expect(postResult.raw_log).toContain("wrong number of signers"); + }); + + it("can send multiple messages with one signature", async () => { + pendingWithoutWasmd(); + const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); + const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos"); + + const memo = "My first contract on chain"; + const msg1: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: address1, + to_address: emptyAddress, + amount: [ + { + denom: "ucosm", + amount: "1234567", + }, + ], + }, + }; + const msg2: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: address1, + to_address: emptyAddress, + amount: [ + { + denom: "ucosm", + amount: "7654321", + }, + ], + }, + }; + + const fee: StdFee = { + amount: [ + { + amount: "5000", + denom: "ucosm", + }, + ], + gas: "890000", + }; + + const client = new RestClient(wasmd.endpoint); + const { account_number, sequence } = (await client.authAccounts(address1)).result.value; + + const signBytes = makeSignBytes([msg1, msg2], fee, wasmd.chainId, memo, account_number, sequence); + const signature1 = await account1.sign(signBytes); + const signedTx = { + msg: [msg1, msg2], + fee: fee, + memo: memo, + signatures: [signature1], + }; + const postResult = await client.postTx(signedTx); + // console.log(postResult.raw_log); + expect(postResult.code).toBeUndefined(); + }); + + it("can send multiple messages with multiple signatures", async () => { + pendingWithoutWasmd(); + const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); + const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); + const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos"); + const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos"); + + const memo = "My first contract on chain"; + const msg1: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: address1, + to_address: emptyAddress, + amount: [ + { + denom: "ucosm", + amount: "1234567", + }, + ], + }, + }; + const msg2: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: address2, + to_address: emptyAddress, + amount: [ + { + denom: "ucosm", + amount: "7654321", + }, + ], + }, + }; + + const fee: StdFee = { + amount: [ + { + amount: "5000", + denom: "ucosm", + }, + ], + gas: "890000", + }; + + const client = new RestClient(wasmd.endpoint); + const { account_number: an1, sequence: sequence1 } = (await client.authAccounts(address1)).result.value; + const { account_number: an2, sequence: sequence2 } = (await client.authAccounts(address2)).result.value; + + const signBytes1 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an1, sequence1); + const signBytes2 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an2, sequence2); + const signature1 = await account1.sign(signBytes1); + const signature2 = await account2.sign(signBytes2); + const signedTx = { + msg: [msg2, msg1], + fee: fee, + memo: memo, + signatures: [signature2, signature1], + }; + const postResult = await client.postTx(signedTx); + // console.log(postResult.raw_log); + expect(postResult.code).toBeUndefined(); + + await sleep(500); + const searched = await client.txsQuery(`tx.hash=${postResult.txhash}`); + expect(searched.txs.length).toEqual(1); + expect(searched.txs[0].tx.value.signatures).toEqual([signature2, signature1]); + }); + + it("can't send transaction with wrong signature order (1)", async () => { + pendingWithoutWasmd(); + const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); + const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); + const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos"); + const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos"); + + const memo = "My first contract on chain"; + const msg1: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: address1, + to_address: emptyAddress, + amount: [ + { + denom: "ucosm", + amount: "1234567", + }, + ], + }, + }; + const msg2: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: address2, + to_address: emptyAddress, + amount: [ + { + denom: "ucosm", + amount: "7654321", + }, + ], + }, + }; + + const fee: StdFee = { + amount: [ + { + amount: "5000", + denom: "ucosm", + }, + ], + gas: "890000", + }; + + const client = new RestClient(wasmd.endpoint); + const { account_number: an1, sequence: sequence1 } = (await client.authAccounts(address1)).result.value; + const { account_number: an2, sequence: sequence2 } = (await client.authAccounts(address2)).result.value; + + const signBytes1 = makeSignBytes([msg1, msg2], fee, wasmd.chainId, memo, an1, sequence1); + const signBytes2 = makeSignBytes([msg1, msg2], fee, wasmd.chainId, memo, an2, sequence2); + const signature1 = await account1.sign(signBytes1); + const signature2 = await account2.sign(signBytes2); + const signedTx = { + msg: [msg1, msg2], + fee: fee, + memo: memo, + signatures: [signature2, signature1], + }; + const postResult = await client.postTx(signedTx); + // console.log(postResult.raw_log); + expect(postResult.code).toEqual(8); + }); + + it("can't send transaction with wrong signature order (2)", async () => { + pendingWithoutWasmd(); + const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); + const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); + const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos"); + const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos"); + + const memo = "My first contract on chain"; + const msg1: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: address1, + to_address: emptyAddress, + amount: [ + { + denom: "ucosm", + amount: "1234567", + }, + ], + }, + }; + const msg2: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: address2, + to_address: emptyAddress, + amount: [ + { + denom: "ucosm", + amount: "7654321", + }, + ], + }, + }; + + const fee: StdFee = { + amount: [ + { + amount: "5000", + denom: "ucosm", + }, + ], + gas: "890000", + }; + + const client = new RestClient(wasmd.endpoint); + const { account_number: an1, sequence: sequence1 } = (await client.authAccounts(address1)).result.value; + const { account_number: an2, sequence: sequence2 } = (await client.authAccounts(address2)).result.value; + + const signBytes1 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an1, sequence1); + const signBytes2 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an2, sequence2); + const signature1 = await account1.sign(signBytes1); + const signature2 = await account2.sign(signBytes2); + const signedTx = { + msg: [msg2, msg1], + fee: fee, + memo: memo, + signatures: [signature1, signature2], + }; + const postResult = await client.postTx(signedTx); + // console.log(postResult.raw_log); + expect(postResult.code).toEqual(8); + }); + + it("can upload, instantiate and execute wasm", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new RestClient(wasmd.endpoint); + + const transferAmount: readonly Coin[] = [ + { + amount: "1234", + denom: "ucosm", + }, + { + amount: "321", + denom: "ustake", + }, + ]; + const beneficiaryAddress = makeRandomAddress(); + + let codeId: number; + + // upload + { + // console.log("Raw log:", result.raw_log); + const result = await uploadContract(client, pen); + expect(result.code).toBeFalsy(); + const logs = parseLogs(result.logs); + const codeIdAttr = findAttribute(logs, "message", "code_id"); + codeId = Number.parseInt(codeIdAttr.value, 10); + expect(codeId).toBeGreaterThanOrEqual(1); + expect(codeId).toBeLessThanOrEqual(200); + } + + let contractAddress: string; + + // instantiate + { + const result = await instantiateContract(client, pen, codeId, beneficiaryAddress, transferAmount); + expect(result.code).toBeFalsy(); + // console.log("Raw log:", result.raw_log); + const logs = parseLogs(result.logs); + const contractAddressAttr = findAttribute(logs, "message", "contract_address"); + contractAddress = contractAddressAttr.value; + const amountAttr = findAttribute(logs, "transfer", "amount"); + expect(amountAttr.value).toEqual("1234ucosm,321ustake"); + + const balance = (await client.authAccounts(contractAddress)).result.value.coins; + expect(balance).toEqual(transferAmount); + } + + // execute + { + const result = await executeContract(client, pen, contractAddress); + expect(result.code).toBeFalsy(); + // console.log("Raw log:", result.logs); + const logs = parseLogs(result.logs); + const wasmEvent = logs.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 beneficiaryBalance = (await client.authAccounts(beneficiaryAddress)).result.value.coins; + expect(beneficiaryBalance).toEqual(transferAmount); + const contractBalance = (await client.authAccounts(contractAddress)).result.value.coins; + expect(contractBalance).toEqual([]); + } + }); + }); + + // The /wasm endpoints + + describe("query", () => { + it("can list upload code", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new RestClient(wasmd.endpoint); + + // check with contracts were here first to compare + const existingInfos = await client.listCodeInfo(); + existingInfos.forEach((val, idx) => expect(val.id).toEqual(idx + 1)); + const numExisting = existingInfos.length; + + // upload data + const wasmCode = getHackatom(); + const result = await uploadCustomContract(client, pen, wasmCode); + expect(result.code).toBeFalsy(); + const logs = parseLogs(result.logs); + const codeIdAttr = findAttribute(logs, "message", "code_id"); + const codeId = Number.parseInt(codeIdAttr.value, 10); + + // ensure we were added to the end of the list + const newInfos = await client.listCodeInfo(); + expect(newInfos.length).toEqual(numExisting + 1); + const lastInfo = newInfos[newInfos.length - 1]; + expect(lastInfo.id).toEqual(codeId); + expect(lastInfo.creator).toEqual(faucet.address); + + // ensure metadata is present + expect(lastInfo.source).toEqual( + "https://github.com/confio/cosmwasm/raw/0.7/lib/vm/testdata/contract_0.6.wasm", + ); + expect(lastInfo.builder).toEqual("confio/cosmwasm-opt:0.6.2"); + + // check code hash matches expectation + const wasmHash = new Sha256(wasmCode).digest(); + expect(lastInfo.data_hash.toLowerCase()).toEqual(toHex(wasmHash)); + + // download code and check against auto-gen + const { data } = await client.getCode(codeId); + expect(fromBase64(data)).toEqual(wasmCode); + }); + + it("can list contracts and get info", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new RestClient(wasmd.endpoint); + const beneficiaryAddress = makeRandomAddress(); + const transferAmount: readonly Coin[] = [ + { + amount: "707707", + denom: "ucosm", + }, + ]; + + // reuse an existing contract, or upload if needed + let codeId: number; + const existingInfos = await client.listCodeInfo(); + if (existingInfos.length > 0) { + codeId = existingInfos[existingInfos.length - 1].id; + } else { + const uploadResult = await uploadContract(client, pen); + expect(uploadResult.code).toBeFalsy(); + const uploadLogs = parseLogs(uploadResult.logs); + const codeIdAttr = findAttribute(uploadLogs, "message", "code_id"); + codeId = Number.parseInt(codeIdAttr.value, 10); + } + + // create new instance and compare before and after + const existingContractsByCode = await client.listContractsByCodeId(codeId); + for (const contract of existingContractsByCode) { + expect(contract.address).toMatch(bech32AddressMatcher); + expect(contract.code_id).toEqual(codeId); + expect(contract.creator).toMatch(bech32AddressMatcher); + expect(contract.label).toMatch(/^.+$/); + } + + const result = await instantiateContract(client, pen, codeId, beneficiaryAddress, transferAmount); + expect(result.code).toBeFalsy(); + const logs = parseLogs(result.logs); + const contractAddressAttr = findAttribute(logs, "message", "contract_address"); + const myAddress = contractAddressAttr.value; + + const newContractsByCode = await client.listContractsByCodeId(codeId); + expect(newContractsByCode.length).toEqual(existingContractsByCode.length + 1); + const newContract = newContractsByCode[newContractsByCode.length - 1]; + expect(newContract).toEqual( + jasmine.objectContaining({ + code_id: codeId, + creator: faucet.address, + label: "my escrow", + }), + ); + + // check out info + const myInfo = await client.getContractInfo(myAddress); + assert(myInfo); + expect(myInfo.code_id).toEqual(codeId); + expect(myInfo.creator).toEqual(faucet.address); + expect((myInfo.init_msg as any).beneficiary).toEqual(beneficiaryAddress); + + // make sure random addresses don't give useful info + const nonExistentAddress = makeRandomAddress(); + expect(await client.getContractInfo(nonExistentAddress)).toBeNull(); + }); + + describe("contract state", () => { + const client = new RestClient(wasmd.endpoint); + const noContract = makeRandomAddress(); + const expectedKey = toAscii("config"); + let contractAddress: string | undefined; + + beforeAll(async () => { + if (wasmdEnabled()) { + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const uploadResult = await uploadContract(client, pen); + assert(!uploadResult.code); + const uploadLogs = parseLogs(uploadResult.logs); + const codeId = Number.parseInt(findAttribute(uploadLogs, "message", "code_id").value, 10); + const instantiateResult = await instantiateContract(client, pen, codeId, makeRandomAddress()); + assert(!instantiateResult.code); + const instantiateLogs = parseLogs(instantiateResult.logs); + const contractAddressAttr = findAttribute(instantiateLogs, "message", "contract_address"); + contractAddress = contractAddressAttr.value; + } + }); + + it("can get all state", async () => { + pendingWithoutWasmd(); + + // get contract state + const state = await client.getAllContractState(contractAddress!); + expect(state.length).toEqual(1); + const data = state[0]; + expect(data.key).toEqual(expectedKey); + const value = JSON.parse(fromAscii(data.val)); + expect(value.verifier).toBeDefined(); + expect(value.beneficiary).toBeDefined(); + + // bad address is empty array + const noContractState = await client.getAllContractState(noContract); + expect(noContractState).toEqual([]); + }); + + it("can query by key", async () => { + pendingWithoutWasmd(); + + // query by one key + const raw = await client.queryContractRaw(contractAddress!, expectedKey); + assert(raw, "must get result"); + const model = JSON.parse(fromAscii(raw)); + expect(model.verifier).toBeDefined(); + expect(model.beneficiary).toBeDefined(); + + // missing key is null + const missing = await client.queryContractRaw(contractAddress!, fromHex("cafe0dad")); + expect(missing).toBeNull(); + + // bad address is null + const noContractModel = await client.queryContractRaw(noContract, expectedKey); + expect(noContractModel).toBeNull(); + }); + + it("can make smart queries", async () => { + pendingWithoutWasmd(); + + // we can query the verifier properly + const resultDocument = await client.queryContractSmart(contractAddress!, { verifier: {} }); + expect(resultDocument).toEqual({ verifier: faucet.address }); + + // invalid query syntax throws an error + await client.queryContractSmart(contractAddress!, { nosuchkey: {} }).then( + () => fail("shouldn't succeed"), + (error) => + expect(error).toMatch(/query wasm contract failed: parsing hackatom::contract::QueryMsg/), + ); + + // invalid address throws an error + await client.queryContractSmart(noContract, { verifier: {} }).then( + () => fail("shouldn't succeed"), + (error) => expect(error).toMatch("not found"), + ); + }); + }); + }); +}); diff --git a/packages/cosmwasm/src/restclient.ts b/packages/cosmwasm/src/restclient.ts new file mode 100644 index 00000000..0e5846c8 --- /dev/null +++ b/packages/cosmwasm/src/restclient.ts @@ -0,0 +1,457 @@ +import { Coin } from "@cosmwasm/sdk38"; +import { Encoding, isNonNullObject } from "@iov/encoding"; +import axios, { AxiosError, AxiosInstance } from "axios"; + +import { CosmosSdkTx, JsonObject, Model, parseWasmData, StdTx, WasmData } from "./types"; + +const { fromBase64, fromUtf8, toHex, toUtf8 } = Encoding; + +export interface CosmosSdkAccount { + /** Bech32 account address */ + readonly address: string; + readonly coins: ReadonlyArray; + /** Bech32 encoded pubkey */ + readonly public_key: string; + readonly account_number: number; + readonly sequence: number; +} + +export interface NodeInfo { + readonly protocol_version: { + readonly p2p: string; + readonly block: string; + readonly app: string; + }; + readonly id: string; + readonly listen_addr: string; + readonly network: string; + readonly version: string; + readonly channels: string; + readonly moniker: string; + readonly other: { + readonly tx_index: string; + readonly rpc_address: string; + }; +} + +export interface ApplicationVersion { + readonly name: string; + readonly server_name: string; + readonly client_name: string; + readonly version: string; + readonly commit: string; + readonly build_tags: string; + readonly go: string; +} + +export interface NodeInfoResponse { + readonly node_info: NodeInfo; + readonly application_version: ApplicationVersion; +} + +export interface BlockId { + readonly hash: string; + // TODO: here we also have this + // parts: { + // total: '1', + // hash: '7AF200C78FBF9236944E1AB270F4045CD60972B7C265E3A9DA42973397572931' + // } +} + +export interface BlockHeader { + readonly version: { + readonly block: string; + readonly app: string; + }; + readonly height: string; + readonly chain_id: string; + /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ + readonly time: string; + readonly last_commit_hash: string; + readonly last_block_id: BlockId; + /** Can be empty */ + readonly data_hash: string; + readonly validators_hash: string; + readonly next_validators_hash: string; + readonly consensus_hash: string; + readonly app_hash: string; + /** Can be empty */ + readonly last_results_hash: string; + /** Can be empty */ + readonly evidence_hash: string; + readonly proposer_address: string; +} + +export interface Block { + readonly header: BlockHeader; + readonly data: { + /** Array of base64 encoded transactions */ + readonly txs: ReadonlyArray | null; + }; +} + +export interface BlockResponse { + readonly block_id: BlockId; + readonly block: Block; +} + +interface AuthAccountsResponse { + readonly height: string; + readonly result: { + readonly type: "cosmos-sdk/Account"; + readonly value: CosmosSdkAccount; + }; +} + +// Currently all wasm query responses return json-encoded strings... +// later deprecate this and use the specific types for result +// (assuming it is inlined, no second parse needed) +type WasmResponse = WasmSuccess | WasmError; + +interface WasmSuccess { + readonly height: string; + readonly result: T; +} + +interface WasmError { + readonly error: string; +} + +export interface TxsResponse { + readonly height: string; + readonly txhash: string; + /** 🤷‍♂️ */ + readonly codespace?: string; + /** Falsy when transaction execution succeeded. Contains error code on error. */ + readonly code?: number; + readonly raw_log: string; + readonly logs?: object; + readonly tx: CosmosSdkTx; + /** The gas limit as set by the user */ + readonly gas_wanted?: string; + /** The gas used by the execution */ + readonly gas_used?: string; + readonly timestamp: string; +} + +interface SearchTxsResponse { + readonly total_count: string; + readonly count: string; + readonly page_number: string; + readonly page_total: string; + readonly limit: string; + readonly txs: readonly TxsResponse[]; +} + +export interface PostTxsResponse { + readonly height: string; + readonly txhash: string; + readonly code?: number; + readonly raw_log?: string; + /** The same as `raw_log` but deserialized? */ + readonly logs?: object; + /** The gas limit as set by the user */ + readonly gas_wanted?: string; + /** The gas used by the execution */ + readonly gas_used?: string; +} + +interface EncodeTxResponse { + // base64-encoded amino-binary encoded representation + readonly tx: string; +} + +export interface CodeInfo { + readonly id: number; + /** Bech32 account address */ + readonly creator: string; + /** Hex-encoded sha256 hash of the code stored here */ + readonly data_hash: string; + // TODO: these are not supported in current wasmd + readonly source?: string; + readonly builder?: string; +} + +export interface CodeDetails extends CodeInfo { + /** Base64 encoded raw wasm data */ + readonly data: string; +} + +// This is list view, without contract info +export interface ContractInfo { + readonly address: string; + readonly code_id: number; + /** Bech32 account address */ + readonly creator: string; + readonly label: string; +} + +export interface ContractDetails extends ContractInfo { + /** Argument passed on initialization of the contract */ + readonly init_msg: object; +} + +interface SmartQueryResponse { + // base64 encoded response + readonly smart: string; +} + +type RestClientResponse = + | NodeInfoResponse + | BlockResponse + | AuthAccountsResponse + | TxsResponse + | SearchTxsResponse + | PostTxsResponse + | EncodeTxResponse + | WasmResponse + | WasmResponse + | WasmResponse + | WasmResponse + | WasmResponse; + +/** Unfortunately, Cosmos SDK encodes empty arrays as null */ +type CosmosSdkArray = ReadonlyArray | null; + +function normalizeArray(backend: CosmosSdkArray): ReadonlyArray { + return backend || []; +} + +/** + * The mode used to send transaction + * + * @see https://cosmos.network/rpc/#/Transactions/post_txs + */ +export enum BroadcastMode { + /** Return after tx commit */ + Block = "block", + /** Return afer CheckTx */ + Sync = "sync", + /** Return right away */ + Async = "async", +} + +function isWasmError(resp: WasmResponse): resp is WasmError { + return (resp as WasmError).error !== undefined; +} + +function unwrapWasmResponse(response: WasmResponse): T { + if (isWasmError(response)) { + throw new Error(response.error); + } + return response.result; +} + +// We want to get message data from 500 errors +// https://stackoverflow.com/questions/56577124/how-to-handle-500-error-message-with-axios +// this should be chained to catch one error and throw a more informative one +function parseAxiosError(err: AxiosError): never { + // use the error message sent from server, not default 500 msg + if (err.response?.data) { + let errorText: string; + const data = err.response.data; + // expect { error: string }, but otherwise dump + if (data.error && typeof data.error === "string") { + errorText = data.error; + } else if (typeof data === "string") { + errorText = data; + } else { + errorText = JSON.stringify(data); + } + throw new Error(`${errorText} (HTTP ${err.response.status})`); + } else { + throw err; + } +} + +export class RestClient { + private readonly client: AxiosInstance; + private readonly broadcastMode: BroadcastMode; + + /** + * Creates a new client to interact with a Cosmos SDK light client daemon. + * This class tries to be a direct mapping onto the API. Some basic decoding and normalizatin is done + * but things like caching are done at a higher level. + * + * When building apps, you should not need to use this class directly. If you do, this indicates a missing feature + * in higher level components. Feel free to raise an issue in this case. + * + * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) + * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns + */ + public constructor(apiUrl: string, broadcastMode = BroadcastMode.Block) { + const headers = { + post: { "Content-Type": "application/json" }, + }; + this.client = axios.create({ + baseURL: apiUrl, + headers: headers, + }); + this.broadcastMode = broadcastMode; + } + + public async get(path: string): Promise { + const { data } = await this.client.get(path).catch(parseAxiosError); + if (data === null) { + throw new Error("Received null response from server"); + } + return data; + } + + public async post(path: string, params: any): Promise { + if (!isNonNullObject(params)) throw new Error("Got unexpected type of params. Expected object."); + const { data } = await this.client.post(path, params).catch(parseAxiosError); + if (data === null) { + throw new Error("Received null response from server"); + } + return data; + } + + // The /auth endpoints + + public async authAccounts(address: string): Promise { + const path = `/auth/accounts/${address}`; + const responseData = await this.get(path); + if ((responseData as any).result.type !== "cosmos-sdk/Account") { + throw new Error("Unexpected response data format"); + } + return responseData as AuthAccountsResponse; + } + + // The /blocks endpoints + + public async blocksLatest(): Promise { + const responseData = await this.get("/blocks/latest"); + if (!(responseData as any).block) { + throw new Error("Unexpected response data format"); + } + return responseData as BlockResponse; + } + + public async blocks(height: number): Promise { + const responseData = await this.get(`/blocks/${height}`); + if (!(responseData as any).block) { + throw new Error("Unexpected response data format"); + } + return responseData as BlockResponse; + } + + // The /node_info endpoint + + public async nodeInfo(): Promise { + const responseData = await this.get("/node_info"); + if (!(responseData as any).node_info) { + throw new Error("Unexpected response data format"); + } + return responseData as NodeInfoResponse; + } + + // The /txs endpoints + + public async txById(id: string): Promise { + const responseData = await this.get(`/txs/${id}`); + if (!(responseData as any).tx) { + throw new Error("Unexpected response data format"); + } + return responseData as TxsResponse; + } + + public async txsQuery(query: string): Promise { + const responseData = await this.get(`/txs?${query}`); + if (!(responseData as any).txs) { + throw new Error("Unexpected response data format"); + } + return responseData as SearchTxsResponse; + } + + /** returns the amino-encoding of the transaction performed by the server */ + public async encodeTx(tx: CosmosSdkTx): Promise { + const responseData = await this.post("/txs/encode", tx); + if (!(responseData as any).tx) { + throw new Error("Unexpected response data format"); + } + return Encoding.fromBase64((responseData as EncodeTxResponse).tx); + } + + /** + * Broadcasts a signed transaction to into the transaction pool. + * Depending on the RestClient's broadcast mode, this might or might + * wait for checkTx or deliverTx to be executed before returning. + * + * @param tx a signed transaction as StdTx (i.e. not wrapped in type/value container) + */ + public async postTx(tx: StdTx): Promise { + const params = { + tx: tx, + mode: this.broadcastMode, + }; + const responseData = await this.post("/txs", params); + if (!(responseData as any).txhash) { + throw new Error("Unexpected response data format"); + } + return responseData as PostTxsResponse; + } + + // The /wasm endpoints + + // wasm rest queries are listed here: https://github.com/cosmwasm/wasmd/blob/master/x/wasm/client/rest/query.go#L19-L27 + public async listCodeInfo(): Promise { + const path = `/wasm/code`; + const responseData = (await this.get(path)) as WasmResponse>; + return normalizeArray(unwrapWasmResponse(responseData)); + } + + // this will download the original wasm bytecode by code id + // throws error if no code with this id + public async getCode(id: number): Promise { + const path = `/wasm/code/${id}`; + const responseData = (await this.get(path)) as WasmResponse; + return unwrapWasmResponse(responseData); + } + + public async listContractsByCodeId(id: number): Promise { + const path = `/wasm/code/${id}/contracts`; + const responseData = (await this.get(path)) as WasmResponse>; + return normalizeArray(unwrapWasmResponse(responseData)); + } + + /** + * Returns null when contract was not found at this address. + */ + public async getContractInfo(address: string): Promise { + const path = `/wasm/contract/${address}`; + const response = (await this.get(path)) as WasmResponse; + return unwrapWasmResponse(response); + } + + // Returns all contract state. + // This is an empty array if no such contract, or contract has no data. + public async getAllContractState(address: string): Promise { + const path = `/wasm/contract/${address}/state`; + const responseData = (await this.get(path)) as WasmResponse>; + return normalizeArray(unwrapWasmResponse(responseData)).map(parseWasmData); + } + + // Returns the data at the key if present (unknown decoded json), + // or null if no data at this (contract address, key) pair + public async queryContractRaw(address: string, key: Uint8Array): Promise { + const hexKey = toHex(key); + const path = `/wasm/contract/${address}/raw/${hexKey}?encoding=hex`; + const responseData = (await this.get(path)) as WasmResponse; + const data = unwrapWasmResponse(responseData); + return data.length === 0 ? null : fromBase64(data[0].val); + } + + /** + * Makes a smart query on the contract and parses the reponse as JSON. + * Throws error if no such contract exists, the query format is invalid or the response is invalid. + */ + public async queryContractSmart(address: string, query: object): Promise { + const encoded = toHex(toUtf8(JSON.stringify(query))); + const path = `/wasm/contract/${address}/smart/${encoded}?encoding=hex`; + const responseData = (await this.get(path)) as WasmResponse; + const result = unwrapWasmResponse(responseData); + // By convention, smart queries must return a valid JSON document (see https://github.com/CosmWasm/cosmwasm/issues/144) + return JSON.parse(fromUtf8(fromBase64(result.smart))); + } +} diff --git a/packages/cosmwasm/src/signingcosmwasmclient.spec.ts b/packages/cosmwasm/src/signingcosmwasmclient.spec.ts new file mode 100644 index 00000000..753bb76e --- /dev/null +++ b/packages/cosmwasm/src/signingcosmwasmclient.spec.ts @@ -0,0 +1,229 @@ +import { Coin, Secp256k1Pen } from "@cosmwasm/sdk38"; +import { Sha256 } from "@iov/crypto"; +import { Encoding } from "@iov/encoding"; +import { assert } from "@iov/utils"; + +import { PrivateCosmWasmClient } from "./cosmwasmclient"; +import { RestClient } from "./restclient"; +import { SigningCosmWasmClient, UploadMeta } from "./signingcosmwasmclient"; +import { getHackatom, makeRandomAddress, pendingWithoutWasmd } from "./testutils.spec"; + +const { toHex } = Encoding; + +const httpUrl = "http://localhost:1317"; + +const faucet = { + 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", + pubkey: { + type: "tendermint/PubKeySecp256k1", + value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", + }, + address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", +}; + +describe("SigningCosmWasmClient", () => { + describe("makeReadOnly", () => { + it("can be constructed", async () => { + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); + expect(client).toBeTruthy(); + }); + }); + + describe("getHeight", () => { + it("always uses authAccount implementation", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); + + const openedClient = (client as unknown) as PrivateCosmWasmClient; + const blockLatestSpy = spyOn(openedClient.restClient, "blocksLatest").and.callThrough(); + const authAccountsSpy = spyOn(openedClient.restClient, "authAccounts").and.callThrough(); + + const height = await client.getHeight(); + expect(height).toBeGreaterThan(0); + + expect(blockLatestSpy).toHaveBeenCalledTimes(0); + expect(authAccountsSpy).toHaveBeenCalledTimes(1); + }); + }); + + describe("upload", () => { + it("works", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); + const wasm = getHackatom(); + const { + codeId, + originalChecksum, + originalSize, + compressedChecksum, + compressedSize, + } = await client.upload(wasm); + expect(originalChecksum).toEqual(toHex(new Sha256(wasm).digest())); + expect(originalSize).toEqual(wasm.length); + expect(compressedChecksum).toMatch(/^[0-9a-f]{64}$/); + expect(compressedSize).toBeLessThan(wasm.length * 0.5); + expect(codeId).toBeGreaterThanOrEqual(1); + }); + + it("can set builder and source", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); + const wasm = getHackatom(); + + const meta: UploadMeta = { + source: "https://crates.io/api/v1/crates/cw-nameservice/0.1.0/download", + builder: "confio/cosmwasm-opt:0.6.2", + }; + const { codeId } = await client.upload(wasm, meta); + + const codeDetails = await client.getCodeDetails(codeId); + expect(codeDetails.source).toEqual(meta.source); + expect(codeDetails.builder).toEqual(meta.builder); + }); + }); + + describe("instantiate", () => { + it("works with transfer amount", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); + const { codeId } = await client.upload(getHackatom()); + + const transferAmount: readonly Coin[] = [ + { + amount: "1234", + denom: "ucosm", + }, + { + amount: "321", + denom: "ustake", + }, + ]; + const beneficiaryAddress = makeRandomAddress(); + const { contractAddress } = await client.instantiate( + codeId, + { + verifier: faucet.address, + beneficiary: beneficiaryAddress, + }, + "My cool label", + "Let's see if the memo is used", + transferAmount, + ); + + const rest = new RestClient(httpUrl); + const balance = (await rest.authAccounts(contractAddress)).result.value.coins; + expect(balance).toEqual(transferAmount); + }); + + it("can instantiate one code multiple times", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); + const { codeId } = await client.upload(getHackatom()); + + const contractAddress1 = await client.instantiate( + codeId, + { + verifier: faucet.address, + beneficiary: makeRandomAddress(), + }, + "contract 1", + ); + const contractAddress2 = await client.instantiate( + codeId, + { + verifier: faucet.address, + beneficiary: makeRandomAddress(), + }, + "contract 2", + ); + expect(contractAddress1).not.toEqual(contractAddress2); + }); + }); + + describe("execute", () => { + it("works", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); + const { codeId } = await client.upload(getHackatom()); + + // instantiate + const transferAmount: readonly Coin[] = [ + { + amount: "233444", + denom: "ucosm", + }, + { + amount: "5454", + denom: "ustake", + }, + ]; + const beneficiaryAddress = makeRandomAddress(); + const { contractAddress } = await client.instantiate( + codeId, + { + verifier: faucet.address, + beneficiary: beneficiaryAddress, + }, + "amazing random contract", + undefined, + transferAmount, + ); + + // execute + const result = await client.execute(contractAddress, { release: {} }, undefined); + const wasmEvent = result.logs.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 rest = new RestClient(httpUrl); + const beneficiaryBalance = (await rest.authAccounts(beneficiaryAddress)).result.value.coins; + expect(beneficiaryBalance).toEqual(transferAmount); + const contractBalance = (await rest.authAccounts(contractAddress)).result.value.coins; + expect(contractBalance).toEqual([]); + }); + }); + + describe("sendTokens", () => { + it("works", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); + + // instantiate + const transferAmount: readonly Coin[] = [ + { + amount: "7890", + denom: "ucosm", + }, + ]; + const beneficiaryAddress = makeRandomAddress(); + + // no tokens here + const before = await client.getAccount(beneficiaryAddress); + expect(before).toBeUndefined(); + + // send + const result = await client.sendTokens(beneficiaryAddress, transferAmount, "for dinner"); + const [firstLog] = result.logs; + expect(firstLog).toBeTruthy(); + + // got tokens + const after = await client.getAccount(beneficiaryAddress); + assert(after); + expect(after.balance).toEqual(transferAmount); + }); + }); +}); diff --git a/packages/cosmwasm/src/signingcosmwasmclient.ts b/packages/cosmwasm/src/signingcosmwasmclient.ts new file mode 100644 index 00000000..bcdd29b0 --- /dev/null +++ b/packages/cosmwasm/src/signingcosmwasmclient.ts @@ -0,0 +1,283 @@ +import { Coin, coins, makeSignBytes } from "@cosmwasm/sdk38"; +import { Sha256 } from "@iov/crypto"; +import { Encoding } from "@iov/encoding"; +import pako from "pako"; + +import { isValidBuilder } from "./builder"; +import { Account, CosmWasmClient, GetNonceResult, PostTxResult } from "./cosmwasmclient"; +import { findAttribute, Log } from "./logs"; +import { BroadcastMode } from "./restclient"; +import { + MsgExecuteContract, + MsgInstantiateContract, + MsgSend, + MsgStoreCode, + StdFee, + StdSignature, +} from "./types"; + +export interface SigningCallback { + (signBytes: Uint8Array): Promise; +} + +export interface FeeTable { + readonly upload: StdFee; + readonly init: StdFee; + readonly exec: StdFee; + readonly send: StdFee; +} + +function prepareBuilder(buider: string | undefined): string { + if (buider === undefined) { + return ""; // normalization needed by backend + } else { + if (!isValidBuilder(buider)) throw new Error("The builder (Docker Hub image with tag) is not valid"); + return buider; + } +} + +const defaultFees: FeeTable = { + upload: { + amount: coins(25000, "ucosm"), + gas: "1000000", // one million + }, + init: { + amount: coins(12500, "ucosm"), + gas: "500000", // 500k + }, + exec: { + amount: coins(5000, "ucosm"), + gas: "200000", // 200k + }, + send: { + amount: coins(2000, "ucosm"), + gas: "80000", // 80k + }, +}; + +export interface UploadMeta { + /** The source URL */ + readonly source?: string; + /** The builder tag */ + readonly builder?: string; +} + +export interface UploadResult { + /** 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; + /** The ID of the code asigned by the chain */ + readonly codeId: number; + readonly logs: readonly Log[]; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; +} + +export interface InstantiateResult { + /** The address of the newly instantiated contract */ + readonly contractAddress: string; + readonly logs: readonly Log[]; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; +} + +export interface ExecuteResult { + readonly logs: readonly Log[]; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; +} + +export class SigningCosmWasmClient extends CosmWasmClient { + public readonly senderAddress: string; + + private readonly signCallback: SigningCallback; + private readonly fees: FeeTable; + + /** + * Creates a new client with signing capability to interact with a CosmWasm blockchain. This is the bigger brother of CosmWasmClient. + * + * This instance does a lot of caching. In order to benefit from that you should try to use one instance + * for the lifetime of your application. When switching backends, a new instance must be created. + * + * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) + * @param senderAddress The address that will sign and send transactions using this instance + * @param signCallback An asynchonous callback to create a signature for a given transaction. This can be implemented using secure key stores that require user interaction. + * @param customFees The fees that are paid for transactions + * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns + */ + public constructor( + apiUrl: string, + senderAddress: string, + signCallback: SigningCallback, + customFees?: Partial, + broadcastMode = BroadcastMode.Block, + ) { + super(apiUrl, broadcastMode); + this.anyValidAddress = senderAddress; + + this.senderAddress = senderAddress; + this.signCallback = signCallback; + this.fees = { ...defaultFees, ...(customFees || {}) }; + } + + public async getNonce(address?: string): Promise { + return super.getNonce(address || this.senderAddress); + } + + public async getAccount(address?: string): Promise { + return super.getAccount(address || this.senderAddress); + } + + /** Uploads code and returns a receipt, including the code ID */ + public async upload(wasmCode: Uint8Array, meta: UploadMeta = {}, memo = ""): Promise { + const source = meta.source || ""; + const builder = prepareBuilder(meta.builder); + + const compressed = pako.gzip(wasmCode, { level: 9 }); + const storeCodeMsg: MsgStoreCode = { + type: "wasm/store-code", + value: { + sender: this.senderAddress, + // eslint-disable-next-line @typescript-eslint/camelcase + wasm_byte_code: Encoding.toBase64(compressed), + source: source, + builder: builder, + }, + }; + const fee = this.fees.upload; + const { accountNumber, sequence } = await this.getNonce(); + const chainId = await this.getChainId(); + const signBytes = makeSignBytes([storeCodeMsg], fee, chainId, memo, accountNumber, sequence); + const signature = await this.signCallback(signBytes); + const signedTx = { + msg: [storeCodeMsg], + fee: fee, + memo: memo, + signatures: [signature], + }; + + const result = await this.postTx(signedTx); + const codeIdAttr = findAttribute(result.logs, "message", "code_id"); + return { + originalSize: wasmCode.length, + originalChecksum: Encoding.toHex(new Sha256(wasmCode).digest()), + compressedSize: compressed.length, + compressedChecksum: Encoding.toHex(new Sha256(compressed).digest()), + codeId: Number.parseInt(codeIdAttr.value, 10), + logs: result.logs, + transactionHash: result.transactionHash, + }; + } + + public async instantiate( + codeId: number, + initMsg: object, + label: string, + memo = "", + transferAmount?: readonly Coin[], + ): Promise { + const instantiateMsg: MsgInstantiateContract = { + type: "wasm/instantiate", + value: { + sender: this.senderAddress, + // eslint-disable-next-line @typescript-eslint/camelcase + code_id: codeId.toString(), + label: label, + // eslint-disable-next-line @typescript-eslint/camelcase + init_msg: initMsg, + // eslint-disable-next-line @typescript-eslint/camelcase + init_funds: transferAmount || [], + }, + }; + const fee = this.fees.init; + const { accountNumber, sequence } = await this.getNonce(); + const chainId = await this.getChainId(); + const signBytes = makeSignBytes([instantiateMsg], fee, chainId, memo, accountNumber, sequence); + + const signature = await this.signCallback(signBytes); + const signedTx = { + msg: [instantiateMsg], + fee: fee, + memo: memo, + signatures: [signature], + }; + + const result = await this.postTx(signedTx); + const contractAddressAttr = findAttribute(result.logs, "message", "contract_address"); + return { + contractAddress: contractAddressAttr.value, + logs: result.logs, + transactionHash: result.transactionHash, + }; + } + + public async execute( + contractAddress: string, + handleMsg: object, + memo = "", + transferAmount?: readonly Coin[], + ): Promise { + const executeMsg: MsgExecuteContract = { + type: "wasm/execute", + value: { + sender: this.senderAddress, + contract: contractAddress, + msg: handleMsg, + // eslint-disable-next-line @typescript-eslint/camelcase + sent_funds: transferAmount || [], + }, + }; + const fee = this.fees.exec; + const { accountNumber, sequence } = await this.getNonce(); + const chainId = await this.getChainId(); + const signBytes = makeSignBytes([executeMsg], fee, chainId, memo, accountNumber, sequence); + const signature = await this.signCallback(signBytes); + const signedTx = { + msg: [executeMsg], + fee: fee, + memo: memo, + signatures: [signature], + }; + + const result = await this.postTx(signedTx); + return { + logs: result.logs, + transactionHash: result.transactionHash, + }; + } + + public async sendTokens( + recipientAddress: string, + transferAmount: readonly Coin[], + memo = "", + ): Promise { + const sendMsg: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + // eslint-disable-next-line @typescript-eslint/camelcase + from_address: this.senderAddress, + // eslint-disable-next-line @typescript-eslint/camelcase + to_address: recipientAddress, + amount: transferAmount, + }, + }; + const fee = this.fees.send; + const { accountNumber, sequence } = await this.getNonce(); + const chainId = await this.getChainId(); + const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); + const signature = await this.signCallback(signBytes); + const signedTx = { + msg: [sendMsg], + fee: fee, + memo: memo, + signatures: [signature], + }; + + return this.postTx(signedTx); + } +} diff --git a/packages/cosmwasm/src/testdata/contract.json b/packages/cosmwasm/src/testdata/contract.json new file mode 100644 index 00000000..1b75f593 --- /dev/null +++ b/packages/cosmwasm/src/testdata/contract.json @@ -0,0 +1,4 @@ +{ + "// source": "https://github.com/CosmWasm/cosmwasm/blob/v0.8.0-alpha3/packages/vm/testdata/contract_0.8.wasm", + "data": "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RQ0FIAMtAIEBIQIMAgsgDUUEQCADQeABakGagsAAQQcQDiADQewBaigCACEGIANB6AFqKAIAIQQgAygC5AEhBSADKALgASECDAgLIANB4AFqIAgQdiADKALgASICQRZGDQsgAygC7AEhBiADKALoASEEIAMoAuQBIQUgEkUEQEEAIQcMIQsgDRDAAUEAIQcMIAtBACEBIANBADoA3AELIAJB/wFxIgRBIkcEQEETQRAgBEH9AEYbIQIMBAsgA0H4AGogBxB5IAMtAHhBAXFFDQIgAy0AeUEiRwRAQQ4hAgwECyAHEHMgA0HgAWogBxB4IAMoAuwBIQQgAygC6AEhBSADKALkASECIAMoAuABIgxBAUYEQCADKALwASEGDAQLAkAgBEEHRgRAQQAhCiACQZqCwABGDQEgAkGagsAAQQcQjwJFDQELQQEhCgsgBQRAIAIQwAELIARFIAxFIAJBFEdyckUEQCAFEMABCwJAAkACQCAKBEAgA0HgAWogBxB3IAMoAuABIgJBFkYNASADKALoASEEIAMoAuQBIQUgA0GMAmogAygC7AEiBjYCACADQYgCaiAENgIAIAMgBTYChAIgAyACNgKAAgwHCyANRQRAIANB4AFqIANB2AFqEBIgAygC4AFBAUcNAiADQfABaigCACEGIAMoAuwBIQQgAygC6AEhBSADKALkASECQQAhBwwiCyADQeABakGagsAAQQcQECADQewBaigCACEGIANB6AFqKAIAIQQgAygC5AEhBSADKALgASECDAcLIANBgAJqIAcQESADKAKAAiICQRZGDQEgA0GMAmooAgAhBiADQYgCaigCACEEIAMoAoQCIQUMBQsgAygC7AEhHCADKALoASESIAMoAuQBIQ0LIANB8ABqIAcQeUEAIQUgAy0AcSECIAMtAHBBAXENAAtBAiECDAILIANB4AFqQY2DwABBCBAQIANB7AFqKAIAIQYgA0HoAWooAgAMEgtBBCECCyANRQ0BCyASRQ0AIA0QwAFBACEHDBkLQQAhBwwYCyAcIQQgEgshBSABIQYMFgsgCw0CIANB4AFqIAgQdyADKALgASICQRZHDRAgA0HoAGogCBB5IAMtAGhBAXFFBEBBBCECDAoLIAMtAGlB+wBHBEBBDiECDAoLIAgQcyADQeAAaiAIEHEgAygCYCEBIAMgAy0AZEEBcSIOOgDcASADIAE2AtgBIANB2ABqIAEQeSADLQBYQQFxRQRAQQIhAgwJCyADLQBZIQJBACELIA4hBUEAIRACQANAAkACQAJAIAJB/wFxIgRBLEcEQCAEQf0ARg0BIA4NAkEJIQIMCgsgBUH/AXENASABEHMgA0HQAGogARB5IAMtAFBBAXFFDQggAy0AUSECDAILIAtFBEAgA0HgAWpBp4PAAEEGEA4gA0HsAWooAgAhBiADQegBaigCACEEIAMoAuQBIQUgAygC4AEhAkEADAoLIBBFBEAgA0HgAWpBrYPAAEEKEA4gA0HsAWooAgAhBiADQegBaigCACEEIAMoAuQBIQUgAygC4AEhAiAXRQ0NIAsQwAEMDQsgA0HgAWogCBB2IAMoAuABIgJBFkcEQCADKALsASEGIAMoAugBIQQgAygC5AEhBSAXBEAgCxDAAQsgGARAIBhBBXQhByAQQRBqIQEDQCABQQRqKAIABEAgASgCABDAAQsgAUEgaiEBIAdBYGoiBw0ACwsgDEUNDSAQEMABDA0LIBghESAMIRUgECEPIBchFAwEC0EAIQ4gA0EAOgDcAQsgAkH/AXEiBEEiRwRAQRAhAiAEQf0ARw0HQRMhAgwHCyADQcgAaiABEHkgAy0ASEEBcUUNBSADLQBJQSJHBEBBDiECDAcLIAEQcyADQeABaiABEHggAygC7AEhBCADKALoASEFIAMoAuQBIQIgAygC4AEiB0EBRgRAIAMoAvABIQYMBwsCQAJAIARBemoiCkEESw0AAkACQCAKQQFrDgQCAgIBAAtBACEKIAJBp4PAAEYNAiACQaeDwABBBhCPAkUNAgwBC0EBIQogAkGtg8AARg0BIAJBrYPAAEEKEI8CRQ0BC0ECIQoLIAUEQCACEMABCyAERSAHRSACQRRHcnJFBEAgBRDAAQsCQAJAAkACQAJAIApBAU0EQCAKQQFrDQEMAgsgA0HgAWogARB3IAMoAuABIgJBFkYNAiADKALoASEEIAMoAuQBIQUgA0GMAmogAygC7AEiBjYCACADQYgCaiAENgIAIAMgBTYChAIgAyACNgKAAgwLCyALRQRAIANB4AFqIANB2AFqEBIgAygC4AFBAUcNAyADQfABaigCACEGIAMoAuwBIQQgAygC6AEhBSADKALkASECQQAMDAsgA0HgAWpBp4PAAEEGEBAgA0HsAWooAgAhBiADQegBaigCACEEIAMoAuQBIQUgAygC4AEhAgwKCyAQDQQgA0HgAWogA0HYAWoQEyADKALgAUEBRwRAIAMoAuwBIRggAygC6AEhDCADKALkASEQDAMLIANB8AFqKAIAIQYgAygC7AEhBCADKALoASEFIAMoAuQBIQIgCyEJDAwLIANBgAJqIAEQESADKAKAAiICQRZGDQEgA0GMAmooAgAhBiADQYgCaigCACEEIAMoAoQCIQUMCAsgAygC7AEhHSADKALoASEXIAMoAuQBIQsLIANBQGsgARB5QQAhBSADLQBBIQIgAy0AQEEBcQ0AC0ECIQIMBQsgA0HgAWpBrYPAAEEKEBAgA0HsAWooAgAhBiADQegBaigCACEEIAMoAuQBIQUgAygC4AEhAiALIQkMBgsgAyAIEHlBACEHIAMtAAEhAiADLQAAQQFxDQALQQIhAgwRCyADQeABakGGg8AAQQcQECADQewBaigCACEGIANB6AFqKAIADAgLQQQhAgsgCwshCSAQRQ0BCyAYBEAgGEEFdCEHIBBBEGohAQNAIAFBBGooAgAEQCABKAIAEMABCyABQSBqIQEgB0FgaiIHDQALCyAMRQ0AIBAQwAELIAlFIBdFcg0AIAsQwAELQQAhC0EAIQcMCgsgFyEFIB0hBCAQIQYMCQsgA0HgAWpBgYPAAEEFEBAgA0HsAWooAgAhBiADQegBaigCAAshBCADKALkASEFIAMoAuABIQIMBwsgA0HsAWooAgAhBiADQegBaigCACEEIAMoAuQBIQUgAygC4AEhAgwBC0EEIQILIAxFIAlFcg0EIAkQwAEMBAsgEyEFIBkhBAwDCyADKALsASEGIAMoAugBIQQgAygC5AEhBQwCC0EEIQIMAQtBECECC0EACyEOIBJFIA1Fcg0AIA0QwAELAkAgByALRXINACAUBEAgCxDAAQsgEQRAIBFBBXQhByAPQRBqIQEDQCABQQRqKAIABEAgASgCABDAAQsgAUEgaiEBIAdBYGoiBw0ACwsgFUUNACAPEMABCyAWRSAOIBpFcnINACAaEMABCyADQYwCaiAGNgIAIANBiAJqIAQ2AgAgAyAFNgKEAiADIAI2AoACIANB4AFqQduFwABBGCADQYACahAUIABBHGogA0H4AWooAgA2AgAgAEEUaiADQfAB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ASACKAKsASEFIAJBhAFqIAIoArQBIgo2AgAgAkGAAWogATYCACACIAU2AnwgAiAENgJ4DA4LIANFBEAgAkGoAWogBhB3IAIoAqgBIgNBFkcEQCACKAK0ASEKIAIoArABIQkgAigCrAEhCAwQCyACQRBqIAYQeSACLQAQQQFxRQRAQQQhAwwMCyACLQARQSJHBEBBDiEDDAwLIAYQcyACQagBaiAGEHggAigCtAEhESACKAKwASEOIAIoAqwBIQMgAigCqAEiAUEBRgRAIAIoArgBIQogDiEIIBEhCQwQCyARRSABRSADQRRHcnINBSAOEMABDAULIAJBqAFqQYCCwABBBRAQDAMLIBZCAVIEQCACQagBaiAGEHcgAigCqAEiBEEWRwRAIAIoArQBIQogAigCsAEhASACKAKsASEFDA4LIAJBGGogBhB5IAItABhBAXFFBEBBBCEEDAMLIAItABlBIkcEQEEOIQQMAwsgBhBzIAJBqAFqIAYQeCACKAK0ASEBIAIoArABIQUgAigCrAEhBCACKAKoASIQQQFGBEAgAigCuAEhCgwOCyACIAE2AnQgAiAENgJwIAJB+ABqIAQgARCCAgJ/IAItAHhBAUcEQEEAIQcgAigCjAEhFSACKAKIASEKIAIoAoABIQggAigChAEMAQsgAiACLQB5OgCXASACQQI2ArwBIAJCAjcCrAEgAkGojcAANgKoASACQQM2AswBIAJBAjYCxAEgAiACQcABajYCuAEgAiACQZcBajYCyAEgAiACQfAAajYCwAEgAkGYAWogAkGoAWoQyQEgAkGoAWogAkGYAWoQygEgAigCnAEEQCACKAKYARDAAQsgAigCqAEhCCACKAKwASEKQQEhByACKAKsAQshCSAFBEAgBBDAAQsgAUUgEEUgBEEUR3JyRQRAIAUQwAELQgEhFiAHRQ0EQRQhBAwCCyACQagBakGhgMAAQQYQEAwCCyACQfgAaiAGEBEgAigCeCIEQRZGDQIgAkGEAWooAgAhCiACQYABaigCACEBIAIoAnwhBQwLCyAIIQUgCSEBDAoLIAJBtAFqKAIAIQogAkGwAWooAgAhASACKAKsASEFIAIoAqgBIQQMCQsgAkEIaiAGEHlBACEFIAItAAkhASACLQAIQQFxDQALC0ECIQcMBQsgAkGoAWogEhB1IAIoAqgBIgNB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AUEIaikCADcDACACIAEpAgA3AzgMBAsgAkHQAGogAkGIAWpBz4LAAEELEIUBIAIoAlBBAUYEQCACQUBrIAJB3ABqKQIANwMAIAIgAikCVDcDOAwECyACIAIoAlQ2AnggAiACQdgAai0AADoAfCACQdAAaiACQfgAakHagsAAQQkgAUEIahAFIAJB0ABqIQMCQCACKAJQQQNGBEAgAkHQAGogAkH4AGpBp4HAACABQRRqEAYgAigCUEEDRg0BCyACQUBrIANBCGopAgA3AwAgAiADKQIANwM4DAQLIAJBOGogAigCeCACLQB8EH0MAwsgAkHQAGogAkGIAWpBxYLAAEEKEIUBIAIoAlBBAUcNASACQUBrIAJB0ABqQQRyIgFBCGopAgA3AwAgAiABKQIANwM4DAILIAJBOGogAigCVCACQdgAai0AABB9DAELIAJBOGogAigCVCACQdgAai0AABB9CyACKAI4IgNBA0cEQCACQUBrKAIAIQEgAigCPCEEIAJB3ABqIAJBxABqKAIANgIAIAJB2ABqIAE2AgAgAiAENgJUIAIgAzYCUAwECwJAIAIoApABIgMgAigCjAFHBEAgAigCiAEhAQwBCyADQQFqIgEgA0kNBSADQQF0IgQgASAEIAFLGyIEQQBIDQUCfyADRQRAIARBARBADAELIAIoAogBIANBASAEEEELIgFFDQsgAiAENgKMASACIAE2AogBIAIoApABIQMLIAEgA2pB/QA6AABBAyEDIAJBAzYCUCACIAIoApABQQFqNgKQAQwDCwJAIAIoApABIgMgAigCjAFHBEAgAigCiAEhBAwBCyADQQFqIgQgA0kNBCADQQF0IgUgBCAFIARLGyIFQQBIDQQCfyADRQRAIAVBARBADAELIAIoAogBIANBASAFEEELIgRFDQkgAiAFNgKMASACIAQ2AogBCyADIARqQfsAOgAAIAIgAigCkAFBAWo2ApABIAJB0ABqIAJBiAFqQdyAwABBBBCBASACKAJQIgNBA0cEQCACQdgAaiIBKAIAIQQgAigCVCEFIAJB3ABqIgYgBigCADYCACABIAQ2AgAgAiAFNgJUIAIgAzYCUAwDCwJAIAIoApABIgMgAigCjAFHBEAgAigCiAEhBAwBCyADQQFqIgQgA0kNBCADQQF0IgUgBCAFIARLGyIFQQBIDQQCfyADRQRAIAVBARBADAELIAIoAogBIANBASAFEEELIgRFDQkgAiAFNgKMASACIAQ2AogBIAIoApABIQMLIAMgBGpBOjoAACACIAIoApABQQFqNgKQASABQRRqIQMgAUEIaiEEAkACQCABKAIEQQFHBEAgAkHQAGogAkGIAWpBtILAAEEFEIUBIAIoAlBBAUYEQCACQUBrIAJB3ABqKQIANwMAIAIgAikCVDcDOAwDCyACIAIoAlQ2AnggAiACQdgAai0AADoAfCACQdAAaiACQfgAakHPgMAAQQ0gBBAFIAIoAlBBA0YEQCACQdAAaiACQfgAakHAgMAAIAMQCSACKAJQQQNGDQILIAJBQGsgAkHQAGoiAUEIaikCADcDACACIAEpAgA3AzgMAgsgAkHQAGogAkGIAWpBsYLAAEEDEIUBIAIoAlBBAUYEQCACQUBrIAJB3ABqKQIANwMAIAIgAikCVDcDOAwCCyACIAIoAlQ2AnggAiACQdgAai0AADoAfCACQdAAaiACQfgAakHPgMAAQQ0gBBAFIAJB0ABqIQECQCACKAJQQQNGBEAgAkHQAGogAkH4AGpB5oHAACADEAkgAigCUEEDRg0BCyACQUBrIAFBCGopAgA3AwAgAiABKQIANwM4DAILIAJBOGogAigCeCACLQB8EH0MAQsgAkE4aiACKAJ4IAItAHwQfQsgAigCOCIDQQNHBEAgAkFAaygCACEBIAIoAjwhBCACQdwAaiACQcQAaigCADYCACACQdgAaiABNgIAIAIgBDYCVCACIAM2AlAMAwsCQCACKAKQASIDIAIoAowBRwRAIAIoAogBIQEMAQsgA0EBaiIBIANJDQQgA0EBdCIEIAEgBCABSxsiBEEASA0EAn8gA0UEQCAEQQEQQAwBCyACKAKIASADQQEgBBBBCyIBRQ0KIAIgBDYCjAEgAiABNgKIASACKAKQASEDCyABIANqQf0AOgAAQQMhAyACQQM2AlAgAiACKAKQAUEBajYCkAEMAgsgAkE4aiACKAJ4IAItAHwQfQsgAigCOCIDQQNHBEAgAkFAaygCACEBIAIoAjwhBCACQdwAaiACQcQAaigCADYCACACQdgAaiABNgIAIAIgBDYCVCACIAM2AlAMAQsCQCACKAKQASIDIAIoAowBRwRAIAIoAogBIQEMAQsgA0EBaiIBIANJDQIgA0EBdCIEIAEgBCABSxsiBEEASA0CAn8gA0UEQCAEQQEQQAwBCyACKAKIASADQQEgBBBBCyIBRQ0IIAIgBDYCjAEgAiABNgKIASACKAKQASEDCyABIANqQf0AOgAAQQMhAyACQQM2AlAgAiACKAKQAUEBajYCkAELIANBA0cEQCACQdwAaigCACEBIAJB2ABqKAIAIQcgAigCVCEEIAIoAowBBEAgAigCiAEQwAELIAJB3ABqIAE2AgAgAkHYAGoiASAHNgIAIAIgBDYCVCACIAM2AlAgAkGgAWpB+IbAAEE9IAJB0ABqEBYgAkHoAGogAkG4AWooAgA2AgAgAkHgAGogAkGwAWopAgA3AwAgASACQagBaikCADcDACACIAIpAqABNwNQIAJBzABqQQE2AgAgAkIBNwI8IAJB4ITAADYCOCACQQY2AnwgAiACQfgAajYCSCACIAJB0ABqNgJ4IAJBiAFqIAJBOGoQyQEQjgEhASACQcIAaiACQZABaigCADYBACACIAIpA4gBNwE6IAAgAToABSAAQQA6AAQgACACKQE4NwEGIABBDGogAkE+aikBADcBACAAQQE2AgAgAkHQAGoQHAwFCyACKAKMASENIAJB0ABqIAIoAogBIg4gAigCkAEQZiACKAJQIg9BAUcNASACQUBrIgEgAkHcAGopAgA3AwAgAiACKQJUNwM4IAJBtAFqQQE2AgAgAkIBNwKkASACQYCFwAA2AqABIAJBBzYCfCACIAJB+ABqNgKwASACIAJBOGo2AnggAkGIAWogAkGgAWoQyQEQjgEhByACQaoBaiACQZABaigCADYBACACIAIpA4gBNwGiASAAIAc6AAUgAEEAOgAEIAAgAikBoAE3AQYgAEEMaiACQaYBaikBADcBACAAQQE2AgACQCACKAI4IgBBAk0EQAJAAkAgAEEBaw4CAQMACyACQUBrKAIARQ0CIAIoAjwQwAEMAgsgAkFAaygCAEUNASACKAI8EMABDAELIAEoAgBFDQAgAigCPBDAAQtBASEDQQAMAgsQyAEACyACKAJUQQFGBEBBASEFIABBATYCACAAQRxqIAJB8ABqKAIANgIAIABBFGogAkHoAGopAwA3AgAgAEEMaiACQeAAaikDADcCACAAIAJB2ABqKQMANwIEQQEhA0EADAILIAJBgAFqIAJB4ABqKAIA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ASACKAIAQQNGDQIgACACKQMANwIAIABBCGogAkEIaikDADcCACACQRBqJAAPCyAEQQEQxwEACxDIAQALAkAgAigCAEEBRw0AIAJBCGooAgBFDQAgAigCBBDAAQsgARAoAAv/AQEBfwJAAkAgAC0AACIBQQdNBEACQAJAAkACQAJAAkAgAUEBaw4HAQIDBwQFBwALIABBCGooAgBFDQYMBwsgAEEIaigCAEUNBQwGCyAAQQhqKAIARQ0EDAULIABBCGooAgBFDQMMBAsgAEEIaigCAARAIABBBGooAgAQwAELIABBFGooAgBFDQIgAEEQaigCABDAAQ8LIABBCGooAgAEQCAAQQRqKAIAEMABCyAAQRRqKAIARQ0BIABBEGooAgAQwAEMAQsgAEEIaigCAARAIABBBGooAgAQwAELIABBFGooAgBFDQAgAEEQaigCABDAAQsPCyAAQQRqKAIAEMABCwwAQuSuwoWXm6WIEQszAQF/IwBBEGsiACQAIABBHzYCDCAAQZCPwAA2AgggAEEIakGIhcAAQQBBgI/AABC1AQALEQAgACgCACkDAEEBIAEQ9AELDAAgACgCACABEPUBCxEAIAAoAgAgACgCBCABEP8BCwMAAQvDAQEBfwJAIAAoAgAiAUECTQRAAkACQCABQQFrDgIDAQALIAAoAgQgAEEMaigCAARAIABBCGooAgAQwAELDQIgAEEYaigCAEUNAiAAQRRqKAIAEMABDwsgACgCBEEBRw0BIABBDGooAgAEQCAAQQhqKAIAEMABCyAAQRRqKAIAIgFFDQEgAEEYaigCAEUNASABEMABDAELIABBDGooAgAEQCAAQQhqKAIAEMABCyAAQRhqKAIARQ0AIABBFGooAgAQwAELC/sFAQV/IAAoAggiAQRAIAAoAgAiBCABQcgAbGohBQNAIAQiAUHIAGohBAJAIAEoAgAiAkECTQRAAkACQCACQQFrDgIDAQALIAFBCGooAgAEQCABKAIEEMABCyABQRRqKAIABEAgAUEQaigCABDAAQsgAUEkaigCACICBEAgAkEFdCECIAFBHGooAgBBEGohAwNAIANBBGooAgAEQCADKAIAEMABCyADQSBqIQMgAkFgaiICDQALCyABQSBqKAIARQ0CIAEoAhwQwAEMAgsgAUEIaigCACICQQJNBEACQAJAAkAgAkEBaw4CAQIACyABQRBqKAIABEAgAUEMaigCABDAAQsgAUEsaigCAEUNBCABQShqKAIAEMABDAQLIAFBEGooAgAEQCABQQxqKAIAEMABCyABQSxqKAIARQ0DIAFBKGooAgAQwAEMAwsgAUEQaigCAARAIAFBDGooAgAQwAELIAFBGGooAgAiAkUNAiABQRxqKAIARQ0CIAIQwAEMAgsgAUEQaigCAARAIAFBDGooAgAQwAELIAFBHGooAgAEQCABQRhqKAIAEMABCyABQTxqKAIARQ0BIAFBOGooAgAQwAEMAQsgAUEQaigCACECIAFBCGooAgBFBEAgAgRAIAEoAgwQwAELIAFBHGooAgAEQCABQRhqKAIAEMABCyABQSxqKAIAIgIEQCACQQV0IQIgAUEkaigCAEEQaiEDA0AgA0EEaigCAARAIAMoAgAQwAELIANBIGohAyACQWBqIgINAAsLIAFBKGooAgBFDQEgASgCJBDAAQwBCyACBEAgASgCDBDAAQsgAUEgaigCACICBEAgAkEFdCECIAFBGGooAgBBEGohAwNAIANBBGooAgAEQCADKAIAEMABCyADQSBqIQMgAkFgaiICDQALCyABQRxqKAIABEAgASgCGBDAAQsgAUEkaigCACICRQ0AIAFBKGooAgBFDQAgAhDAAQsgBCAFRw0ACwsgAEEEaigCAARAIAAoAgAQwAELC74EAQR/IwBBEGsiBCQAIAAoAgAhAAJAAkACQAJAAkACQCABQYABTwRAIARBADYCDCABQYAQSQ0BIAFBgIAESQRAIAQgAUE/cUGAAXI6AA4gBCABQQZ2QT9xQYABcjoADSAEIAFBDHZBD3FB4AFyOgAMQQMhAQwECyAEIAFBP3FBgAFyOgAPIAQgAUESdkHwAXI6AAwgBCABQQZ2QT9xQYABcjoADiAEIAFBDHZBP3FBgAFyOgANQQQhAQwDCwJAIAAoAggiAiAAQQRqKAIARwRAIAAoAgAhAwwBCyACQQFqIgMgAkkNBiACQQF0IgUgAyAFIANLGyIFQQBIDQYCfyACRQRAIAVBARBADAELIAAoAgAgAkEBIAUQQQsiA0UNAiAAIAM2AgAgAEEEaiAFNgIAIAAoAgghAgsgAiADaiABOgAAIAAgACgCCEEBajYCCAwDCyAEIAFBP3FBgAFyOgANIAQgAUEGdkEfcUHAAXI6AAxBAiEBDAELIAVBARDHAQALAkAgAEEEaigCACIDIABBCGooAgAiAmsgAU8EQCAAKAIAIQMMAQsgASACaiIFIAJJDQMgA0EBdCICIAUgAiAFSxsiAkEASA0DAn8gA0UEQCACQQEQQAwBCyAAKAIAIANBASACEEELIgNFDQIgACADNgIAIABBBGogAjYCACAAQQhqKAIAIQILIABBCGogASACajYCACACIANqIARBDGogARCOAhoLIARBEGokAEEADwsgAkEBEMcBAAsQyAEAC1oBAX8jAEEgayICJAAgAiAAKAIANgIEIAJBGGogAUEQaikCADcDACACQRBqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpBnIrAACACQQhqENUBIAJBIGokAAu/AQEDfwJAAkACQCAAKAIAIgBBBGooAgAiBCAAQQhqKAIAIgNrIAJPBEAgACgCACEEDAELIAIgA2oiBSADSQ0CIARBAXQiAyAFIAMgBUsbIgNBAEgNAgJ/IARFBEAgA0EBEEAMAQsgACgCACAEQQEgAxBBCyIERQ0BIAAgBDYCACAAQQRqIAM2AgAgAEEIaigCACEDCyAAQQhqIAIgA2o2AgAgAyAEaiABIAIQjgIaQQAPCyADQQEQxwEACxDIAQALoAEBA38CQAJAIAAoAggiASAAQQRqKAIARgR/IAFBAWoiAiABSQ0CIAFBAXQiAyACIAMgAksbIgJBAEgNAgJ/IAFFBEAgAkEBEEAMAQsgACgCACABQQEgAhBBCyIBRQ0BIAAgATYCACAAQQRqIAI2AgAgACgCCAUgAQsgACgCAGpBOjoAACAAIAAoAghBAWo2AggPCyACQQEQxwEACxDIAQALxwICAn8BfiMAQUBqIgIkACACIAE2AgQgAkEANgIQIAJCATcDCCACQQg2AhwgAiACQQRqNgIYIAIgAkEIajYCJCACQTxqQQE2AgAgAkIBNwIsIAJBnIXAADYCKCACIAJBGGo2AjgCQAJAIAJBJGpBnIrAACACQShqENUBRQRAIAIoAgwiAyACKAIQIgFHBEAgAyABSQ0CAkAgAUUEQCADBEAgAigCCBDAAQsgAkEBNgIIQQAhAQwBCyACKAIIIANBASABEEEiA0UNBCACIAM2AggLIAIgATYCDAsgAkEwaiACQRBqKAIAIgE2AgAgAiACKQMIIgQ3AyggAEEMaiABNgIAIAAgBDcCBCAAQRQ2AgAgAkFAayQADwtBpIXAAEE3IAJBKGpBtIrAABDfAQALQcyLwABBJEG8i8AAEM0BAAsgAUEBEMcBAAu5AQEBfyMAQUBqIgUkACAFIAI2AgwgBSABNgIIAkAgBARAIAVBNGpBCTYCACAFQSRqQQI2AgAgBUICNwIUIAVB/IzAADYCECAFQQI2AiwgBSAENgI8IAUgAzYCOCAFIAVBKGo2AiAgBSAFQThqNgIwDAELIAVBJGpBATYCACAFQgI3AhQgBUHgjMAANgIQIAVBAjYCLCAFIAVBKGo2AiALIAUgBUEIajYCKCAAIAVBEGoQKSAFQUBrJAAL9wIBBH8jAEEgayICJAAgAkEIaiABKAIAEHkCQAJAAkAgAi0ACEEBcUUEQEECIQMMAQsCQAJAIAItAAkiBCIDQf0ARwRAAkAgA0EsRgRAIAEtAARFDQEMAwsgAS0ABA0CQQkhAwwECyABKAIAEHMgAiABKAIAEHkgAi0AAEEBcUUEQEEEIQMMBAsgAi0AASEEDAILIABBADsBAAwECyABQQA6AAQLIARB/wFxIgNB/QBHBEAgA0EiRwRAQRAhAwwCCyACQRBqIAEoAgAQLCACKAIQIgNBFkYNAiACQRxqKAIAIQEgAkEYaigCACEEIAIoAhQhBQwBC0ETIQMLIABBAToAACAAQRBqIAE2AgAgAEEMaiAENgIAIABBCGogBTYCACAAQQRqIAM2AgAMAQsgAkEQaiABEC0gAigCEEEWRwRAIABBAToAACAAQQRqIAIpAxA3AgAgAEEMaiACQRhqKQMANwIADAELIABBgAI7AQALIAJBIGokAAvNAQEEfyMAQSBrIgIkACACIAEQeQJAIAItAABBAXEEQCACLQABQSJHBEAgAEEONgIADAILIAEQcyACQQhqIAEQeCACQRRqKAIAIQUgAkEQaigCACEBIAIoAgwhAyACKAIIIgRBAUYEQCACQRhqKAIAIQQgACABNgIEIAAgAzYCACAAQQxqIAQ2AgAgAEEIaiAFNgIADAILIABBFjYCACABBEAgAxDAAQsgBUUgBEUgA0EUR3JyDQEgARDAAQwBCyAAQQQ2AgALIAJBIGokAAtxAQN/IwBBEGsiAiQAIAIgASgCABB3AkAgAigCACIDQRZHBEAgAkEMaigCACEBIAJBCGooAgAhBCAAIAIoAgQ2AgQgACADNgIAIABBDGogATYCACAAQQhqIAQ2AgAMAQsgACABKAIAEBELIAJBEGokAAvKAgEDfyMAQSBrIgIkACACQQhqIAEoAgAQeUEBIQMCQAJAAkAgAi0ACEEBcQRAAkACQCACLQAJIgMiBEEsRwRAIARB3QBHDQFBACEDIABBADoAAQwGCyABKAIAEHMgAiABKAIAEHlBASEDIAItAABBAXFFDQMgAi0AASEDDAELIAEtAARFBEAgAEEEakEHNgIAQQEhAwwFCyABQQA6AAQLIANB3QBGDQIgAkEQaiABKAIAEBEgAigCECIBQRZHBEAgAkEYaigCACEDIAIoAhQhBCAAQRBqIAJBHGooAgA2AgAgAEEMaiADNgIAIABBCGogBDYCACAAQQRqIAE2AgBBASEDDAQLIABBAToAAUEAIQMMAwsgAEEEakEBNgIADAILIABBBGpBBDYCAAwBCyAAQQRqQRM2AgBBASEDCyAAIAM6AAAgAkEgaiQAC4wDAQV/IwBBIGsiAiQAIAIgARB5AkACQAJAIAItAABBAXEEQCACLQABQSJHBEAgAEEBOgAAIABBBGpBDjYCAAwECyABEHMgAkEIaiABEHggAkEUaigCACEDIAJBEGooAgAhBCACKAIMIQEgAigCCCIFQQFGBEAgAkEYaigCACEFIABBAToAACAAQRBqIAU2AgAgAEEMaiADNgIAIABBCGogBDYCACAAQQRqIAE2AgAMBAsgA0F4aiIGQQVLDQECQAJAIAZBAWsOBQMDAwMBAAsgAUGvj8AARwRAIAEpAABC9srJy+as2rLyAFINAwsgAEEAOwEADAMLIAFBtJDAAEcEQCABQbSQwABBDRCPAg0CCyAAQYACOwEADAILIABBAToAACAAQQRqQQQ2AgAMAgsgAkEIaiABIANBxJDAAEECECogAEEBOgAAIABBDGogAkEQaikDADcCACAAQQRqIAIpAwg3AgALIAQEQCABEMABCyADRSAFRSABQRRHcnINACAEEMABCyACQSBqJAALrwQBBX8jAEEgayICJAAgAiABEHkCQAJAAkAgAi0AAEEBcQRAIAItAAFBIkcEQCAAQQE6AAAgAEEEakEONgIADAQLIAEQcyACQQhqIAEQeCACQRRqKAIAIQMgAkEQaigCACEEIAIoAgwhASACKAIIIgVBAUYEQCACQRhqKAIAIQUgAEEBOgAAIABBEGogBTYCACAAQQxqIAM2AgAgAEEIaiAENgIAIABBBGogATYCAAwECyADQXtqIgZBEEsNAQJAAkACQAJAAkACQCAGQQFrDhAHAAEHBwMCBwcHBwcHBwcEBQsgAUHsjcAARwRAIAFB7I3AAEEHEI8CDQcLIABBADsBAAwHCyABQciPwABHBEAgASkAAELj4NX7xe3bt/AAUg0GCyAAQYACOwEADAYLIAFB0I/AAEcEQCABQdCPwABBDBCPAg0FCyAAQYAEOwEADAULIAFB3I/AAEcEQCABQdyPwABBCxCPAg0ECyAAQYAGOwEADAQLIAFB54/AAEcEQCABQeePwABBFRCPAg0DCyAAQYAIOwEADAMLIAFB/I/AAEcEQCABQfyPwABBBRCPAg0CCyAAQYAKOwEADAILIABBAToAACAAQQRqQQQ2AgAMAgsgAkEIaiABIANBhJDAAEEGECogAEEBOgAAIABBDGogAkEQaikDADcCACAAQQRqIAIpAwg3AgALIAQEQCABEMABCyADRSAFRSABQRRHcnINACAEEMABCyACQSBqJAAL4xABBH8jAEEwayIDJAACQAJAAkACQAJAAkACQAJAAkAgASgCCCIEIAFBBGooAgBHBEAgASgCACEFDAELIARBAWoiBSAESQ0HIARBAXQiBiAFIAYgBUsbIgZBAEgNBwJ/IARFBEAgBkEBEEAMAQsgASgCACAEQQEgBhBBCyIFRQ0BIAEgBTYCACABQQRqIAY2AgAgASgCCCEECyAEIAVqQfsAOgAAIAEgASgCCEEBajYCCCADQRhqIAFBjI3AAEEDEIEBIAMoAhgiBEEDRwRAIANBJGooAgAhASADQSBqKAIAIQIgACADKAIcNgIEIAAgBDYCACAAQQxqIAE2AgAgAEEIaiACNgIADAgLAkAgASgCCCIEIAFBBGooAgBHBEAgASgCACEFDAELIARBAWoiBSAESQ0HIARBAXQiBiAFIAYgBUsbIgZBAEgNBwJ/IARFBEAgBkEBEEAMAQsgASgCACAEQQEgBhBBCyIFRQ0CIAEgBTYCACABQQRqIAY2AgAgASgCCCEECyAEIAVqQTo6AAAgASABKAIIQQFqNgIIAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAi0AAEEBaw4IAQIDBAUGBwgACyADQRhqIAFBuoTAAEELEIUBIAMoAhhBAUcNCCADQQhqIANBJGopAgA3AwAgAyADKQIcNwMADBQLIANBGGogAUGshMAAQQ4QhQEgAygCGEEBRw0IIANBCGogA0EkaikCADcDACADIAMpAhw3AwAMEwsgA0EYaiABQaCEwABBDBCFASADKAIYQQFHDQggA0EIaiADQSRqKQIANwMAIAMgAykCHDcDAAwSCyADQRhqIAFBk4TAAEEJEIUBIAMoAhhBAUcNCCADQQhqIANBJGopAgA3AwAgAyADKQIcNwMADBELIANBGGogAUGHhMAAQQwQhQEgAygCGEEBRw0IIANBCGogA0EYakEEciICQQhqKQIANwMAIAMgAikCADcDAAwQCyADQRhqIAFB+IPAAEEJEIUBIAMoAhhBAUYEQCADQQhqIANBJGopAgA3AwAgAyADKQIcNwMADBALIAMgAygCHDYCECADIANBIGotAAA6ABQgA0EYaiADQRBqQYGEwABBBiACQQRqEAUgA0EYaiEEAkAgAygCGEEDRgRAIANBGGogA0EQakHAgMAAQQMgAkEQahAFIAMoAhhBA0YNAQsgA0EIaiAEQQhqKQIANwMAIAMgBCkCADcDAAwQCyADIAMoAhAgAy0AFBB9DA8LIANBGGogAUHlg8AAQQ0QhQEgAygCGEEBRgRAIANBCGogA0EkaikCADcDACADIAMpAhw3AwAMDwsgAyADKAIcNgIQIAMgA0Egai0AADoAFCADQRhqIANBEGpB8oPAAEEGIAJBBGoQBSADQRhqIQQCQCADKAIYQQNGBEAgA0EYaiADQRBqQcCAwABBAyACQRBqEAUgAygCGEEDRg0BCyADQQhqIARBCGopAgA3AwAgAyAEKQIANwMADA8LIAMgAygCECADLQAUEH0MDgsgA0EYaiABQdmDwABBDBCFASADKAIYQQFHDQYgA0EIaiADQRhqQQRyIgJBCGopAgA3AwAgAyACKQIANwMADA0LIANBGGogAUG/g8AAQQkQhQEgAygCGEEBRgRAIANBCGogA0EkaikCADcDACADIAMpAhw3AwAMDQsgAyADKAIcNgIQIAMgA0Egai0AADoAFCADQRhqIANBEGpByIPAAEEHIAJBBGoQBSADQRhqIQQCQCADKAIYQQNGBEAgA0EYaiADQRBqQc+DwABBCiACQRBqEAUgAygCGEEDRg0BCyADQQhqIARBCGopAgA3AwAgAyAEKQIANwMADA0LIAMgAygCECADLQAUEH0MDAsgAyADKAIcNgIQIAMgA0Egai0AADoAFCADQRhqIANBEGpBwIDAAEEDIAJBBGoQBSADKAIYQQNGDQUgA0EIaiADQSBqKQMANwMAIAMgAykDGDcDAAwLCyADIAMoAhw2AhAgAyADQSBqLQAAOgAUIANBGGogA0EQakHAgMAAQQMgAkEEahAFIAMoAhhBA0YNCSADQQhqIANBIGopAwA3AwAgAyADKQMYNwMADAoLIAMgAygCHDYCECADIANBIGotAAA6ABQgA0EYaiADQRBqQcCAwABBAyACQQRqEAUgAygCGEEDRg0HIANBCGogA0EgaikDADcDACADIAMpAxg3AwAMCQsgAyADKAIcNgIQIAMgA0Egai0AADoAFCADQRhqIANBEGpBnITAAEEEIAJBBGoQBSADKAIYQQNGDQUgA0EIaiADQSBqKQMANwMAIAMgAykDGDcDAAwICyADIAMoAhwgA0Egai0AABB9DAcLIAMgAygCHCADQSBqLQAAEH0MBgsgAyADKAIQIAMtABQQfQwFCyAGQQEQxwEACyAGQQEQxwEACyADIAMoAhAgAy0AFBB9DAILIAMgAygCECADLQAUEH0MAQsgAyADKAIQIAMtABQQfQsgAygCACICQQNHBEAgA0EMaigCACEBIANBCGooAgAhBCAAIAMoAgQ2AgQgACACNgIAIABBDGogATYCACAAQQhqIAQ2AgAMAgsCQAJAIAEoAggiBCABQQRqKAIARwRAIAEoAgAhBQwBCyAEQQFqIgIgBEkNAiAEQQF0IgUgAiAFIAJLGyICQQBIDQICfyAERQRAIAJBARBADAELIAEoAgAgBEEBIAIQQQsiBUUNASABIAU2AgAgAUEEaiACNgIAIAEoAgghBAsgBCAFakH9ADoAACAAQQM2AgAgASABKAIIQQFqNgIIDAILIAJBARDHAQALEMgBAAsgA0EwaiQACx0AIAEoAgBFBEAACyAAQdCNwAA2AgQgACABNgIAC1UBAn8gASgCACECIAFBADYCAAJAIAIEQCABKAIEIQNBCEEEEEAiAUUNASABIAM2AgQgASACNgIAIABB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AnwhBiADQZgBaiACQQxqIggQZCADQZgBakEEciEEIAMoApgBQQFGBEAgAEEBNgIAIAAgBCkCADcCBCAAQRxqIARBGGooAgA2AgAgAEEUaiAEQRBqKQIANwIAIABBDGogBEEIaikCADcCACAFRQ0BIAYQwAEMAQsgA0HkAGogBEEIaigCADYCACADQfAAaiABQShqKAIANgIAIAMgBzYCWCADIAU2AlQgAyAGNgJQIAMgBCkCADcCXCADIAEpAiA3A2ggA0G4AWoQfyADQdABaiADQbgBahCEAQJAIAMoAtABQQFGBEAgA0H4AWogA0HcAWopAgA3AwAgAyADKQLUATcD8AEMAQsgAyADKALUATYCyAEgAyADQdgBai0AADoAzAEgA0HQAWogA0HIAWpBr4/AAEEIIANB0ABqEDcgA0HQAWohBAJAIAMoAtABQQNHDQAgA0HQAWogA0HIAWpBt4/AAEELIANB3ABqEDcgAygC0AFBA0cNACADQdABaiADQcgBakHCj8AAQQYgA0HoAGoQNyADKALQAUEDRw0AIANB8AFqIAMoAsgBIAMtAMwBEHwMAQsgA0H4AWogBEEIaikCADcDACADIAQpAgA3A/ABCwJ/IAMoAvABIgRBA0cEQCADQfgBaigCACEFIANB/AFqKAIAIQYgAygC9AEhByADKAK8AQRAIAMoArgBEMABCyADQfwBaiAGNgIAIANB+AFqIAU2AgAgAyAHNgL0ASADIAQ2AvABIANB0AFqQZiJwABBGSADQfABahAWIANByABqIANB5AFqKQIANwMAIAMgA0HcAWopAgA3A0AgA0HYAWooAgAhBSADKALQASEEQQEhBiADKALUAQwBCyADKALAASEFIAMoArgBIQRBACEGIAMoArwBCyEHIANBLGoiCSAFNgIAIANBKGogBzYCACADQTBqIAMpA0A3AwAgA0E4aiADQcgAaikDADcDACADIAQ2AiQgAyAGNgIgAkAgBkUEQCADQeCNwABBBiAEIAUQYyADLQAAQQlHDQEgBwRAIAQQwAELIAMoAlQEQCADKAJQEMABCyADQeAAaigCAARAIAMoAlwQwAELIANB7ABqKAIABEAgAygCaBDAAQsgAEEYakIANwIAIABBEGpCBDcCACAAQQhqQgA3AgAgAEKAgICAgAE3AgAgAkEEaigCAARAIAIoAgAQwAELIAJBEGooAgAEQCAIKAIAEMABCyABQRRqKAIABEAgASgCEBDAAQsgAUE0aigCACIABEAgAEEFdCECIAFBLGooAgBBEGohAANAIABBBGooAgAEQCAAKAIAEMABCyAAQSBqIQAgAkFgaiICDQALCyABQTBqKAIABEAgASgCLBDAAQsgAUE8aigCAEUNBCABKAI4EMABDAQLIAAgAykCJDcCBCAAQQE2AgAgAEEcaiADQTxqKAIANgIAIABBFGogA0E0aikCADcCACAAQQxqIAkpAgA3AgAgAygCVARAIAMoAlAQwAELIANB4ABqKAIABEAgAygCXBDAAQsgA0HsAGooAgAEQCADKAJoEMABCyACQQRqKAIABEAgAigCABDAAQsgAkEQaigCAARAIAgoAgAQwAELIAFBFGooAgBFDQIgASgCEBDAAQwCCyAAIAMpAwA3AgQgAEEBNgIAIABBHGogA0EYaigCADYCACAAQRRqIANBEGopAwA3AgAgAEEMaiADQQhqKQMANwIAIAcEQCAEEMABCyADKAJUBEAgAygCUBDAAQsgA0HgAGooAgAEQCADKAJcEMABCyADQewAaigCAARAIAMoAmgQwAELIAJBBGooAgAEQCACKAIAEMABCyACQRBqKAIABEAgCCgCABDAAQsgAUEUaigCAEUNASABKAIQEMABDAELIAJBBGooAgAEQCACKAIAEMABCyACQRBqKAIABEAgAigCDBDAAQsgAUEUaigCAARAIAEoAhAQwAELIAFBJGooAgBFDQAgASgCIBDAAQsgAUE0aigCACIABEAgAEEFdCECIAFBLGooAgBBEGohAANAIABBBGooAgAEQCAAKAIAEMABCyAAQSBqIQAgAkFgaiICDQALCyABQTBqKAIABEAgASgCLBDAAQsgAUE8aigCAEUNACABKAI4EMABCyADQYACaiQAC+4YAgl/A34jAEHAAmsiAyQAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCACQf8BcUEBaw4FAgMEBQABCxAeAAsgA0EQaiABQcgAEI4CGiADQYABahBiIAMoAoABQQFHDQkgAEEBNgIAIAAgA0GAAWpBBHIiASkCADcCBCAAQRxqIAFBGGooAgA2AgAgAEEUaiABQRBqKQIANwIAIABBDGogAUEIaikCADcCAAwKCxA6AAsCQANAIAMgDDcD+AEgAyADQfgBajYCyAEgA0EANgKgAiADQgE3A5gCIANBCjYCXCADIANByAFqNgJYIAMgA0GYAmo2AtgBIANBATYClAEgA0IBNwKEASADQZyFwAA2AoABIAMgA0HYAGo2ApABIANB2AFqQZyKwAAgA0GAAWoQ1QENBQJAIAMoApwCIgIgAygCoAIiBEYEQCADKAKYAiEFDAELIAIgBEkNByAERQRAIAIEQCADKAKYAhDAAQsgA0IBNwOYAkEAIQJBASEFDAELIAMoApgCIAJBASAEEEEiBUUNCCADIAQ2ApwCIAMgBTYCmAIgBCECCyADQRBqQaSOwABBCCAFIAMoAqACEGMgAy0AEEEJRw0BIAIEQCAFEMABCyADKQP4ASINQgF8IgwgDVoNAAtBgIvAAEEcQayOwAAQzQEACyAAIAMpAxA3AgQgAEEBNgIAIABBHGogA0EoaigCADYCACAAQRRqIANBIGopAwA3AgAgAEEMaiADQRhqKQMANwIAIAJFDQIgBRDAAQwCCxA7AAtBwAxAAEF/RwRAIABCgICAgIABNwIAIABBGGpCADcCACAAQRBqQgQ3AgAgAEEIakIANwIADAELQSBBARBAIgJFDQQgAkEYakH4jsAAKQAANwAAIAJBEGpB8I7AACkAADcAACACQQhqQeiOwAApAAA3AAAgAkHgjsAAKQAANwAAEI4BIQQgAEEMakKggICAgAQ3AgAgAEEIaiACNgIAIAAgBDoABSAAQQA6AAQgAEEBNgIAIABBFGogAykCmAI3AgAgAEEcaiADQaACaigCADYCAAsgAUEUaigCAARAIAEoAhAQwAELIAFBJGooAgAEQCABKAIgEMABCyABQSxqKAIAIQAgAUE0aigCACICBEAgAkEFdCEEIABBEGohAgNAIAJBBGooAgAEQCACKAIAEMABCyACQSBqIQIgBEFgaiIEDQALCyABQTBqKAIABEAgABDAAQsgAUE8aigCAEUNBiABKAI4EMABDAYLQaSFwABBNyADQYABakG0isAAEN8BAAtBzIvAAEEkQbyLwAAQzQEACyAEQQEQxwEAC0EgQQEQxwEACwJ/AkAgAygChAEiAkUEQEEBIQFBBSEGQQVBARBAIgQNAUEFQQEQxwEACyACQQh2IQUgA0GIAWooAgAhBCADQYwBaigCACEGQQAhASACQYCAfHEMAQsgBEEEakGCjsAALQAAOgAAIARB/o3AACgAADYAABCOASEFIANB4ABqIANBoAJqKAIANgIAIAMgAykCmAI3A1hBAyECQQALIQcgA0GAAmoiCCADQeAAaigCADYCACADIAMpA1g3A/gBIAJB/wFxIAVBCHRBgP4DcXIgB3IhBSABBEAgACAFNgIEIABBATYCACAAQRBqQQU2AgAgAEEMaiAGNgIAIABBCGogBDYCACAAQRRqIAMpA/gBNwIAIABBHGogCCgCADYCAAwBCyADQYABaiAFIAYQFSADQYABakEEciEBIAMoAoABQQFGBEAgAEEBNgIAIAAgASkCADcCBCAAQRxqIAFBGGooAgA2AgAgAEEUaiABQRBqKQIANwIAIABBDGogAUEIaikCADcCACAERQ0BIAUQwAEMAQsgA0H4AGogAUEgaigCADYCACADQfAAaiABQRhqKQIANwMAIANB6ABqIAFBEGopAgA3AwAgA0HgAGogAUEIaikCACIMNwMAIAMgASkCACINNwNYAkACQAJAAkAgA0E4aigCACIBIAynRgRAIAMoAjAiAiANpyIGRg0BIAIgBiABEI8CRQ0BCxCOASEBIANBgAFqIgJBBzoAACACIAE6AAEgAEEBNgIAIABBHGogA0GYAWooAgA2AgAgAEEUaiADQZABaikDADcCACAAQQxqIANBiAFqKQMANwIAIAAgAykDgAE3AgQMAQsgA0GAAWogA0HkAGoQZSADQYABakEEciEBIAMoAoABQQFGBEAgAEEBNgIAIAAgASkCADcCBCAAQRxqIAFBGGooAgA2AgAgAEEUaiABQRBqKQIANwIAIABBDGogAUEIaikCADcCAAwDCyADQbABaiABQQhqIgIoAgA2AgAgAyABKQIANwOoASADQYABaiADQcgAahBlIAMoAoABQQFGBEAgAEEBNgIAIAAgASkCADcCBCAAQRxqIAFBGGooAgA2AgAgAEEUaiABQRBqKQIANwIAIABBDGogAikCADcCAAwCCyADQcABaiACKAIANgIAIAMgASkCADcDuAEgA0GAAWogA0G4AWoQygEgA0GgAmogAykCgAE3AwAgA0GoAmogA0GIAWooAgA2AgAgA0KAgICAEDcDmAIgA0GAAWogA0GYAmoQGiADQYABakEEciEBIAMoAoABQQFGBEAgA0GQAmoiAiABQRhqKAIANgIAIANBiAJqIgYgAUEQaikCADcDACADQYACaiIHIAFBCGopAgA3AwAgAyABKQIANwP4ASADQZgCahAjIANB8AFqIAIoAgAiATYCACADQegBaiAGKQMAIgw3AwAgA0HgAWogBykDACINNwMAIAMgAykD+AEiDjcD2AEgAEEcaiABNgIAIABBFGogDDcCACAAQQxqIA03AgAgACAONwIEIABBATYCACADKAK8AUUNAiADKAK4ARDAAQwCCyADQYACaiICIAFBCGooAgA2AgAgAyABKQIANwP4ASADQZgCahAjIANB4AFqIAIpAwAiDDcDACADQdABaiAMPgIAIAMgAykD+AE3A8gBAkBBMEEEEEAiAQRAIANBmAJqQeaNwABBBkHsjcAAQQcQXCADQQhqIANBqAFqEF4gA0GAAWpB843AAEELIAMoAgggAygCDBBcIAFBEGogA0GoAmoiBikDADcCACABQQhqIANBoAJqKQMANwIAIAEgAykDmAI3AgAgASADKQOAATcCGCABQSBqIANBiAFqKQMANwIAIAFBKGogA0GQAWoiBykDADcCAEHIAEEIEEAiAkUNASADQYgBaiIIIANBwAFqKAIANgIAIANBlAFqIANBsAFqKAIANgIAIANBsAJqIgkgAykDyAE3AwAgA0G4AmoiCiADQdABaigCADYCACADIAMpA6gBNwKMASADQaACaiILIAgpAwA3AwAgBiAHKQMANwMAIAMgAykDuAE3A5gCIAJBADYCACACIAMpA5gCNwIEIAJBDGogCykDADcCACACQRRqIAYpAwA3AgAgAkEcaiAJKQMANwIAIAJBJGogCigCADYCACAAQRxqQQA2AgAgAEEUakKCgICAIDcCACAAQRBqIAE2AgAgAEEIakKBgICAEDcCACAAIAI2AgQgAEEANgIADAILQTBBBBDHAQALQcgAQQgQxwEACyADKAJcBEAgAygCWBDAAQsgA0HoAGooAgAEQCADKAJkEMABCyADQfQAaigCAARAIAMoAnAQwAELIAQEQCAFEMABCyADQSRqKAIABEAgAygCIBDAAQsgA0E0aigCAARAIAMoAjAQwAELIANBxABqKAIAIgAEQCAAQQV0IQQgA0E8aigCAEEQaiECA0AgAkEEaigCAARAIAIoAgAQwAEL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An8gBEUEQCAFQQEQQAwBCyABKAIAIARBASAFEEELIgNFDQcgASADNgIAIAFBBGogBTYCACABQQhqKAIAIQQLIAMgBGpBIjoAACABQQhqIgMgAygCAEEBaiIENgIAAkAgAUEEaigCACIDIARrQQZPBEAgASgCACEDDAELIARBBmoiBSAESQ0KIANBAXQiBCAFIAQgBUsbIgRBAEgNCgJ/IANFBEAgBEEBEEAMAQsgASgCACADQQEgBBBBCyIDRQ0IIAEgAzYCACABQQRqIAQ2AgAgAUEIaigCACEECyABQQhqIgUgBEEGajYCACADIARqIgNBBGpBpYDAAC8AADsAACADQaGAwAAoAAA2AAACQCABQQRqKAIAIgMgBSgCACIEa0ECTwRAIAEoAgAhAwwBCyAEQQJqIgUgBEkNCiADQQF0IgQgBSAEIAVLGyIEQQBIDQoCfyADRQRAIARBARBADAELIAEoAgAgA0EBIAQQQQsiA0UNCSABIAM2AgAgAUEEaiAENgIAIAFBCGooAgAhBAsgAUEIaiAEQQJqNgIAIAMgBGpBovQAOwAAIAJBKGogAkHQAGogARALIAIoAigiA0EDRw0KIAIgAUEAEHwMCwsgAEEBNgIAIAAgAkEoakEEciIDKQIANwIEIABBHGogA0EYaigCADYCACAAQRRqIANBEGopAgA3AgAgAEEMaiADQQhqKQIANwIADA4LIAZBARDHAQALIAZBARDHAQALIANBARDHAQALIANBARDHAQALIAVBARDHAQALIAVBARDHAQALIARBARDHAQALIARBARDHAQALEMgBAAsgAkEwaigCACEBIAIoAiwhBCACQQxqIAJBNGooAgA2AgAgAkEIaiABNgIAIAIgBDYCBCACIAM2AgALIAACfyACKAIAIgFBA0cEQCACQQxqKAIAIQMgAkEIaigCACEEIAIoAgQhBSACKAKUAQRAIAIoApABEMABCyACQQxqIAM2AgAgAkEIaiAENgIAIAIgBTYCBCACIAE2AgAgAkEoakHyicAAQScgAhAWIAJB+ABqIAJBPGopAgAiDDcDACACIAJBNGopAgAiDTcDcCACKQMoIQ4gAEEMaiACQTBqKAIANgIAIAAgDjcCBCAAQRBqIA03AgAgAEEYaiAMNwIAQQEMAQsgAikDkAEhDCAAQQxqIAIoApgBNgIAIAAgDDcCBEEACzYCACACKAJQIQMgAigCWCIABEAgAEEFdCEBIANBEGohAANAIABBBGooAgAEQCAAKAIAEMABCyAAQSBqIQAgAUFgaiIBDQALCyACKAJURQ0EIAMQwAEMBAsgAkEwaigCACEEIAIoAiwhBSACQZwBaiACQTRqKAIANgIAIAJBmAFqIAQ2AgAgAiAFNgKUASACIAM2ApABCwJAIAIoApABIgNBA0cEQCACQZwBaigCACEEIAJBmAFqKAIAIQUgAigClAEhBiACKAJkRQ0BIAIoAmAQwAEMAQsgAkHYAGogAkH4AGopAwA3AwAgAiACKQNwNwNQIAIpA2AhDCAAQQxqIAIoAmg2AgAgACAMNwIEIABBADYCACACKAIEBEAgAigCABDAAQsgAkEQaigCAARAIAIoAgwQwAELIAJBHGooAgAEQCACKAIYEMABCyAHBEAgCBDAAQsgCkUNAiAJEMABDAILIAJBnAFqIAQ2AgAgAkGYAWogBTYCACACIAY2ApQBIAIgAzYCkAEgAkEoakHOicAAQSQgAkGQAWoQFiACQdgAaiACQTxqKQIAIgw3AwAgAiACQTRqKQIAIg03A1AgAikDKCEOIABBDGogAkEwaigCADYCACAAIA43AgQgAEEQaiANNwIAIABBGGogDDcCACAAQQE2AgAgAigCBARAIAIoAgAQwAELIAJBEGooAgAEQCACKAIMEMABCyACQRxqKAIABEAgAigCGBDAAQsgBwRAIAgQwAELIApFDQEgCRDAAQwBCyAHRQ0AIAgQwAELIAEoAgAiAEUNACABQQRqKAIARQ0AIAAQwAELIAJBsAFqJAALpR4CG38DfiMAQZACayICJAAgAkHwAGogABBpIAJB8ABqQQRyIQACQCACKAJwQQFGBEAgAkHkAGogAEEYaigCADYCACACQdwAaiAAQRBqKQIANwIAIAJB1ABqIABBCGopAgA3AgAgAkEBNgJIIAIgACkCADcCTAwBCyACQfwAaiIDKAIAIQQgAkH4AGoiBSgCACERIAIoAnQhCiACQfAAaiABEGkCQCACKAJwQQFGBEAgAkHkAGogAEEYaigCADYCACACQdwAaiAAQRBqKQIANwIAIAJB1ABqIABBCGopAgA3AgAgAkEBNgJIIAIgACkCADcCTAwBCyADKAIAIQAgBSgCACESIAIoAnQh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2AJBAUcEQAJAIAIoAogDIgAgAigChANHBEAgAigCgAMhAwwBCyAAQQFqIgEgAEkNCSAAQQF0IgMgASADIAFLGyIBQQBIDQkCfyAARQRAIAFBARBADAELIAIoAoADIABBASABEEELIgNFDQIgAiABNgKEAyACIAM2AoADCyAAIANqQfsAOgAAIAIgAigCiANBAWo2AogDIAJBsANqIAJBgANqQY+NwABBAhCBASACKAKwAyIBQQNHBEAgAkG8A2ooAgAhACACQbgDaigCACEDIAIoArQDIQQMBgsCQCACKAKIAyIBIAIoAoQDRwRAIAIoAoADIQMMAQsgAUEBaiIAIAFJDQkgAUEBdCIDIAAgAyAASxsiAEEASA0JAn8gAUUEQCAAQQEQQAwBCyACKAKAAyABQQEgABBBCyIDRQ0DIAIgADYChAMgAiADNgKAAyACKAKIAyEBCyABIANqQTo6AAAgAiACKAKIA0EBajYCiAMgAkGwA2ogAkGAA2oQhAEgAigCsANBAUYEQCACQZgDaiACQbwDaikCADcDACACIAIpArQDNwOQAwwFCyACIAIoArQDNgKABCACIAJBuANqLQAAOgCEBCACQbADaiACQYAEaiAEEDYgAkGwA2ohACACKAKwA0EDRw0DIAJBsANqIAJBgARqIAJB6AJqEDQgAigCsANBA0cNAyACQbADaiACQYAEaiACQfQCahA1IAIoArADQQNHDQMgAkGQA2ogAigCgAQgAi0AhAQQfAwECyACQZAEaiACQYADaiAEEDEgAigCkAQiAUEDRwRAIAJBnARqKAIAIQAgAkGYBGooAgAhAyACKAKUBCEEDAULIAIoAogDIQEMBQsgAUEBEMcBAAsgAEEBEMcBAAsgAkGYA2ogAEEIaikCADcDACACIAApAgA3A5ADCyACKAKQAyIBQQNHBEAgAkGYA2ooAgAhAyACQZwDaigCACEAIAIoApQDIQQMAQsCQCACKAKIAyIBIAIoAoQDRwRAIAIoAoADIQMMAQsgAUEBaiIAIAFJDQQgAUEBdCIDIAAgAyAASxsiAEEASA0EAn8gAUUEQCAAQQEQQAwBCyACKAKAAyABQQEgABBBCyIDRQ0DIAIgADYChAMgAiADNgKAAyACKAKIAyEBCyABIANqQf0AOgAAIAIgAigCiANBAWoiATYCiAMMAQsgAigChAMEQCACKAKAAxDAAQsgAkGcA2ogADYCACACQZgDaiADNgIAIAIgBDYClAMgAiABNgKQAyACQbADakGOhsAAQeoAIAJBkANqEBYgAkGYBGogAkHEA2oiACkCACIXNwMAIAIgAkG8A2oiASkCACIYNwOQBCACKQOwAyEZIAJBuANqIgMgAygCADYCACABIBg3AgAgACAXNwIAIAIgGTcDsANBxIrAAEErIAJBsANqQfCKwAAQ3wEACyACKQOAAyEXIAIgATYCuAMgAiAXNwOwAyACQbADahBoAkAgAigC2AJFBEAgBBAkIAJB8AJqKAIAIgEEQCACQegCaigCACEAIAFBGGwhAQNAIABBBGooAgAEQCAAKAIAEMABCyAAQRBqKAIABEAgAEEMaigCABDAAQsgAEEYaiEAIAFBaGoiAQ0ACwsgAkHsAmooAgAEQCACKALoAhDAAQsgAkH0AmooAgAiAEUNASACQfgCaigCAEUNASAAEMABDAELIAQQHAsgAkGgBGokAA8LIABBARDHAQALEMgBAAu1GwIPfwN+IwBB8AFrIgEkACABQaABaiAAEGlBBCEAAkAgASgCoAFBAUYEQCABQZwBaiABQaABakEEciIAQRhqKAIANgIAIAFBlAFqIABBEGopAgA3AgAgAUGMAWogAEEIaikCADcCACABQQE2AoABIAEgACkCADcChAEMAQsgAUGoAWoiDigCACELIAFB4AFqIAEoAqQBIgwgAUGsAWoiDygCABByIAFB+ABqIAFB4AFqEHkCQCABLQB4QQFxRQ0AIAEtAHkiAkH7AEcEQCACQSJHBEBBCiEADAILIAFBoAFqIAFB4AFqEC8gAUGsAWooAgAhAiABQagBaigCACEEIAEoAqQBIQNBDiEAAkAgAS0AoAEiCEEBTQRAIAhBAWsNAwwBCyACRSADQRRHcg0CIAQQwAEMAgsgAUGwAWooAgAhBSADIQAMAQsgAUHgAWoQcyABQaABaiABQeABaiIJEC8gAUGsAWooAgAhAiABQagBaigCACEEIAEoAqQBIQMgAS0AoQEhBwJAIAEtAKABIgVBAU0EQCAFQQFrDQEgAUGwAWooAgAhBSADIQAMAgsgAkUgA0EUR3INACAEEMABCyABQaABaiAJEHcgASgCoAEiA0EWRwRAIAFBrAFqKAIAIQUgAUGoAWooAgAh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AgAgASAHNgKkASABIAFB4AFqNgKgASABQYABaiABQaABakEEchA8IAtFDQMgDBDAAQwDC0ECIQAMAQtBDiEACyABQewBaiAFNgIAIAFB6AFqIAI2AgAgASAENgLkASABIAA2AuABIAFBoAFqQfyIwABBHCABQeABahAUIAFByAFqIAFBtAFqKQIAIhA3AwAgAUGMAWogDigCADYCACABQZABaiAPKQIANwMAIAFBmAFqIBA3AwAgAUEBNgKAASABIAEpA6ABNwKEASALRQ0AIAwQwAELIAFB0AFqEH8gAUGAAWpBBHIhCAJAAkACQAJAAn8gASgCgAFBAUcEQAJAIAEoAtgBIgcgASgC1AFHBEAgASgC0AEhAgwBCyAHQQFqIgMgB0kNBSAHQQF0IgAgAyAAIANLGyIAQQBIDQUCfyAHRQRAIABBARBADAELIAEoAtABIAdBASAAEEELIgJFDQYgASAANgLUASABIAI2AtABCyACIAdqQfsAOgAAIAEgASgC2AFBAWo2AtgBIAFBoAFqIAFB0AFqQY+NwABBAhCBASABKAKgASIGQQNHBEAgAUGoAWooAgAhAiABKAKkASEEIAFBrAFqKAIADAILAkAgASgC2AEiBiABKALUAUcEQCABKALQASECDAELIAZBAWoiAyAGSQ0FIAZBAXQiACADIAAgA0sbIgBBAEgNBQJ/IAZFBEAgAEEBEEAMAQsgASgC0AEgBkEBIAAQQQsiAkUNBiABIAA2AtQBIAEgAjYC0AEgASgC2AEhBgsgAiAGakE6OgAAIAEgASgC2AFBAWo2AtgBIAFBwAFqIAgQWiABQaABaiABQdABaiABKALAASIAIAEoAsgBEIEBIAEoAsQBBEAgABDAAQsgASgCoAEiBkEDRwRAIAFBqAFqKAIAIQIgASgCpAEhBCABQawBaigCAAwCCwJAIAEoAtgBIgYgASgC1AFHBEAgASgC0AEhAgwBCyAGQQFqIgMgBkkNBSAGQQF0IgAgAyAAIANLGyIAQQBIDQUCfyAGRQRAIABBARBADAELIAEoAtABIAZBASAAEEELIgJFDQYgASAANgLUASABIAI2AtABIAEoAtgBIQYLIAIgBmpB/QA6AAAgASABKALYAUEBaiIGNgLYAQwDCyABQeABaiABQdABaiAIEDEgASgC4AEiBkEDRg0BIAFB6AFq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AmooAgAhBiADQdACaigCACECIAMoAswCIQcgAygCyAJBAUYEQCACIQQgBiECIAEhBiAHIQEMCwsgA0HkBGogATYCACADQeAEaiAGNgIAIANB6ARqIgUgAykD+AQ3AwAgA0HwBGogBCgCADYCACADIAI2AtwEIAMgBzYC2AQgA0GYAmogChB5IAMtAJgCQQFxDQEgA0HYBGoQHEEEIQEMCgsgA0HUAmooAgAhCCADQdACaigCACEFIAMoAswCIQcMAQsgAy0AmQJB/QBHBEAgA0HYBGoQHEELIQEMCQsgChBzIANByARqIAVBCGooAgA2AgAgAyAFKQIANwPABEEBDAQLIAQEQCABEMABCyACRSAMRSABQRRHcnJFBEAgBBDAAQsgCwRAIAghAiAFIQQgByEBDAgLIANBqAJqIAoQeSADLQCoAkEBcQ0CIAUNAQtBBCEBDAYLIAcQwAFBBCEBDAULIAMtAKkCQf0ARwRAQQshASAFRQ0FIAcQwAEMBQsgChBzIAghBiAFIQJBAAshCiADQaAEaiIFIAE2AgAgA0GcBGogBjYCACADQZgEaiACNgIAIANBpARqIAMpA8AENwIAIANBrARqIANByARqKAIANgIAIAMgByIENgKUBCADIAo2ApAEIANBkAJqIA0QeSADLQCQAkEBcUUNASADLQCRAkH9AEcEQAJAIApFBEAgAkUNASAHEMABQQshAQwGCyADQZAEakEEchAcC0ELIQEMBAsgDRBzIANBiARqIAVBCGopAgA3AwAgAyAFKQIANwOABEEACyEIIANB2ANqIg0gBjYCACADQdQDaiACNgIAIANB0ANqIAQ2AgAgA0HcA2ogAykDgAQ3AgAgA0HkA2ogA0GIBGopAwA3AgAgAyAKNgLMAyADIAg2AsgDIANByAJqIANBuANqEHQgA0HUAmohByADQdACaiELIAMoAsgCIgFBFkcEQCAHKAIAIQYgCygCACECIAMoAswCIQQgA0HIA2oQSQwDCyADQfgCaiANQQhqKQIAIhE3AwAgA0GAA2ogDUEQaigCACIFNgIAIAsgCjYCACAHIAQ2AgAgA0HYAmogAjYCACADQdwCaiANKQIAIhI3AgAgA0HkAmogETcCACADQewCaiIBIAU2AgAgAyASNwPwAiADIAg2AswCIANBADYCyAIgEARAIA8QwAELIAAgAykCzAI3AgAgAEEgaiABKAIANgIAIABBGGogA0HkAmopAgA3AgAgAEEQaiADQdwCaikCADcCACAAQQhqIANB1AJqKQIANwIADAMLIApFBEAgAkUNASAHEMABQQQhAQwCCyADQZAEakEEchAcC0EEIQELIANB1ANqIAY2AgAgA0HQA2ogAjYCACADIAQ2AswDIAMgATYCyANBpgFBARBAIgFFDQEgAUHPkcAAQaYBEI4CIQUgAyADQcgDajYCiAUgA0EANgKYBCADQgE3A5AEIANBHjYC/AQgAyADQYgFajYC+AQgAyADQZAEajYC2AQgA0HcAmpBATYCACADQgE3AswCIANBkJHAADYCyAIgAyADQfgEajYC2AIgA0HYBGpB+JLAACADQcgCahDVAQ0CIAMoApQEIgEgAygCmAQiAkcEQCABIAJJDQQCQCACRQRAIAEEQCADKAKQBBDAAQsgA0EBNgKQBEEAIQIMAQsgAygCkAQgAUEBIAIQQSIBRQ0GIAMgATYCkAQLIAMgAjYClAQLIANB4ARqIgIgA0GYBGooAgA2AgAgAyADKQOQBDcD2AQQjgEhBAJAIAMoAsgDQRRHDQAgAygC0ANFDQAgAygCzAMQwAELIANBqANqIgEgAigCADYCACADQZgDaiADQbADaigCADYCACADQZADaiABKQMAIhE3AwAgA0HUAmoiAkKmgYCA4BQ3AgAgA0HQAmogBTYCACADQdwCaiADKQPYBCISNwIAIANB5AJqIgEgET4CACADIBI3A4gDIAMgBEEIdEEFcjYCzAIgA0EBNgLIAiAQBEAgDxDAAQsgAEKAgICAEDcCACAAQQhqIAMpAswCNwIAIABBEGogAikCADcCACAAQRhqIANB3AJqKQIANwIAIABBIGogASgCADYCAAsgDhDAASADQaAFaiQADwtBpgFBARDHAQALQZiRwABBNyADQcgCakGQk8AAEN8BAAtB3JTAAEEkQcyUwAAQzQEACyACQQEQxwEAC5FvAQ9/IwBB8AVrIgIkACACQagFaiABEFUgAkG0BWooAgAhAyACQbAFaigCACEJIAIoAqwFIQQgAi0AqQUhBgJAAkAgAAJ/AkACQAJAAkACfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAi0AqAUiBUEBTQRAIAVBAWsNAgwBCyADRSAE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IAIgBBDPAQALQaCwwABBHEGAssAAEM0BAAsgBEEBagwBCyAKDQMgAwshBCABIAQ2AgggByAESw0ACwsgAEKBgICAMDcCAAwFC0GgsMAAQRxBgLLAABDNAQALQQwhBiALQQFxDQFBESEGIA1BAUYNAQsgCEEQaiAFIAQQ5AEgCCgCEEEBRw0BIAEEQCAFEMABCyAAQoGAgIDwATcCACAAQQhqIAgpAhA3AgAgAEEQaiAIQRhqKAIANgIADAILIABBATYCACAAIAY2AgQgAUUNASAFEMABDAELIAAgBTYCBCAAQQA2AgAgAEEMaiAENgIAIABBCGogATYCAAsgCEEgaiQADwsQbAALIAJBARDHAQALcwEGfwJAIAEoAgQiBCABKAIIIgJNDQAgASgCACEFA0AgAiAFai0AACIGQXdqIgdBF0sEQEEBIQMMAgtBASAHdEGTgIAEcUUEQEEBIQMMAgsgASACQQFqIgI2AgggBCACSw0ACwsgACAGOgABIAAgAzoAAAs4AQF/An9BACABKAIEIAEoAggiAk0NABogASgCACACai0AACECQQELIQEgACACOgABIAAgAToAAAuyAQEDfwJAAkACQCABKAIIIgIgAUEEaigCAEcEQCABKAIAIQQMAQsgAkEBaiIDIAJJDQIgAkEBdCIEIAMgBCADSxsiA0EASA0CAn8gAkUEQCADQQEQQAwBCyABKAIAIAJBASADEEELIgRFDQEgASAENgIAIAFBBGogAzYCACABKAIIIQILIAIgBGpB3QA6AAAgAEEDNgIAIAEgASgCCEEBajYCCA8LIANBARDHAQALEMgBAAuyAQECfwJAAkACQCABKAIIIgIgAUEEaigCAEcEQCABKAIAIQMMAQsgAkEBaiIDIAJJDQIgAkEBdCIEIAMgBCADSxsiBEEASA0CAn8gAkUEQCAEQQEQQAwBCyABKAIAIAJBASAEEEELIgNFDQEgASADNgIAIAFBBGogBDYCACABKAIIIQILIAIgA2pB/QA6AAAgAEEDNgIAIAEgASgCCEEBajYCCA8LIARBARDHAQALEMgBAAvOAgECfwJAAkACQAJAIAEoAggiAiABQQRqKAIARwRAIAEoAgAhAwwBCyACQQFqIgMgAkkNAyACQQF0IgQgAyAEIANLGyIEQQBIDQMCfyACRQRAIARBARBADAELIAEo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ACAAQQhqQQE6AAAgACABNgIEIABBADYCACABIAEoAghBAWo2AggPCyADQQEQxwEACxDIAQAL8wQBBH8jAEEQayIGJAACQAJAAkACQAJAAkAgASgCCCIEIAFBBGooAgBHBEAgASgCACEFDAELIARBAWoiBSAESQ0EIARBAXQiByAFIAcgBUsbIgdBAEgNBAJ/IARFBEAgB0EBEEAMAQsgASgCACAEQQEgBxBBCyIFRQ0BIAEgBTYCACABQQRqIAc2AgAgASgCCCEECyAEIAVqQfsAOgAAQQEhBCABIAEoAghBAWo2AgggBiABIAIgAxCBASAGKAIAIgJBA0cEQCAGQQxqKAIAIQEgBkEIaigCACEDIAYoAgQhBSAAIAI2AgQgAEEQaiABNgIAIABBDGogAzYCACAAQQhqIAU2AgAMBQsCQCABKAIIIgQgAUEEaigCAEcEQCABKAIAIQUMAQsgBEEBaiICIARJDQQgBEEBdCIDIAIgAyACSxsiAkEASA0EAn8gBEUEQCACQQEQQAwBCyABKAIAIARBASACEEELIgVFDQIgASAFNgIAIAFBBGogAjYCACABKAIIIQQLIAQgBWpBOjoAACABIAEoAghBAWoiBDYCCAJAIAFBBGooAgAgBEcEQCABKAIAIQUMAQsgBEEBaiICIARJDQQgBEEBdCIDIAIgAyACSxsiAkEASA0EAn8gBEUEQCACQQEQQAwBCyABKAIAIARBASACEEELIgVFDQMgASAFNgIAIAFBBGogAjYCACABKAIIIQQLIAQgBWpB+wA6AAAgACABNgIEIABBCGpBAToAACABIAEoAghBAWo2AghBACEEDAQLIAdBARDHAQALIAJBARDHAQALIAJBARDHAQALEMgBAAsgACAENgIAIAZBEGokAAucEAIJfwN+AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQEEAIAFBZmoiBiAGIAFLGyIHBEADQCAJQRpqIgggCUkiBg0CIAYNByAIIAFLDQggC0EgaiIIIAtJIgYNAyAGDQkgCCADSw0KIAIgC2oiBSAEIAAgCWoiDCkAACIOQjiGIg9COoinai0AADoAACAFQQFqIAQgDyAOQiiGQoCAgICAgMD/AIOEIg9CNIinQT9xai0AADoAACAFQQJqIAQgDyAOQhiGQoCAgICA4D+DIA5CCIZCgICAgPAfg4SEIhBCLoinQT9xai0AADoAACAFQQNqIAQgEEIoiKdBP3FqLQAAOgAAIAVBBGogBCAQQiKIp0E/cWotAAA6AAAgBUEGaiAEIA5CCIhCgICA+A+DIA5CGIhCgID8B4OEIA5CKIhCgP4DgyAOQjiIhIQiD6ciBkEWdkE/cWotAAA6AAAgBUEHaiAEIAZBEHZBP3FqLQAAOgAAIAVBBWogBCAPIBCEQhyIp0E/cWotAAA6AAAgBUEIaiAEIAxBBmopAAAiDkI4hiIPQjqIp2otAAA6AAAgBUEJaiAEIA8gDkIohkKAgICAgIDA/wCDhCIPQjSIp0E/cWotAAA6AAAgBUEKaiAEIA8gDkIYhkKAgICAgOA/gyAOQgiGQoCAgIDwH4OEhCIQQi6Ip0E/cWotAAA6AAAgBUELaiAEIBBCKIinQT9xai0AADoAACAFQQxqIAQgEEIiiKdBP3FqLQAAOgAAIAVBDWogBCAQIA5CCIhCgICA+A+DIA5CGIhCgID8B4OEIA5CKIhCgP4DgyAOQjiIhIQiD4RCHIinQT9xai0AADoAACAFQQ5qIAQgD6ciBkEWdkE/cWotAAA6AAAgBUEPaiAEIAZBEHZBP3FqLQAAOgAAIAVBEGogBCAMQQxqKQAAIg5COIYiD0I6iKdqLQAAOgAAIAVBEWogBCAPIA5CKIZCgICAgICAwP8Ag4QiD0I0iKdBP3FqLQAAOgAAIAVBEmogBCAPIA5CGIZCgICAgIDgP4MgDkIIhkKAgICA8B+DhIQiEEIuiKdBP3FqLQAAOgAAIAVBE2ogBCAQQiiIp0E/cWotAAA6AAAgBUEUaiAEIBBCIoinQT9xai0AADoAACAFQRZqIAQgDkIIiEKAgID4D4MgDkIYiEKAgPwHg4QgDkIoiEKA/gODIA5COIiEhCIPpyIGQRZ2QT9xai0AADoAACAFQRdqIAQgBkEQdkE/cWotAAA6AAAgBUEVaiAEIA8gEIRCHIinQT9xai0AADoAACAFQRhqIAQgDEESaikAACIOQjiGIg9COoinai0AADoAACAFQRlqIAQgDyAOQiiGQoCAgICAgMD/AIOEIg9CNIinQT9xai0AADoAACAFQRpqIAQgDyAOQhiGQoCAgICA4D+DIA5CCIZCgICAgPAfg4SEIhBCLoinQT9xai0AADoAACAFQRtqIAQgEEIoiKdBP3FqLQAAOgAAIAVBHGogBCAQQiKIp0E/cWotAAA6AAAgBUEdaiAEIBAgDkIIiEKAgID4D4MgDkIYiEKAgPwHg4QgDkIoiEKA/gODIA5COIiEhCIPhEIciKdBP3FqLQAAOgAAIAVBHmogBCAPpyIGQRZ2QT9xai0AADoAACAFQR9qIAQgBkEQdkE/cWotAAA6AAAgC0EgaiELIAlBGGoiCSAHTQ0ACwsgASABQQNwIgtrIgogAUsNAiAJIApPBEAgCCEHDA4LA0AgCUEDaiIGIAlJIgcNBCAHDQogBiABSw0LIAhBBGoiByAISSIMDQUgDA0MIAcgA0sNDSACIAhqIg0gBCAAIAlqIgUtAAAiCUECdmotAAA6AAAgDUEDaiAEIAVBAmotAAAiDEE/cWotAAA6AAAgDUEBaiAEIAlBBHRBMHEgBUEBai0AACIJQQR2cmotAAA6AAAgDUECaiAEIAlBAnRBPHEgDEEGdnJqLQAAOgAAIAhBBGohCCAGIgkgCkkNAAsMDQtBoLTAAEEcQby1wAAQzQEAC0GgtMAAQRxBzLXAABDNAQALQfCzwABBIUHctcAAEM0BAAtBoLTAAEEcQey1wAAQzQEAC0GgtMAAQRxB/LXAABDNAQALIAkgCBDPAQALIAggARDOAQALIAsgCBDPAQALIAggAxDOAQALIAkgBhDPAQALIAYgARDOAQALIAggBxDPAQALIAcgAxDOAQALIAtBf2oiBkEBSwRAIAcPCwJAAkACQAJAAkACQAJAAkACQCAGQQFrRQRAIAogAU8NAiAHIANPDQMgAiAHaiAEIAAgCmotAAAiBkECdmotAAA6AAAgCkEBaiIIIAFPDQQgB0EBaiIBIANPDQUgASACaiAEIAZBBHRBMHEgACAIai0AACIAQfABcUEEdnJqLQAAOgAAIAdBAmoiASADTw0GIAEgAmogBCAAQQJ0QTxxai0AADoAACAHQQNqIgQgB08NAUGgtMAAQRxB3LbAABDNAQALIAogAU8NBiAHIANPDQcgAiAHaiAEIAAgCmotAAAiAEECdmotAAA6AAAgB0EBaiIBIANPDQggASACaiAEIABBBHRBMHFqLQAAOgAAIAdBAmohBAsgBA8LQYy2wAAgCiABEMwBAAtBnLbAACAHIAMQzAEAC0GstsAAIAggARDMAQALQby2wAAgASADEMwBAAtBzLbAACABIAMQzAEAC0HstsAAIAogARDMAQALQfy2wAAgByADEMwBAAtBjLfAACABIAMQzAEAC5wBAQJ/IAAgASACIANBlMHAACgCABCGASEEQQAhAAJAAkAgBCADTQRAIAFBA3BBA3NBA3AiBQRAIAMgBGshAyACIARqIQJBACEBA0AgASADRg0DIAEgAmpBPToAACABQQFqIgAhASAAIAVJDQALCyAAIARqIARJDQIPCyAEIAMQzwEAC0H8t8AAIAMgAxDMAQALQby0wABBKhDdAQALbQEFfyMAQSBrIgUkACABQQNuIgJB/////wNxIgYgAkYhAyACQQJ0IQQCQCABIAJBA2xrRQRAIAQhAgwBCyACIAZHBEBBACEDDAELIARBBGoiAiAETyEDCyAAIAI2AgQgACADNgIAIAVBIGokAAujAgEBfyMAQTBrIgIkAAJ/AkACQAJAIAAtAABBAWsOAgECAAsgAiAAQQRqKAIANgIAIAIgAC0AAToAByACQRxqQQI2AgAgAkEsakE0NgIAIAJCAzcCDCACQYi5wAA2AgggAkE1NgIkIAIgAkEgajYCGCACIAI2AiggAiACQQdqNgIgIAEgAkEIahD4AQwCCyACQgQ3AxggAkIBNwIMIAJB8LjAADYCCCABIAJBCGoQ+AEMAQsgAiAAQQRqKAIANgIAIAIgAC0AAToAByACQRxqQQI2AgAgAkEsakE0NgIAIAJCAzcCDCACQay4wAA2AgggAkE1NgIkIAIgAkEgajYCGCACIAI2AiggAiACQQdqNgIgIAEgAkEIahD4AQsgAkEwaiQACyEBAX8gAEEHaiIBIABJBEBBoLnAAEEzEN0BAAsgAUEDdgu+AQEBfyMAQSBrIgEkACAAKAIAIgAtAAAgAEEAOgAAQQFxBEAgAUEQahCsAQJAIAEoAhBFBEAgARCsAQwBCyABQQhqIAFBGGooAgA2AgAgASABKQMQNwMAC0EAIQACQCABKAIARQ0AIAFBGGogAUEIaigCADYCACABIAEpAwA3AxAgAUEQahCtASEAIAEoAhRFDQAgASgCEBDAAQtBrILBACAAOgAAIAFBIGokAA8LQZzCwABBK0GMwsAAEM0BAAsOACABQcDBwABBAhDQAQsoAQF/IwBBEGsiAiQAIAIgACgCADYCDCACQQxqIAEQiwEgAkEQaiQAC0YBAn8jAEEQayIAJABBqILBACgCAEEDRwRAIABBAToACyAAIABBC2o2AgwgAEEMahCuAQtBrILBAC0AACAAQRBqJABBAEcLPQEBfyMAQRBrIgIkACACIAFB8sLAAEEJEPwBIAIgADYCDCACIAJBDGpB/MLAABDrASACEOwBIAJBEGokAAszAQF/IwBBEGsiACQAIABBDjYCDCAAQeTEwAA2AgggAEEIakGMw8AAQQBB1MTAABC1AQALFgAgACgCACIAKAIAIAAoAgQgARD/AQsdACABKAIARQRAAAsgAEGgw8AANgIEIAAgATYCAAtVAQJ/IAEoAgAhAiABQQA2AgACQCACBEAgASgCBCEDQQhBBBBAIgFFDQEgASADNgIEIAEgAjYCACAAQaDDwAA2AgQgACABNgIADwsAC0EIQQQQxwEAC5gDAQN/IwBBMGsiAiQAAn8gACgCBCIEQQJNBEACQAJAAkAgBEEBaw4CAQIACxCQAQALIAJBLGpBATYCACACQgI3AhwgAkHMw8AANgIYIAJBOjYCBCACIAAoAgA2AgAgAiACNgIoIAEgAkEYahD4AQwCCyACQQxqQTo2AgAgAkEsakECNgIAIAJCAzcCHCACQeTDwAA2AhggAkE6NgIEIAIgACgCACIANgIAIAIgAEEIajYCCCACIAI2AiggASACQRhqEPgBDAELIAJCBDcDKCACQgE3AhwgAkG4w8AANgIYQQEgASACQRhqEPgBDQAaIARBA3RBeGpBA3ZBAWohBCAAKAIAIQACQANAIAIgADYCFCADBEAgAkIENwMoIAJCATcCHCACQcTDwAA2AhggASACQRhqEPgBDQILIAJBATYCLCACQgI3AhwgAkHMw8AANgIYIAJBOzYCBCACIAI2AiggAiACQRRqNgIAIAEgAkEYahD4AQ0BIABBCGohACAEIANBAWoiA0cNAAtBAAwBC0EBCyACQTBqJAALDABCjaDg/6rqvtl8Cw0AQtCb6PfD3q+r7QALMgAgACgCACEAIAEQ+QFFBEAgARD6AUUEQCAAIAEQ0wEPCyAAIAEQhQIPCyAAIAEQgAILYQECfyMAQRBrIgIkACAAKAIAIgAoAgghAyAAKAIAIQAgAiABEP0BIAMEQANAIAIgADYCDCACIAJBDGpBjMXAABDtASAAQQFqIQAgA0F/aiIDDQALCyACEO4BIAJBEGokAAsyACAAKAIAIQAgARD5AUUEQCABEPoBRQRAIAAgARDmAQ8LIAAgARCEAg8LIAAgARDjAQstAQF/IwBBEGsiAyQAIAMgATYCDCADIAA2AgggA0EIakGMzsAAQQAgAhC1AQALEgAgACgCBARAIAAoAgAQwAELC0gAAkAgAC0ABA0AQZCGwQAoAgBBAUcEQEGQhsEAQgE3AwAMAQtBlIbBACgCAEUNACAAKAIAQQE6AAQLIAAoAgAoAgBBADoAAAsVACAAQQhqKAIABEAgACgCBBDAAQsLIQEBfwJAIAAoAgQiAUUNACAAQQhqKAIARQ0AIAEQwAELCxoAIABFBEBBnMfAAEErQeDGwAAQzQEACyAAC78CAQJ/IwBBEGsiAiQAAkAgACgCACIAAn8CQCABQYABTwRAIAJBADYCDCABQYAQSQ0BIAFBgIAESQRAIAIgAUE/cUGAAXI6AA4gAiABQQZ2QT9xQYABcjoADSACIAFBDHZBD3FB4AFyOgAMQQMMAwsgAiABQT9xQYABcjoADyACIAFBEnZB8AFyOgAMIAIgAUEGdkE/cUGAAXI6AA4gAiABQQx2QT9xQYABcjoADUEEDAILIAAoAggiAyAAKAIERgR/IABBARChASAAKAIIBSADCyAAKAIAaiABOgAAIAAgACgCCEEBajYCCAwCCyACIAFBP3FBgAFyOgANIAIgAUEGdkEfcUHAAXI6AAxBAgsiARChASAAIAAoAggiAyABajYCCCADIAAoAgBqIAJBDGogARCOAhoLIAJBEGokAEEAC38BAn8CQAJAIAAoAgQiAiAAKAIIIgNrIAFJBEAgASADaiIBIANJDQIgAkEBdCIDIAEgAyABSxsiAUEASA0CAn8gAkUEQCABQQEQQAwBCyAAKAIAIAJBASABEEELIgJFDQEgACABNgIEIAAgAjYCAAsPCyABQQEQxwEACxDIAQALWgEBfyMAQSBrIgIkACACIAAoAgA2AgQgAkEYaiABQRBqKQIANwMAIAJBEGogAUEIaikCADcDACACIAEpAgA3AwggAkEEakH0xMAAIAJBCGoQ1QEgAkEgaiQACzEBAX8gACgCACIAIAIQoQEgACAAKAIIIgMgAmo2AgggAyAAKAIAaiABIAIQjgIaQQALYgECfwJAIAAoAgAiAUEQaigCACICRQ0AIAJBADoAACABQRRqKAIARQ0AIAEoAhAQwAELIAFBHGooAgAQwAEgACgCACIBIAEoAgQiAUF/ajYCBCABQQFGBEAgACgCABDAAQsLnAIBA38jAEEgayIBJAACQAJAQcSCwQAoAgAiAEEBakEASgRAQcSCwQAgADYCAEHIgsEAKAIAIgJFBEAgAUEANgIIIAFBCGoQqQEhAkHEgsEAKAIADQNBxILBAEF/NgIAAkBByILBACgCACIARQ0AIAAgACgCACIAQX9qNgIAIABBAUcNAEHIgsEAEKQBC0HIgsEAIAI2AgBBxILBAEHEgsEAKAIAQQFqIgA2AgALIAANAkHEgsEAQX82AgAgAiACKAIAIgBBAWo2AgAgAEF/TA0BQcSCwQBBxILBACgCAEEBajYCACABQSBqJAAgAg8LQazFwABBGCABQRhqQejHwAAQ3wEACwALQZzFwABBECABQRhqQcjHwAAQ3wEAC/kEAQZ/IwBB4ABrIgAkAEHAgsEAKAIAQQFHBEBBwILBAEIBNwIAQciCwQBBADYCAAsQpQEiAUEAIAEoAhgiAyADQQJGIgMbNgIYIAAgATYCCAJAIAMNAAJAAkAgACgCCCIDQRxqIgQoAgAiAS0AAEUEQCABQQE6AABBACEBAkBBkIbBACgCAEEBRgRAQZSGwQAoAgAhAQwBC0GQhsEAQgE3AwALQZSGwQAgATYCACADQSBqLQAADQEgA0EYaiICIAIoAgAiAkEBIAIbNgIAIAIEQCACQQJHBEBBwMnAAEEXQbDJwAAQmgEACyAAKAIIQRhqIgUoAgAhAiAFQQA2AgAgACACNgIMIAJBAkcNAwJAIAENAEGQhsEAKAIAQQFHBEBBkIbBAEIBNwMADAELQZSGwQAoAgBFDQAgA0EBOgAgCyAEKAIAQQA6AAAMBAsgACgCCEEkaiIAIAQoAgAQpwEgACgCABpB+M7AAEEdQejOwAAQmgEAC0HEz8AAQSBBtM/AABCaAQALIAAgBDYCSCAAIAFBAEc6AExB+MfAAEErIABByABqQaTIwAAQ3wEACyAAQTxqQcAANgIAIABBNGpBwQA2AgAgAEEkakEDNgIAIAAgAEEMajYCQCAAQdjJwAA2AkQgAEIDNwIUIABBhMfAADYCECAAQcEANgIsIABCBDcDWCAAQgE3AkwgAEH8ycAANgJIIAAgAEEoajYCICAAIABByABqNgI4IAAgAEHEAGo2AjAgACAAQUBrNgIoIABBEGpBhMrAABCoAQALIAAoAggiASABKAIAIgFBf2o2AgAgAUEBRgRAIABBCGoQpAELIABB4ABqJAALMAAgACAAKAIEIgAgASAAGzYCBCAARSAAIAFGckUEQEHky8AAQTZB1MvAABCaAQALC04BAX8jAEEgayICJAAgAiABKAIAIAEoAgQgASgCCCABKAIMENsBIAIgADYCGCACQfDGwAA2AhQgAkEBNgIQIAIgAjYCHCACQRBqELQBAAvIAwIEfwF+IwBBMGsiASQAAkACQAJAAkACf0EAIAAoAgAiAkUNABogASAAKQIENwIkIAEgAjYCICABQRBqIgAgAUEgaiICKQIANwIAIABBCGogAkEIaigCADYCACABQQhqQQAgASgCECIAIAEoAhgQ4AEgASgCCA0BIAFBKGogAUEYaigCADYCACABIAEpAxA3AyAgASABQSBqEKoBIAEoAgQhAyABKAIACyEEQZiGwQAtAAANAUGYhsEAQQE6AAACQEGghsEAKQMAIgVCf1IEQEGghsEAIAVCAXw3AwAgBUIAUg0BQZzHwABBK0HgxsAAEM0BAAtBpMrAAEE3QZTKwAAQmgEAC0GYhsEAQQA6AABBAUEBEEAiAkUNAiACQQA6AABBMEEIEEAiAEUNAyAAQgE3AiQgAEEANgIYIAAgAzYCFCAAIAQ2AhAgACAFNwMIIABCgYCAgBA3AwAgACACrTcCHCABQTBqJAAgAA8LIAEoAgwhAiABQShqIAEpAhQ3AwAgASAANgIkIAEgAjYCIEHbysAAQS8gAUEgakHYx8AAEN8BAAtBxM/AAEEgQbTPwAAQmgEAC0EBQQEQxwEAC0EwQQgQxwEAC5ACAQR/AkACQAJAAkAgASgCBCICIAEoAggiA0YEQCADQQFqIgIgA0kgAkEASHINAgJ/IANFBEAgAkEBEEAMAQsgASgCACADQQEgAhBBCyIFRQ0BIAEgAjYCBCABIAU2AgALIAIgA0YEQCABQQEQoQEgASgCCCEDIAEoAgQhAgsgASADQQFqIgQ2AgggAyABKAIAIgVqQQA6AAAgAiAERgRAIAUhASACIQQMBAsgAiAESQ0CIARFBEBBACEEQQEhASACRQ0EIAUQwAEMBAsgBSACQQEgBBBBIgENAyAEQQEQxwEACyACQQEQxwEACxDIAQALQeTIwABBJEHUyMAAEM0BAAsgACAENgIEIAAgATYCAAv0AQEDfyMAQRBrIgIkACAAKAIAQRhqIgMoAgAhASADQQI2AgACQAJAAkACQCABQQJNBEAgAUEBaw0CDAELQZzLwABBHEGMy8AAEJoBAAsgACgCACIDQRxqIgEoAgAiAC0AAA0BIABBAToAAEEAIQACQEGQhsEAKAIAQQFGBEBBlIbBACgCACEADAELQZCGwQBCATcDAAtBlIbBACAANgIAIANBIGotAAANAiABKAIAQQA6AAALIAJBEGokAA8LQcTPwABBIEG0z8AAEJoBAAsgAiABNgIIIAIgAEEARzoADEH4x8AAQSsgAkEIakGkyMAAEN8BAAsJACAAQQA2AgALPAECfyAAKAIIIgFB3MLAACgCAEYEfyAAKAIAIgBB2MLAACgCACICRgRAQQEPCyAAIAIgARCPAkUFQQALC7kDAQV/IwBBEGsiAyQAIANBAnIhBEGogsEAKAIAIQEDQAJAAkACQAJAAkAgASICQQNLDQACQAJAIAJBAWsOAwMCAQALQaiCwQBBAkGogsEAKAIAIgEgASACRhs2AgAgASACRw0GIANBqILBADYCACAAIAJBAUZBuMHAACgCABEBACADQQM2AgQgAxCvAQsgA0EQaiQADwsgAkEDcUECRgRAA0BBwILBACgCAEEBRwRAQcCCwQBCATcCAEHIgsEAQQA2AgALIAIhARClASEFQaiCwQAgBEGogsEAKAIAIgIgASACRhs2AgAgA0EAOgAIIAMgBTYCACADIAFBfHE2AgQgASACRgRAIAMtAAhFDQQMBQsCQCADKAIAIgFFDQAgASABKAIAIgFBf2o2AgAgAUEBRw0AIAMQpAELIAJBA3FBAkYNAAwFCwALQcTMwABBOUG0zMAAEJoBAAtBkM3AAEEqQYDNwAAQmgEACwNAEKYBIAMtAAhFDQALCyADKAIAIgJFDQAgAiACKAIAIgJBf2o2AgAgAkEBRw0AIAMQpAFBqILBACgCACEBDAELQaiCwQAoAgAhAQwACwALowIBA38jAEFAaiIBJAAgACgCACICKAIAIQMgAiAAKAIENgIAIAEgA0EDcSIANgIMIABBAkYEQAJAIANBfHEiAARAA0AgACgCBCAAKAIAIQIgAEEA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AAtBAAwECyAFQQQQzgEACyABIAJBACAEENEBAAsgASACIAQgAhDRAQALQQELIANBMGokAAt4AQJ/IAAtAAQhASAALQAFBEAgAUH/AXEhAiAAAn9BASACDQAaIAAoAgAiAUEcaigCACgCDCECIAEoAhghACABLQAAQQRxRQRAIABB49vAAEECIAIRAgAMAQsgAEHi28AAQQEgAhECAAsiAToABAsgAUH/AXFBAEcL5QICA38FfiMAQdAAayIDJAAgAAJ/QQEgAC0ACA0AGiAAKAIEIQUgACgCACIELQAAQQRxRQRAQQEgBCgCGEHd28AAQefbwAAgBRtBAkEBIAUbIARBHGooAgAoAgwRAgANARogASAAKAIAIAIoAgwRAAAMAQsgBUUEQEEBIAQoAhhB5dvAAEECIARBHGooAgAoAgwRAgANARogACgCACEECyADQQE6ABcgAyADQRdqNgIQIAQpAgghBiAEKQIQIQcgA0E0akG828AANgIAIAMgBCkCGDcDCCAEKQIgIQggBCkCKCEJIAMgBC0AMDoASCAEKQIAIQogAyAJNwNAIAMgCDcDOCADIAc3AyggAyAGNwMgIAMgCjcDGCADIANBCGo2AjBBASABIANBGGogAigCDBEAAA0AGiADKAIwQdvbwABBAiADKAI0KAIMEQIACzoACCAAIAAoAgRBAWo2AgQgA0HQAGokAAuWAQECfyAALQAIIQEgACgCBCICBEAgAUH/AXEhASAAAn9BASABDQAaAkAgAkEBRw0AIAAtAAlFDQAgACgCACICLQAAQQRxDQBBASACKAIYQejbwABBASACQRxqKAIAKAIMEQIADQEaCyAAKAIAIgEoAhhBlNfAAEEBIAFBHGooAgAoAgwRAgALIgE6AAgLIAFB/wFxQQBHCwsAIAAgASACEOcBCzIBAX9BASEBIAAtAAQEfyABBSAAKAIAIgAoAhhB/NvAAEEBIABBHGooAgAoAgwRAgALC+gBAQF/IwBBEGsiAiQAIAJBADYCDCAAIAJBDGoCfwJAIAFBgAFPBEAgAUGAEEkNASABQYCABEkEQCACIAFBP3FBgAFyOgAOIAIgAUEGdkE/cUGAAXI6AA0gAiABQQx2QQ9xQeABcjoADEEDDAMLIAIgAUE/cUGAAXI6AA8gAiABQRJ2QfABcjoADCACIAFBBnZBP3FBgAFy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CWohAwwECyAPQv////+PYINCgICAgMAAhAshD0EDIQQLIAIoAhggAyACKAIcKAIQEQAARQ0ACwwECyAJIA1rIAZqIQkgBiENIAYgDEcNAQsLIANFIAEgA0ZyDQAgAyABTw0CIAAgA2osAABBv39MDQILQQEhCiACKAIYIAAgA2ogASADayACKAIcKAIMEQIADQAgAigCGEEiIAIoAhwoAhARAAAhCgsgCEEgaiQAIAoPCyAAIAEgAyABENEBAAsLACACIAAgARDQAQuKAQEDfyMAQYABayIDJAAgACgCACECQQAhAANAIAAgA2pB/wBqIAJBD3EiBEEwciAEQdcAaiAEQQpJGzoAACAAQX9qIQAgAkEEdiICDQALIABBgAFqIgJBgQFPBEAgAkGAARDPAQALIAFBAUH928AAQQIgACADakGAAWpBACAAaxD2ASADQYABaiQAC9cCAQd/QQEhCQJAAkAgAkUNACABIAJBAXRqIQogAEGA/gNxQQh2IQsgAEH/AXEhDQJAA0AgAUECaiEMIAcgAS0AASICaiEIIAsgAS0AACIBRwRAIAEgC0sNAyAIIQcgDCIBIApHDQEMAwsgCCAHTwRAIAggBEsNAiADIAdqIQECQANAIAJFDQEgAkF/aiECIAEtAAAgAUEBaiEBIA1HDQALQQAhCQwFCyAIIQcgDCIBIApHDQEMAwsLIAcgCBDPAQALIAggBBDOAQALIAZFDQAgBSAGaiEDIABB//8DcSEBA0ACQCAFQQFqIQACfyAAIAUtAAAiAkEYdEEYdSIEQQBODQAaIAAgA0YNASAFLQABIARB/wBxQQh0ciECIAVBAmoLIQUgASACayIBQQBIDQIgCUEBcyEJIAMgBUcNAQwCCwtBzNTAAEErQYzVwAAQzQEACyAJQQFxC8oCAgN/BH4jAEEwayIDJAACQCACRQRAIABBADoAAUEBIQQMAQsCQCABLQAAQStGBEBBASEEIAJBf2oiAkUNASABQQFqIQELIANBKGohBAJAA0AgAkUNASABLQAAQVBqIgVBCk8EQEEBIQQgAEEBOgABDAQLIANBEGogB0IAQgoQkgIgA0EgaiAGQgBCChCSAiADQgBCACAGEJICIAMpAxggAykDCIRCAFIgBCkDACIGIAMpAxAgAykDAHx8IgggBlRyQQFGBEAgAEECOgABQQEhBAwE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ZXNyY192YWxpZGF0b3Jkc3RfdmFsaWRhdG9yd2l0aGRyYXd2YWxpZGF0b3JyZWNpcGllbnR1bmRlbGVnYXRlZGVsZWdhdGVJbml0UmVzcG9uc2Vsb2dMb2dBdHRyaWJ1dGVrZXl2YWx1ZUhhbmRsZVJlc3BvbnNlQ29pbmRlbm9tQmFua1F1ZXJ5YWxsX2JhbGFuY2VzYWRkcmVzc2JhbGFuY2VXYXNtUXVlcnlyYXdzbWFydFN0YWtpbmdRdWVyeXZhbGlkYXRvcnNkZWxlZ2F0aW9uc2RlbGVnYXRvcmJvbmRlZF9kZW5vbUFsbEJhbGFuY2VSZXNwb25zZWJsb2NrbWVzc2FnZWNvbnRyYWN0aGVpZ2h0dGltZWNoYWluX2lkc2VuZGVyc2VudF9mdW5kc1N0ZEVycm9ydW5kZXJmbG93bWludWVuZHN1YnRyYWhlbmR1bmF1dGhvcml6ZWRzZXJpYWxpemVfZXJyc291cmNlcGFyc2VfZXJydGFyZ2V0bnVsbF9wb2ludGVybm90X2ZvdW5ka2luZGludmFsaWRfdXRmOGludmFsaWRfYmFzZTY0Z2VuZXJpY19lcnJTZXJpYWxpemluZyBRdWVyeVJlcXVlc3Q6IABFAhAAGgAAAFF1ZXJpZXIgc3lzdGVtIGVycm9yOiAAAGgCEAAWAAAACwAAAAgAAAAEAAAADAAAAA0AAACcAhAAAAAAAGEgRGlzcGxheSBpbXBsZW1lbnRhdGlvbiByZXR1cm5lZCBhbiBlcnJvciB1bmV4cGVjdGVkbHljb3Ntd2FzbV9zdGQ6OnR5cGVzOjpFbnZoYWNrYXRvbTo6Y29udHJhY3Q6OkluaXRNc2djb3JlOjpyZXN1bHQ6OlJlc3VsdDxjb3Ntd2FzbV9zdGQ6OmluaXRfaGFuZGxlOjpIYW5kbGVSZXNwb25zZSwgY29zbXdhc21fc3RkOjplcnJvcnM6OnN0ZF9lcnJvcjo6U3RkRXJyb3I+Y29zbXdhc21fc3RkOjpxdWVyeTo6UXVlcnlSZXF1ZXN0PGNvc213YXNtX3N0ZDo6dHlwZXM6Ok5ldmVyPmNvcmU6OnJlc3VsdDo6UmVzdWx0PGNvc213YXNtX3N0ZDo6ZW5jb2Rpbmc6OkJpbmFyeSwgY29zbXdhc21fc3RkOjplcnJvcnM6OnN0ZF9lcnJvcjo6U3RkRXJyb3I+Y29yZTo6cmVzdWx0OjpSZXN1bHQ8Y29zbXdhc21fc3RkOjppbml0X2hhbmRsZTo6SW5pdFJlc3BvbnNlLCBjb3Ntd2FzbV9zdGQ6OmVycm9yczo6c3RkX2Vycm9yOjpTdGRFcnJvcj5oYWNrYXRvbTo6Y29udHJhY3Q6OlF1ZXJ5TXNnaGFja2F0b206OmNvbnRyYWN0OjpTdGF0ZWhhY2thdG9tOjpjb250cmFjdDo6SGFuZGxlTXNnaGFja2F0b206OmNvbnRyYWN0OjpWZXJpZmllclJlc3BvbnNlY29zbXdhc21fc3RkOjpxdWVyeTo6QWxsQmFsYW5jZVJlc3BvbnNlAAAADgAAAAQAAAAEAAAADwAAABAAAAARAAAAEgAAAAAAAAABAAAAEwAAAGNhbGxlZCBgUmVzdWx0Ojp1bndyYXAoKWAgb24gYW4gYEVycmAgdmFsdWUAFAAAABwAAAAEAAAAFQAAAGF0dGVtcHQgdG8gYWRkIHdpdGggb3ZlcmZsb3c8Ojpjb3JlOjptYWNyb3M6OnBhbmljIG1hY3Jvcz4AAJwFEAAeAAAAAwAAAAoAAABUcmllZCB0byBzaHJpbmsgdG8gYSBsYXJnZXIgY2FwYWNpdHltaXNzaW5nIGZpZWxkIGBg8AUQAA8AAAD/BRAAAQAAAGR1cGxpY2F0ZSBmaWVsZCBgAAAAEAYQABEAAAD/BRAAAQAAAHVua25vd24gdmFyaWFudCBgYCwgdGhlcmUgYXJlIG5vIHZhcmlhbnRzAAAANAYQABEAAABFBhAAGAAAAGAsIGV4cGVjdGVkIDQGEAARAAAAcAYQAAwAAABFcnJPa2ludmFsaWQgVWludDEyOCAnJyAtIAAAkQYQABEAAACiBhAABAAAAGludmFsaWQgYmFzZTY0OiC4BhAAEAAAAAsAAAAIAAAABAAAABYAAABjb25maWdhY3Rpb25yZWxlYXNlZGVzdGluYXRpb25TdGF0ZXNyYy9jb250cmFjdC5ycwAAAwcQAA8AAAB/AAAACQAAAHRlc3Qua2V5AwcQAA8AAACNAAAACQAAAG11c3Qgbm90IGJlIGVtcHR5AAAAAwcQAA8AAACVAAAAEwAAAEVycm9yIGluIG1lbW9yeS5ncm93IGluc3RydWN0aW9uAwcQAA8AAACuAAAABQAAAFRoaXMgcGFnZSBpbnRlbnRpb25hbGx5IGZhdWx0ZWR2ZXJpZmllcmJlbmVmaWNpYXJ5ZnVuZGVyY3B1X2xvb3BzdG9yYWdlX2xvb3BtZW1vcnlfbG9vcGFsbG9jYXRlX2xhcmdlX21lbW9yeXBhbmljAAAA7AYQAAcAAADIBxAACAAAANAHEAAMAAAA3AcQAAsAAADnBxAAFQAAAPwHEAAFAAAAb3RoZXJfYmFsYW5jZQAAAK8HEAAIAAAANAgQAA0AAABWZXJpZmllclJlc3BvbnNlT2tFcnIAAABkCBAAAgAAAGYIEAADAAAAHwAAAAgAAAAEAAAAIAAAACEAAACQCBAAAAAAAGEgRGlzcGxheSBpbXBsZW1lbnRhdGlvbiByZXR1cm5lZCBhbiBlcnJvciB1bmV4cGVjdGVkbHljb3JlOjpyZXN1bHQ6OlJlc3VsdDxjb3JlOjpyZXN1bHQ6OlJlc3VsdDxjb3Ntd2FzbV9zdGQ6OmVuY29kaW5nOjpCaW5hcnksIGNvc213YXNtX3N0ZDo6ZXJyb3JzOjpzdGRfZXJyb3I6OlN0ZEVycm9yPiwgY29zbXdhc21fc3RkOjplcnJvcnM6OnN5c3RlbV9lcnJvcjo6U3lzdGVtRXJyb3I+AAAAIgAAAAQAAAAEAAAAIwAAACQAAAAlAAAAJgAAAAAAAAABAAAAEwAAACcAAAAUAAAABAAAACgAAAAvcnVzdGMvZjNlMWE5NTRkMmVhZDRlMmZjMTk3YzdkYTdkNzFlNmM2MWJhZDE5Ni9zcmMvbGliY29yZS9vcHMvYXJpdGgucnOwCRAASAAAALsCAAAzAEGQlMAAC+EFYXR0ZW1wdCB0byBhZGQgd2l0aCBvdmVyZmxvdzw6OmNvcmU6Om1hY3Jvczo6cGFuaWMgbWFjcm9zPgAALAoQAB4AAAADAAAACgAAAFRyaWVkIHRvIHNocmluayB0byBhIGxhcmdlciBjYXBhY2l0eW1pc3NpbmcgZmllbGQgYGCAChAADwAAAI8KEAABAAAAZHVwbGljYXRlIGZpZWxkIGAAAACgChAAEQAAAI8KEAABAAAAdW5rbm93biB2YXJpYW50IGBgLCB0aGVyZSBhcmUgbm8gdmFyaWFudHMAAADEChAAEQAAANUKEAAYAAAAYCwgZXhwZWN0ZWQgxAoQABEAAAAACxAADAAAAFNvbWUiAAAABAAAAAQAAAApAAAATm9uZSIAAAAEAAAABAAAACoAAAAvdXNyL2xvY2FsL2NhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvYmFzZTY0LTAuMTEuMC9zcmMvZGVjb2RlLnJzAAAARAsQAFUAAADHAQAAHwAAAEQLEABVAAAAzQEAAB8AAABECxAAVQAAANABAAANAAAARAsQAFUAAADWAQAAHwAAAEQLEABVAAAA2QEAAA0AAABECxAAVQAAAN8BAAAfAAAARAsQAFUAAADiAQAADQAAAEQLEABVAAAA6AEAAB8AAABECxAAVQAAAOsBAAANAAAARAsQAFUAAADxAQAAHwAAAEQLEABVAAAA9AEAAA0AAABECxAAVQAAAPoBAAAfAAAARAsQAFUAAAD9AQAADQAAAEQLEABVAAAAAwIAAB8AAABECxAAVQAAAAYCAAANAAAARAsQAFUAAABtAAAALwAAAAAAAABhdHRlbXB0IHRvIG11bHRpcGx5IHdpdGggb3ZlcmZsb3cAAABECxAAVQAAAPgAAAA3AAAARAsQAFUAAAD5AAAAPgAAAEQLEABVAAAAFgEAABEAQYCawAALlRZhdHRlbXB0IHRvIHN1YnRyYWN0IHdpdGggb3ZlcmZsb3cAAABECxAAVQAAAB8BAAApAAAARAsQAFUAAAAjAQAAKQAAAEQLEABVAAAAIwEAACgAAABECxAAVQAAACgBAAARAAAARAsQAFUAAAA5AQAAJwAAAEQLEABVAAAAPAEAAAkAAABECxAAVQAAAFsBAAApAAAARAsQAFUAAABtAQAADQAAAEQLEABVAAAAdwEAABEAAABECxAAVQAAAH8BAAAVAAAARAsQAFUAAACDAQAAMQAAAGludGVybmFsIGVycm9yOiBlbnRlcmVkIHVucmVhY2hhYmxlIGNvZGU6IAAA1A0QACoAAABJbXBvc3NpYmxlOiBtdXN0IG9ubHkgaGF2ZSAwIHRvIDggaW5wdXQgYnl0ZXMgaW4gbGFzdCBjaHVuaywgd2l0aCBubyBpbnZhbGlkIGxlbmd0aHMIDhAAVAAAAEQLEABVAAAAkgEAAA4AAABECxAAVQAAAJ0BAAANAAAARAsQAFUAAACmAQAACQAAAE92ZXJmbG93IHdoZW4gY2FsY3VsYXRpbmcgb3V0cHV0IGJ1ZmZlciBsZW5ndGhJbnZhbGlkIFVURjgvdXNyL2xvY2FsL2NhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvYmFzZTY0LTAuMTEuMC9zcmMvZW5jb2RlLnJzAM4OEABVAAAALwAAABEAAABpbnRlZ2VyIG92ZXJmbG93IHdoZW4gY2FsY3VsYXRpbmcgYnVmZmVyIHNpemV1c2l6ZSBvdmVyZmxvdyB3aGVuIGNhbGN1bGF0aW5nIGJ1ZmZlciBzaXplHwAAAAgAAAAEAAAAKwAAAGludmFsaWQgYmFzZTY0OiCcDxAAEAAAAFVuc3VwcG9ydCBxdWVyeSB0eXBlOiAAALQPEAAWAAAAVW5rbm93biBzeXN0ZW0gZXJyb3LUDxAAFAAAAE5vIHN1Y2ggY29udHJhY3Q6IAAA8A8QABIAAABDYW5ub3QgcGFyc2UgcmVxdWVzdDogAAAMEBAAFgAAAEFsbG9jYXRlZCBtZW1vcnkgdG9vIHNtYWxsIHRvIGhvbGQgdGhlIGRhdGFiYXNlIHZhbHVlIGZvciB0aGUgZ2l2ZW4ga2V5LiBZb3UgY2FuIHNwZWNpZnkgY3VzdG9tIHJlc3VsdCBidWZmZXIgbGVuZ3RocyBieSB1c2luZyBFeHRlcm5hbFN0b3JhZ2UuZ2V0X3dpdGhfcmVzdWx0X2xlbmd0aCBleHBsaWNpdGVseS5FcnJvciByZWFkaW5nIGZyb20gZGF0YWJhc2UuIEVycm9yIGNvZGU6IADeEBAAKQAAAEVycm9yIHdyaXRpbmcgdG8gZGF0YWJhc2UuIEVycm9yIGNvZGU6IAAQERAAJwAAAGNhbm9uaWNhbGl6ZV9hZGRyZXNzIHJldHVybmVkIGVycm9yaHVtYW5pemVfYWRkcmVzcyByZXR1cm5lZCBlcnJvcmdlbmVyaWNfZXJybXNnaW52YWxpZF9iYXNlNjRpbnZhbGlkX3V0Zjhub3RfZm91bmRraW5kbnVsbF9wb2ludGVycGFyc2VfZXJydGFyZ2V0c2VyaWFsaXplX2VycnNvdXJjZXVuYXV0aG9yaXplZHVuZGVyZmxvd21pbnVlbmRzdWJ0cmFoZW5kQ2Fubm90IHN1YnRyYWN0ICBmcm9tIAAAAAsSEAAQAAAAGxIQAAYAAABVbmF1dGhvcml6ZWQ0EhAADAAAAEVycm9yIHNlcmlhbGl6aW5nIHR5cGUgOiAAAABIEhAAFwAAAF8SEAACAAAARXJyb3IgcGFyc2luZyBpbnRvIHR5cGUgdBIQABgAAABfEhAAAgAAAFJlY2VpdmVkIG51bGwgcG9pbnRlciwgcmVmdXNlIHRvIHVzZZwSEAAkAAAAIG5vdCBmb3VuZAAAkAgQAAAAAADIEhAACgAAAENhbm5vdCBkZWNvZGUgVVRGOCBieXRlcyBpbnRvIHN0cmluZzogAADkEhAAJgAAAEludmFsaWQgQmFzZTY0IHN0cmluZzogABQTEAAXAAAAR2VuZXJpYyBlcnJvcjogADQTEAAPAAAATnVsbFBvaW50ZXIAghEQAAsAAACQERAADgAAAJ4REAAMAAAAqhEQAAkAAAC3ERAADAAAAMMREAAJAAAA0hEQAA0AAADlERAADAAAAPEREAAJAAAAVW5kZXJmbG93YmFja3RyYWNlAAAiAAAABAAAAAQAAAAsAAAAU2VyaWFsaXplRXJyUGFyc2VFcnJOb3RGb3VuZEludmFsaWRVdGY4SW52YWxpZEJhc2U2NEdlbmVyaWNFcnJpbnZhbGlkX3JlcXVlc3RlcnJvcm5vX3N1Y2hfY29udHJhY3RhZGRydW5rbm93bnVuc3VwcG9ydGVkX3JlcXVlc3QCFBAADwAAABYUEAAQAAAAKhQQAAcAAAAxFBAAEwAAAC8AAAAIAAAABAAAADAAAAAxAAAALwAAAAgAAAAEAAAAMgAAAIgUEAAAAAAASW52YWxpZCBKU09OSlNPTiBoYXMgYSBjb21tYSBhZnRlciB0aGUgbGFzdCB2YWx1ZSBpbiBhbiBhcnJheSBvciBtYXAuSlNPTiBoYXMgbm9uLXdoaXRlc3BhY2UgdHJhaWxpbmcgY2hhcmFjdGVycyBhZnRlciB0aGUgdmFsdWUuRm91bmQgYSBsb25lIHN1cnJvZ2F0ZSwgd2hpY2ggY2FuIGV4aXN0IGluIEpTT04gYnV0IGNhbm5vdCBiZSBlbmNvZGVkIHRvIFVURi04Lk9iamVjdCBrZXkgaXMgbm90IGEgc3RyaW5nLkludmFsaWQgdW5pY29kZSBjb2RlIHBvaW50LkludmFsaWQgdHlwZUludmFsaWQgbnVtYmVyLkludmFsaWQgZXNjYXBlIHNlcXVlbmNlLkV4cGVjdGVkIHRoaXMgY2hhcmFjdGVyIHRvIHN0YXJ0IGEgSlNPTiB2YWx1ZS5FeHBlY3RlZCB0byBwYXJzZSBlaXRoZXIgYSBgdHJ1ZWAsIGBmYWxzZWAsIG9yIGEgYG51bGxgLkV4cGVjdGVkIHRoaXMgY2hhcmFjdGVyIHRvIGJlIGVpdGhlciBhIGAnLCdgIG9yIGEgYCd9J2AuRXhwZWN0ZWQgYSBsb3cgc3Vycm9nYXRlIChEQzAw4oCTREZGRikuRXhwZWN0ZWQgdGhpcyBjaGFyYWN0ZXIgdG8gYmUgZWl0aGVyIGEgYCcsJ2Agb3JhIGAnXSdgLkV4cGVjdGVkIGEgaGlnaCBzdXJyb2dhdGUgKEQ4MDDigJNEQkZGKS5FeHBlY3RlZCB0aGlzIGNoYXJhY3RlciB0byBiZSBhIGAnOidgLkVPRiB3aGlsZSBwYXJzaW5nIGEgSlNPTiB2YWx1ZS5FT0Ygd2hpbGUgcGFyc2luZyBhIHN0cmluZy5FT0Ygd2hpbGUgcGFyc2luZyBhbiBvYmplY3QuRU9GIHdoaWxlIHBhcnNpbmcgYSBsaXN0LkNvbnRyb2wgY2hhcmFjdGVyIGZvdW5kIGluIHN0cmluZy4vdXNyL2xvY2FsL2NhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvc2VyZGUtanNvbi13YXNtLTAuMi4xL3NyYy9kZS91bmVzY2FwZS5ycwAApBcQAGIAAAAlAAAAFQBBoLDAAAvxA2F0dGVtcHQgdG8gYWRkIHdpdGggb3ZlcmZsb3ekFxAAYgAAADMAAAApAAAAAAAAAGF0dGVtcHQgdG8gc3VidHJhY3Qgd2l0aCBvdmVyZmxvdwAAAKQXEABiAAAAmQAAAA4AAABOb24taGV4IEFTQ0lJIGNoYXJhY3RlciBmb3VuZC91c3IvbG9jYWwvY2FyZ28vcmVnaXN0cnkvc3JjL2dpdGh1Yi5jb20tMWVjYzYyOTlkYjllYzgyMy9zZXJkZS1qc29uLXdhc20tMC4yLjEvc3JjL2RlL21vZC5ycwAAoRgQAF0AAAAeAAAACQAAAKEYEABdAAAAewAAACsAAAChGBAAXQAAAHsAAAAgAAAAoRgQAF0AAAB8AAAAIQAAAFVua25vd24gc2VyaWFsaXphdGlvbiBlcnJvcgBAGRAAGwAAAEJ1ZmZlciBpcyBmdWxsAABkGRAADgAAAC91c3IvbG9jYWwvY2FyZ28vcmVnaXN0cnkvc3JjL2dpdGh1Yi5jb20tMWVjYzYyOTlkYjllYzgyMy9zZXJkZS1qc29uLXdhc20tMC4yLjEvc3JjL3Nlci9tb2QucnMAAHwZEABeAAAA0QAAAAkAAAAAAAAAYXR0ZW1wdCB0byBzdWJ0cmFjdCB3aXRoIG92ZXJmbG93AEGgtMAAC4skYXR0ZW1wdCB0byBhZGQgd2l0aCBvdmVyZmxvd3VzaXplIG92ZXJmbG93IHdoZW4gY2FsY3VsYXRpbmcgYjY0IGxlbmd0aC91c3IvbG9jYWwvY2FyZ28vcmVnaXN0cnkvc3JjL2dpdGh1Yi5jb20tMWVjYzYyOTlkYjllYzgyMy9iYXNlNjQtMC4xMS4wL3NyYy9lbmNvZGUucnMAZhoQAFUAAAC7AAAAMwAAAGYaEABVAAAAvAAAADoAAABmGhAAVQAAAPwAAAAYAAAAZhoQAFUAAAABAQAALwAAAGYaEABVAAAAAgEAADYAAABmGhAAVQAAABABAAAuAAAAZhoQAFUAAAAQAQAACQAAAGYaEABVAAAAEgEAAA8AAABmGhAAVQAAABEBAAAJAAAAZhoQAFUAAAAUAQAACQAAAGYaEABVAAAAFgEAAAkAAABmGhAAVQAAABgBAAAuAAAAZhoQAFUAAAAYAQAACQAAAGYaEABVAAAAGQEAAAkAAABpbnRlcm5hbCBlcnJvcjogZW50ZXJlZCB1bnJlYWNoYWJsZSBjb2RlOiAAAJwbEAAqAAAASW1wb3NzaWJsZSByZW1haW5kZXLQGxAAFAAAAGYaEABVAAAALwEAABYAAABmGhAAVQAAAEABAAAJAAAASW52YWxpZCBsYXN0IHN5bWJvbCAsIG9mZnNldCAuAAAMHBAAFAAAACAcEAAJAAAAKRwQAAEAAABFbmNvZGVkIHRleHQgY2Fubm90IGhhdmUgYSA2LWJpdCByZW1haW5kZXIuAEQcEAArAAAASW52YWxpZCBieXRlIAAAAHgcEAANAAAAIBwQAAkAAAApHBAAAQAAAE92ZXJmbG93IHdoZW4gY2FsY3VsYXRpbmcgbnVtYmVyIG9mIGNodW5rcyBpbiBpbnB1dC4vMDEyMzQ1Njc4OUFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6MDEyMzQ1Njc4OS1fQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrL/////////////////////////////////////////////////////////////8AAQIDBAUGBwgJCgv/////////DA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCX///////8mJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8+//80NTY3ODk6Ozw9/////////wABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ/////z//GhscHR4fICEiIyQlJicoKSorLC0uLzAxMjP//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////z7///8/NDU2Nzg5Ojs8Pf////////8AAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGf///////xobHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIz/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBTHRAAEx0QANMcEACTHxAAkx4QAJMdEAA2AAAABAAAAAQAAAA3AAAAOAAAACgpL3J1c3RjL2YzZTFhOTU0ZDJlYWQ0ZTJmYzE5N2M3ZGE3ZDcxZTZjNjFiYWQxOTYvc3JjL2xpYmNvcmUvbWFjcm9zL21vZC5ycwDCIBAASQAAAA8AAAAoAAAAY2FsbGVkIGBPcHRpb246OnVud3JhcCgpYCBvbiBhIGBOb25lYCB2YWx1ZVJVU1RfQkFDS1RSQUNFMQAAVSEQAAEAAABSVVNUX0xJQl9CQUNLVFJBQ0VCYWNrdHJhY2UANgAAAAQAAAAEAAAAOQAAADwAAAAIAAAABAAAAD0AAAA+AAAAPAAAAAgAAAAEAAAAPwAAAGBvbmUgb2YgsSEQAAcAAAAsIAAAwCEQAAIAAACwIRAAAQAAALAhEAABAAAAYCBvciBgAACwIRAAAQAAANwhEAAGAAAAsCEQAAEAAAAvdXNyL2xvY2FsL2NhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvc2VyZGUtMS4wLjEwNC9zcmMvZGUvbW9kLnJzAAAA/CEQAFUAAADVCAAAEgAAAGV4cGxpY2l0IHBhbmljAABDAAAABAAAAAQAAABEAAAARQAAAEYAAABDAAAABAAAAAQAAABHAAAAYWxyZWFkeSBib3Jyb3dlZGFscmVhZHkgbXV0YWJseSBib3Jyb3dlZC9ydXN0Yy9mM2UxYTk1NGQyZWFkNGUyZmMxOTdjN2RhN2Q3MWU2YzYxYmFkMTk2L3NyYy9saWJjb3JlL21hY3Jvcy9tb2QucnNhc3NlcnRpb24gZmFpbGVkOiBgKGxlZnQgPT0gcmlnaHQpYAogIGxlZnQ6IGBgLAogcmlnaHQ6IGBgAA0jEAAtAAAAOiMQAAwAAABGIxAAAQAAAMQiEABJAAAADwAAACgAAABIAAAAAAAAAAEAAABJAAAAYDogAA0jEAAtAAAAOiMQAAwAAACAIxAAAwAAAGNhbGxlZCBgT3B0aW9uOjp1bndyYXAoKWAgb24gYSBgTm9uZWAgdmFsdWUASAAAAAAAAAABAAAASgAAAEsAAAAQAAAABAAAAEwAAABIAAAAAAAAAAEAAABNAAAAY2FsbGVkIGBSZXN1bHQ6OnVud3JhcCgpYCBvbiBhbiBgRXJyYCB2YWx1ZQBOAAAACAAAAAQAAABPAAAAPDo6Y29yZTo6bWFjcm9zOjpwYW5pYyBtYWNyb3M+AAA0JBAAHgAAAAMAAAAKAAAAVHJpZWQgdG8gc2hyaW5rIHRvIGEgbGFyZ2VyIGNhcGFjaXR5QwAAAAQAAAAEAAAAQQAAAHNyYy9saWJzdGQvdGhyZWFkL21vZC5yc5gkEAAYAAAAiQMAABMAAABpbmNvbnNpc3RlbnQgcGFyayBzdGF0ZQACAAAAcGFyayBzdGF0ZSBjaGFuZ2VkIHVuZXhwZWN0ZWRseQDcJBAAHwAAAJgkEAAYAAAAhgMAAA0AAACYJBAAGAAAAB8EAAARAAAAZmFpbGVkIHRvIGdlbmVyYXRlIHVuaXF1ZSB0aHJlYWQgSUQ6IGJpdHNwYWNlIGV4aGF1c3RlZHRocmVhZCBuYW1lIG1heSBub3QgY29udGFpbiBpbnRlcmlvciBudWxsIGJ5dGVzAACYJBAAGAAAAJQEAAASAAAAaW5jb25zaXN0ZW50IHN0YXRlIGluIHVucGFya3NyYy9saWJzdGQvc3luYy9jb25kdmFyLnJzAAC4JRAAGgAAAEgCAAASAAAAYXR0ZW1wdGVkIHRvIHVzZSBhIGNvbmRpdGlvbiB2YXJpYWJsZSB3aXRoIHR3byBtdXRleGVzc3JjL2xpYnN0ZC9zeW5jL29uY2UucnMAAAAaJhAAFwAAAKgBAAAVAAAAYXNzZXJ0aW9uIGZhaWxlZDogc3RhdGVfYW5kX3F1ZXVlICYgU1RBVEVfTUFTSyA9PSBSVU5OSU5HAAAAGiYQABcAAACMAQAAFQAAAE9uY2UgaW5zdGFuY2UgaGFzIHByZXZpb3VzbHkgYmVlbiBwb2lzb25lZAAAGiYQABcAAADpAQAACQAAAFBvaXNvbkVycm9yIHsgaW5uZXI6IC4uIH0AAABQAAAAEAAAAAQAAABRAAAAUgAAAFMAAAAMAAAABAAAAFQAAABVAAAACAAAAAQAAABWAAAAVwAAAFUAAAAIAAAABAAAAFgAAABOdWxFcnJvckMAAAAEAAAABAAAAFkAAABzcmMvbGlic3RkL3N5cy93YXNtL2NvbmR2YXIucnMAAEgnEAAeAAAAFwAAAAkAAABjYW4ndCBibG9jayB3aXRoIHdlYiBhc3NlbWJseXNyYy9saWJzdGQvc3lzL3dhc20vbXV0ZXgucnMAAACVJxAAHAAAABYAAAAJAAAAY2Fubm90IHJlY3Vyc2l2ZWx5IGFjcXVpcmUgbXV0ZXhaAAAABAAAAAQAAABbAAAAXAAAAF0AAABaAAAABAAAAAQAAABeAAAAL3J1c3RjL2YzZTFhOTU0ZDJlYWQ0ZTJmYzE5N2M3ZGE3ZDcxZTZjNjFiYWQxOTYvc3JjL2xpYmNvcmUvZm10L21vZC5ycwAADCgQAEYAAABaAQAAEwAAAF8AAAAAAAAAAQAAABMAAABzcmMvbGliYWxsb2MvcmF3X3ZlYy5yc2NhcGFjaXR5IG92ZXJmbG93dCgQABcAAAAJAwAABQAAAGEgZm9ybWF0dGluZyB0cmFpdCBpbXBsZW1lbnRhdGlvbiByZXR1cm5lZCBhbiBlcnJvckZyb21VdGY4RXJyb3JieXRlcwAAAFoAAAAEAAAABAAAAGAAAABlcnJvcgAAAFoAAAAEAAAABAAAAGEAAABgbnVtYmVyIHdvdWxkIGJlIHplcm8gZm9yIG5vbi16ZXJvIHR5cGVudW1iZXIgdG9vIHNtYWxsIHRvIGZpdCBpbiB0YXJnZXQgdHlwZW51bWJlciB0b28gbGFyZ2UgdG8gZml0IGluIHRhcmdldCB0eXBlaW52YWxpZCBkaWdpdCBmb3VuZCBpbiBzdHJpbmdjYW5ub3QgcGFyc2UgaW50ZWdlciBmcm9tIGVtcHR5IHN0cmluZy4u0ikQAAIAAABCb3Jyb3dFcnJvckJvcnJvd011dEVycm9yAAAAZwAAAAAAAAABAAAAaAAAAGluZGV4IG91dCBvZiBib3VuZHM6IHRoZSBsZW4gaXMgIGJ1dCB0aGUgaW5kZXggaXMgAAAIKhAAIAAAACgqEAASAAAAY2FsbGVkIGBPcHRpb246OnVud3JhcCgpYCBvbiBhIGBOb25lYCB2YWx1ZXNyYy9saWJjb3JlL29wdGlvbi5yc3cqEAAVAAAAfQEAABUAAAAcKRAAAAAAAHcqEAAVAAAApAQAAAUAAAA6IAAAHCkQAAAAAAC0KhAA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AGAAAAAAAAAAAKDDB/jnDwAAADwAABwAAAAAAAAA////////f/j//////x8gABAAAPj+/wAAf///+dsHAAAAAAAAAPAAAAAAfwAAAAAA8AcAQcz1wAALEv///////////////////////wBBwPbAAAsO//////////////////8AQfD2wAALAvgDAEGS98AACwf+/////7+2AEGi98AACw3/BwAAAAAA+P//AAABAEG698AACxDAn589AAAAAAIAAAD///8HAEHU98AAC9EKwP8BAAAAAAAA+A8gwDQQAEoAAAAQNxAAAAIAABA5EAA6AAAAAAECAwQFBgcICQgKCwwNDg8QERITFAIVFhcYGRobHB0eHyACAgICAgICAgICIQICAgICAgICAgICAgICIiMkJSYCJwIoAgICKSorAiwtLi8wAgIxAgICMgICAgICAgICMwICNAICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICNQI2AjcCAgICAgICAjgCOQICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICOjs8AgICAj0CAj4/QEFCQ0RFRgICAkcCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICSAICAgICAgICAgICSQICAgICOwIAAQICAgIDAgICAgQCBQYCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICbQAAAAQAAAAEAAAAcgAAAFNvbWVOb25lVXRmOEVycm9ydmFsaWRfdXBfdG9lcnJvcl9sZW4AAABtAAAABAAAAAQAAABzAAAARXJyb3IAQaCGwQALAQE=" +} diff --git a/packages/cosmwasm/src/testdata/cosmoshub.json b/packages/cosmwasm/src/testdata/cosmoshub.json new file mode 100644 index 00000000..cb33539c --- /dev/null +++ b/packages/cosmwasm/src/testdata/cosmoshub.json @@ -0,0 +1,44 @@ +{ + "//source": "https://hubble.figment.network/cosmos/chains/cosmoshub-3/blocks/415777/transactions/2BD600EA6090FC75FD844CA73542CC90A828770F4C01C5B483C3C1C43CCB65F4?format=json", + "tx": { + "type": "cosmos-sdk/StdTx", + "value": { + "msg": [ + { + "type": "cosmos-sdk/MsgSend", + "value": { + "from_address": "cosmos1txqfn5jmcts0x0q7krdxj8tgf98tj0965vqlmq", + "to_address": "cosmos1nynns8ex9fq6sjjfj8k79ymkdz4sqth06xexae", + "amount": [ + { + "denom": "uatom", + "amount": "35997500" + } + ] + } + } + ], + "fee": { + "amount": [ + { + "denom": "uatom", + "amount": "2500" + } + ], + "gas": "100000" + }, + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A5qFcJBJvEK/fOmEAY0DHNWwSRZ9TEfNZyH8VoVvDtAq" + }, + "signature": "NK1Oy4EUGAsoC03c1wi9GG03JC/39LEdautC5Jk643oIbEPqeXHMwaqbdvO/Jws0X/NAXaN8SAy2KNY5Qml+5Q==" + } + ], + "memo": "" + } + }, + "tx_data": "ygEoKBapCkOoo2GaChRZgJnSW8Lg8zwesNppHWhJTrk8uhIUmSc4HyYqQahKSZHt4pN2aKsALu8aEQoFdWF0b20SCDM1OTk3NTAwEhMKDQoFdWF0b20SBDI1MDAQoI0GGmoKJuta6YchA5qFcJBJvEK/fOmEAY0DHNWwSRZ9TEfNZyH8VoVvDtAqEkA0rU7LgRQYCygLTdzXCL0YbTckL/f0sR1q60LkmTrjeghsQ+p5cczBqpt2878nCzRf80Bdo3xIDLYo1jlCaX7l", + "id": "2BD600EA6090FC75FD844CA73542CC90A828770F4C01C5B483C3C1C43CCB65F4" +} diff --git a/packages/cosmwasm/src/testdata/txresponse1.json b/packages/cosmwasm/src/testdata/txresponse1.json new file mode 100644 index 00000000..703ea696 --- /dev/null +++ b/packages/cosmwasm/src/testdata/txresponse1.json @@ -0,0 +1,57 @@ +{ + "height": "15888", + "txhash": "672DEDE8EF4DE8B5818959F417CCA357079D4D7A19C4B65443C7FBF8176AABF9", + "raw_log": "[{\"msg_index\":0,\"log\":\"\",\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2\"},{\"key\":\"amount\",\"value\":\"75000ucosm\"}]}]}]", + "logs": [ + { + "msg_index": 0, + "log": "", + "events": [ + { + "type": "message", + "attributes": [ + { "key": "action", "value": "send" }, + { "key": "sender", "value": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6" }, + { "key": "module", "value": "bank" } + ] + }, + { + "type": "transfer", + "attributes": [ + { "key": "recipient", "value": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2" }, + { "key": "amount", "value": "75000ucosm" } + ] + } + ] + } + ], + "gas_wanted": "200000", + "gas_used": "65407", + "tx": { + "type": "cosmos-sdk/StdTx", + "value": { + "msg": [ + { + "type": "cosmos-sdk/MsgSend", + "value": { + "from_address": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + "to_address": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2", + "amount": [{ "denom": "ucosm", "amount": "75000" }] + } + } + ], + "fee": { "amount": [{ "denom": "ucosm", "amount": "5000" }], "gas": "200000" }, + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ" + }, + "signature": "US7oH8S/8TxVrtBQkOhHxAM+oDB2spNAEawgh6H8CCFLRMOJK+uvQZZ6ceUgUsvDbxwCz7re1RU272fymMYRZQ==" + } + ], + "memo": "My first payment" + } + }, + "timestamp": "2020-02-14T11:25:55Z" +} diff --git a/packages/cosmwasm/src/testdata/txresponse2.json b/packages/cosmwasm/src/testdata/txresponse2.json new file mode 100644 index 00000000..0d1a37f9 --- /dev/null +++ b/packages/cosmwasm/src/testdata/txresponse2.json @@ -0,0 +1,57 @@ +{ + "height": "16456", + "txhash": "7BFE4B93AF190F60132C62D08FDF50BE462FBCE374EB13D3FD0C32461E771EC0", + "raw_log": "[{\"msg_index\":0,\"log\":\"\",\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2\"},{\"key\":\"amount\",\"value\":\"75000ucosm\"}]}]}]", + "logs": [ + { + "msg_index": 0, + "log": "", + "events": [ + { + "type": "message", + "attributes": [ + { "key": "action", "value": "send" }, + { "key": "sender", "value": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6" }, + { "key": "module", "value": "bank" } + ] + }, + { + "type": "transfer", + "attributes": [ + { "key": "recipient", "value": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2" }, + { "key": "amount", "value": "75000ucosm" } + ] + } + ] + } + ], + "gas_wanted": "200000", + "gas_used": "65407", + "tx": { + "type": "cosmos-sdk/StdTx", + "value": { + "msg": [ + { + "type": "cosmos-sdk/MsgSend", + "value": { + "from_address": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + "to_address": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2", + "amount": [{ "denom": "ucosm", "amount": "75000" }] + } + } + ], + "fee": { "amount": [{ "denom": "ucosm", "amount": "5000" }], "gas": "200000" }, + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ" + }, + "signature": "ltvd9Rb3RF4zjbUVrpDpkok34g+py7XR8ZcM0tZUYRxxVdcMEin010x+ZFd/mOuutPj9fDmSENnienc/yi4msw==" + } + ], + "memo": "My first payment" + } + }, + "timestamp": "2020-02-14T11:35:41Z" +} diff --git a/packages/cosmwasm/src/testdata/txresponse3.json b/packages/cosmwasm/src/testdata/txresponse3.json new file mode 100644 index 00000000..8ffd0727 --- /dev/null +++ b/packages/cosmwasm/src/testdata/txresponse3.json @@ -0,0 +1,57 @@ +{ + "height": "20730", + "txhash": "625BC75E697F73DA037387C34002BB2F682E7ACDCC4E015D3E90420516C6D0C8", + "raw_log": "[{\"msg_index\":0,\"log\":\"\",\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2\"},{\"key\":\"amount\",\"value\":\"75000ucosm\"}]}]}]", + "logs": [ + { + "msg_index": 0, + "log": "", + "events": [ + { + "type": "message", + "attributes": [ + { "key": "action", "value": "send" }, + { "key": "sender", "value": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6" }, + { "key": "module", "value": "bank" } + ] + }, + { + "type": "transfer", + "attributes": [ + { "key": "recipient", "value": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2" }, + { "key": "amount", "value": "75000ucosm" } + ] + } + ] + } + ], + "gas_wanted": "200000", + "gas_used": "65407", + "tx": { + "type": "cosmos-sdk/StdTx", + "value": { + "msg": [ + { + "type": "cosmos-sdk/MsgSend", + "value": { + "from_address": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + "to_address": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2", + "amount": [{ "denom": "ucosm", "amount": "75000" }] + } + } + ], + "fee": { "amount": [{ "denom": "ucosm", "amount": "5000" }], "gas": "200000" }, + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ" + }, + "signature": "eOFGl1tIHDMv3JdCK9fRSikVbYUD8+B0ksb3dJFya8MPYgpEpdSA7zZc+5n/cW6LR/BJdib4nqmJQv1yD9lm3g==" + } + ], + "memo": "My first payment" + } + }, + "timestamp": "2020-02-14T12:48:56Z" +} diff --git a/packages/cosmwasm/src/testutils.spec.ts b/packages/cosmwasm/src/testutils.spec.ts new file mode 100644 index 00000000..888f5954 --- /dev/null +++ b/packages/cosmwasm/src/testutils.spec.ts @@ -0,0 +1,77 @@ +import { Random } from "@iov/crypto"; +import { Bech32, Encoding } from "@iov/encoding"; + +import hackatom from "./testdata/contract.json"; + +export function getHackatom(): Uint8Array { + return Encoding.fromBase64(hackatom.data); +} + +export function makeRandomAddress(): string { + return Bech32.encode("cosmos", Random.getBytes(20)); +} + +export const nonNegativeIntegerMatcher = /^[0-9]+$/; +export const tendermintIdMatcher = /^[0-9A-F]{64}$/; +export const tendermintOptionalIdMatcher = /^([0-9A-F]{64}|)$/; +export const tendermintAddressMatcher = /^[0-9A-F]{40}$/; +export const tendermintShortHashMatcher = /^[0-9a-f]{40}$/; +export const semverMatcher = /^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$/; + +// 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}$/; + +/** Deployed as part of scripts/wasmd/init.sh */ +export const deployedErc20 = { + codeId: 1, + source: "https://crates.io/api/v1/crates/cw-erc20/0.4.0/download", + builder: "cosmwasm/rust-optimizer:0.8.0", + checksum: "41b3bafd7f9a3870bbfb0a0620508df564c52499cdcdc67bf9df72262f3958a6", + instances: [ + "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", // HASH + "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd", // ISA + "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c", // JADE + ], +}; + +export const wasmd = { + endpoint: "http://localhost:1317", + chainId: "testing", +}; + +export const faucet = { + 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", + pubkey: { + type: "tendermint/PubKeySecp256k1", + value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", + }, + address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", +}; + +/** Unused account */ +export const unused = { + pubkey: { + type: "tendermint/PubKeySecp256k1", + value: "ArkCaFUJ/IH+vKBmNRCdUVl3mCAhbopk9jjW4Ko4OfRQ", + }, + address: "cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u", + accountNumber: 9, + sequence: 0, +}; + +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]; +} diff --git a/packages/cosmwasm/src/types.ts b/packages/cosmwasm/src/types.ts new file mode 100644 index 00000000..bfc8dbcf --- /dev/null +++ b/packages/cosmwasm/src/types.ts @@ -0,0 +1,173 @@ +import { Coin } from "@cosmwasm/sdk38"; +import { Encoding } from "@iov/encoding"; + +const { fromBase64, fromHex } = Encoding; + +/** An Amino/Cosmos SDK StdTx */ +export interface StdTx { + readonly msg: ReadonlyArray; + readonly fee: StdFee; + readonly signatures: ReadonlyArray; + readonly memo: string | undefined; +} + +export function isStdTx(txValue: unknown): txValue is StdTx { + const { memo, msg, fee, signatures } = txValue as StdTx; + return ( + typeof memo === "string" && Array.isArray(msg) && typeof fee === "object" && Array.isArray(signatures) + ); +} + +export interface CosmosSdkTx { + readonly type: string; + readonly value: StdTx; +} + +interface MsgTemplate { + readonly type: string; + readonly value: any; +} + +/** A Cosmos SDK token transfer message */ +export interface MsgSend extends MsgTemplate { + readonly type: "cosmos-sdk/MsgSend"; + readonly value: { + /** Bech32 account address */ + readonly from_address: string; + /** Bech32 account address */ + readonly to_address: string; + readonly amount: ReadonlyArray; + }; +} + +/** + * Uploads Wam code to the chain + * + * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L17 + */ +export interface MsgStoreCode extends MsgTemplate { + readonly type: "wasm/store-code"; + readonly value: { + /** Bech32 account address */ + readonly sender: string; + /** Base64 encoded Wasm */ + readonly wasm_byte_code: string; + /** A valid URI reference to the contract's source code. Can be empty. */ + readonly source: string; + /** A docker tag. Can be empty. */ + readonly builder: string; + }; +} + +/** + * Creates an instance of contract that was uploaded before. + * + * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L73 + */ +export interface MsgInstantiateContract extends MsgTemplate { + readonly type: "wasm/instantiate"; + readonly 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; + /** Init message as JavaScript object */ + readonly init_msg: any; + readonly init_funds: ReadonlyArray; + }; +} + +/** + * Creates an instance of contract that was uploaded before. + * + * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L103 + */ +export interface MsgExecuteContract extends MsgTemplate { + readonly type: "wasm/execute"; + readonly value: { + /** Bech32 account address */ + readonly sender: string; + /** Bech32 account address */ + readonly contract: string; + /** Handle message as JavaScript object */ + readonly msg: any; + readonly sent_funds: ReadonlyArray; + }; +} + +export type Msg = MsgSend | MsgStoreCode | MsgInstantiateContract | MsgExecuteContract | MsgTemplate; + +export function isMsgSend(msg: Msg): msg is MsgSend { + return (msg as MsgSend).type === "cosmos-sdk/MsgSend"; +} + +export function isMsgStoreCode(msg: Msg): msg is MsgStoreCode { + return (msg as MsgStoreCode).type === "wasm/store-code"; +} + +export function isMsgInstantiateContract(msg: Msg): msg is MsgInstantiateContract { + return (msg as MsgInstantiateContract).type === "wasm/instantiate"; +} + +export function isMsgExecuteContract(msg: Msg): msg is MsgExecuteContract { + return (msg as MsgExecuteContract).type === "wasm/execute"; +} + +export interface StdFee { + readonly amount: ReadonlyArray; + readonly gas: string; +} + +export interface StdSignature { + readonly pub_key: PubKey; + readonly signature: string; +} + +export interface PubKey { + // type is one of the strings defined in pubkeyTypes + // I don't use a string literal union here as that makes trouble with json test data: + // https://github.com/confio/cosmwasm-js/pull/44#pullrequestreview-353280504 + readonly type: string; + // Value field is base64-encoded in all cases + // Note: if type is Secp256k1, this must contain a COMPRESSED pubkey - to encode from bcp/keycontrol land, you must compress it first + readonly value: string; +} + +export const pubkeyType = { + /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/ed25519/ed25519.go#L22 */ + secp256k1: "tendermint/PubKeySecp256k1" as const, + /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/secp256k1/secp256k1.go#L23 */ + ed25519: "tendermint/PubKeyEd25519" as const, + /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12 */ + sr25519: "tendermint/PubKeySr25519" as const, +}; + +export const pubkeyTypes: readonly string[] = [pubkeyType.secp256k1, pubkeyType.ed25519, pubkeyType.sr25519]; + +export interface WasmData { + // key is hex-encoded + readonly key: string; + // value is base64 encoded + readonly val: string; +} + +// Model is a parsed WasmData object +export interface Model { + readonly key: Uint8Array; + readonly val: Uint8Array; +} + +export function parseWasmData({ key, val }: WasmData): Model { + return { + key: fromHex(key), + val: fromBase64(val), + }; +} + +/** + * An object containing a parsed JSON document. The result of JSON.parse(). + * This doen't privide any type safety over `any` but expresses intent in the code. + */ +export type JsonObject = any; diff --git a/packages/cosmwasm/tsconfig.json b/packages/cosmwasm/tsconfig.json new file mode 100644 index 00000000..167e8c02 --- /dev/null +++ b/packages/cosmwasm/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "build", + "declarationDir": "build/types", + "rootDir": "src" + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/cosmwasm/typedoc.js b/packages/cosmwasm/typedoc.js new file mode 100644 index 00000000..e2387c7d --- /dev/null +++ b/packages/cosmwasm/typedoc.js @@ -0,0 +1,14 @@ +const packageJson = require("./package.json"); + +module.exports = { + src: ["./src"], + out: "docs", + exclude: "**/*.spec.ts", + target: "es6", + name: `${packageJson.name} Documentation`, + readme: "README.md", + mode: "file", + excludeExternals: true, + excludeNotExported: true, + excludePrivate: true, +}; diff --git a/packages/cosmwasm/types/builder.d.ts b/packages/cosmwasm/types/builder.d.ts new file mode 100644 index 00000000..68e18c0a --- /dev/null +++ b/packages/cosmwasm/types/builder.d.ts @@ -0,0 +1 @@ +export declare function isValidBuilder(builder: string): boolean; diff --git a/packages/cosmwasm/types/cosmwasmclient.d.ts b/packages/cosmwasm/types/cosmwasmclient.d.ts new file mode 100644 index 00000000..3b57ffde --- /dev/null +++ b/packages/cosmwasm/types/cosmwasmclient.d.ts @@ -0,0 +1,175 @@ +import { Coin } from "@cosmwasm/sdk38"; +import { Log } from "./logs"; +import { BroadcastMode, RestClient } from "./restclient"; +import { CosmosSdkTx, JsonObject, PubKey, StdTx } from "./types"; +export interface GetNonceResult { + readonly accountNumber: number; + readonly sequence: number; +} +export interface Account { + /** Bech32 account address */ + readonly address: string; + readonly balance: ReadonlyArray; + readonly pubkey: PubKey | undefined; + readonly accountNumber: number; + readonly sequence: number; +} +export interface PostTxResult { + readonly logs: readonly Log[]; + readonly rawLog: string; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; +} +export interface SearchByIdQuery { + readonly id: string; +} +export interface SearchByHeightQuery { + readonly height: number; +} +export interface SearchBySentFromOrToQuery { + readonly sentFromOrTo: string; +} +/** + * This query type allows you to pass arbitrary key/value pairs to the backend. It is + * more powerful and slightly lower level than the other search options. + */ +export interface SearchByTagsQuery { + readonly tags: readonly { + readonly key: string; + readonly value: string; + }[]; +} +export declare type SearchTxQuery = + | SearchByIdQuery + | SearchByHeightQuery + | SearchBySentFromOrToQuery + | SearchByTagsQuery; +export interface SearchTxFilter { + readonly minHeight?: number; + readonly maxHeight?: number; +} +export interface Code { + readonly id: number; + /** Bech32 account address */ + readonly creator: string; + /** Hex-encoded sha256 hash of the code stored here */ + readonly checksum: string; + readonly source?: string; + readonly builder?: string; +} +export interface CodeDetails extends Code { + /** The original wasm bytes */ + readonly data: Uint8Array; +} +export interface Contract { + readonly address: string; + readonly codeId: number; + /** Bech32 account address */ + readonly creator: string; + readonly label: string; +} +export interface ContractDetails extends Contract { + /** Argument passed on initialization of the contract */ + readonly initMsg: object; +} +/** A transaction that is indexed as part of the transaction history */ +export interface IndexedTx { + readonly height: number; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly hash: string; + /** Transaction execution error code. 0 on success. */ + readonly code: number; + readonly rawLog: string; + readonly logs: readonly Log[]; + readonly tx: CosmosSdkTx; + /** The gas limit as set by the user */ + readonly gasWanted?: number; + /** The gas used by the execution */ + readonly gasUsed?: number; + /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ + readonly timestamp: string; +} +export interface BlockHeader { + readonly version: { + readonly block: string; + readonly app: string; + }; + readonly height: number; + readonly chainId: string; + /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ + readonly time: string; +} +export interface Block { + /** The ID is a hash of the block header (uppercase hex) */ + readonly id: string; + readonly header: BlockHeader; + /** Array of raw transactions */ + readonly txs: ReadonlyArray; +} +/** Use for testing only */ +export interface PrivateCosmWasmClient { + readonly restClient: RestClient; +} +export declare class CosmWasmClient { + protected readonly restClient: RestClient; + /** Any address the chain considers valid (valid bech32 with proper prefix) */ + protected anyValidAddress: string | undefined; + private readonly codesCache; + private chainId; + /** + * Creates a new client to interact with a CosmWasm blockchain. + * + * This instance does a lot of caching. In order to benefit from that you should try to use one instance + * for the lifetime of your application. When switching backends, a new instance must be created. + * + * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) + * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns + */ + constructor(apiUrl: string, broadcastMode?: BroadcastMode); + getChainId(): Promise; + getHeight(): Promise; + /** + * Returns a 32 byte upper-case hex transaction hash (typically used as the transaction ID) + */ + getIdentifier(tx: CosmosSdkTx): Promise; + /** + * Returns account number and sequence. + * + * Throws if the account does not exist on chain. + * + * @param address returns data for this address. When unset, the client's sender adddress is used. + */ + getNonce(address: string): Promise; + getAccount(address: string): Promise; + /** + * Gets block header and meta + * + * @param height The height of the block. If undefined, the latest height is used. + */ + getBlock(height?: number): Promise; + searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise; + postTx(tx: StdTx): Promise; + getCodes(): Promise; + getCodeDetails(codeId: number): Promise; + getContracts(codeId: number): Promise; + /** + * Throws an error if no contract was found at the address + */ + getContract(address: string): Promise; + /** + * 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. + */ + queryContractRaw(address: string, key: Uint8Array): Promise; + /** + * 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. + */ + queryContractSmart(address: string, queryMsg: object): Promise; + private txsQuery; +} diff --git a/packages/cosmwasm/types/index.d.ts b/packages/cosmwasm/types/index.d.ts new file mode 100644 index 00000000..1241731b --- /dev/null +++ b/packages/cosmwasm/types/index.d.ts @@ -0,0 +1,33 @@ +import * as logs from "./logs"; +import * as types from "./types"; +export { logs, types }; +export { BroadcastMode, RestClient, TxsResponse } from "./restclient"; +export { + Account, + Block, + BlockHeader, + Code, + CodeDetails, + Contract, + ContractDetails, + CosmWasmClient, + GetNonceResult, + IndexedTx, + PostTxResult, + SearchByHeightQuery, + SearchByIdQuery, + SearchBySentFromOrToQuery, + SearchByTagsQuery, + SearchTxQuery, + SearchTxFilter, +} from "./cosmwasmclient"; +export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; +export { + ExecuteResult, + FeeTable, + InstantiateResult, + SigningCallback, + SigningCosmWasmClient, + UploadMeta, + UploadResult, +} from "./signingcosmwasmclient"; diff --git a/packages/cosmwasm/types/logs.d.ts b/packages/cosmwasm/types/logs.d.ts new file mode 100644 index 00000000..2e1decb3 --- /dev/null +++ b/packages/cosmwasm/types/logs.d.ts @@ -0,0 +1,28 @@ +export interface Attribute { + readonly key: string; + readonly value: string; +} +export interface Event { + readonly type: string; + readonly attributes: readonly Attribute[]; +} +export interface Log { + readonly msg_index: number; + readonly log: string; + readonly events: readonly Event[]; +} +export declare function parseAttribute(input: unknown): Attribute; +export declare function parseEvent(input: unknown): Event; +export declare function parseLog(input: unknown): Log; +export declare function parseLogs(input: unknown): readonly Log[]; +/** + * Searches in logs for the first event of the given event type and in that event + * for the first first attribute with the given attribute key. + * + * Throws if the attribute was not found. + */ +export declare function findAttribute( + logs: readonly Log[], + eventType: "message" | "transfer", + attrKey: string, +): Attribute; diff --git a/packages/cosmwasm/types/pubkey.d.ts b/packages/cosmwasm/types/pubkey.d.ts new file mode 100644 index 00000000..b6ebdebc --- /dev/null +++ b/packages/cosmwasm/types/pubkey.d.ts @@ -0,0 +1,4 @@ +import { PubKey } from "./types"; +export declare function encodeSecp256k1Pubkey(pubkey: Uint8Array): PubKey; +export declare function decodeBech32Pubkey(bechEncoded: string): PubKey; +export declare function encodeBech32Pubkey(pubkey: PubKey, prefix: string): string; diff --git a/packages/cosmwasm/types/restclient.d.ts b/packages/cosmwasm/types/restclient.d.ts new file mode 100644 index 00000000..2c914be4 --- /dev/null +++ b/packages/cosmwasm/types/restclient.d.ts @@ -0,0 +1,231 @@ +import { Coin } from "@cosmwasm/sdk38"; +import { CosmosSdkTx, JsonObject, Model, StdTx } from "./types"; +export interface CosmosSdkAccount { + /** Bech32 account address */ + readonly address: string; + readonly coins: ReadonlyArray; + /** Bech32 encoded pubkey */ + readonly public_key: string; + readonly account_number: number; + readonly sequence: number; +} +export interface NodeInfo { + readonly protocol_version: { + readonly p2p: string; + readonly block: string; + readonly app: string; + }; + readonly id: string; + readonly listen_addr: string; + readonly network: string; + readonly version: string; + readonly channels: string; + readonly moniker: string; + readonly other: { + readonly tx_index: string; + readonly rpc_address: string; + }; +} +export interface ApplicationVersion { + readonly name: string; + readonly server_name: string; + readonly client_name: string; + readonly version: string; + readonly commit: string; + readonly build_tags: string; + readonly go: string; +} +export interface NodeInfoResponse { + readonly node_info: NodeInfo; + readonly application_version: ApplicationVersion; +} +export interface BlockId { + readonly hash: string; +} +export interface BlockHeader { + readonly version: { + readonly block: string; + readonly app: string; + }; + readonly height: string; + readonly chain_id: string; + /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ + readonly time: string; + readonly last_commit_hash: string; + readonly last_block_id: BlockId; + /** Can be empty */ + readonly data_hash: string; + readonly validators_hash: string; + readonly next_validators_hash: string; + readonly consensus_hash: string; + readonly app_hash: string; + /** Can be empty */ + readonly last_results_hash: string; + /** Can be empty */ + readonly evidence_hash: string; + readonly proposer_address: string; +} +export interface Block { + readonly header: BlockHeader; + readonly data: { + /** Array of base64 encoded transactions */ + readonly txs: ReadonlyArray | null; + }; +} +export interface BlockResponse { + readonly block_id: BlockId; + readonly block: Block; +} +interface AuthAccountsResponse { + readonly height: string; + readonly result: { + readonly type: "cosmos-sdk/Account"; + readonly value: CosmosSdkAccount; + }; +} +declare type WasmResponse = WasmSuccess | WasmError; +interface WasmSuccess { + readonly height: string; + readonly result: T; +} +interface WasmError { + readonly error: string; +} +export interface TxsResponse { + readonly height: string; + readonly txhash: string; + /** 🤷‍♂️ */ + readonly codespace?: string; + /** Falsy when transaction execution succeeded. Contains error code on error. */ + readonly code?: number; + readonly raw_log: string; + readonly logs?: object; + readonly tx: CosmosSdkTx; + /** The gas limit as set by the user */ + readonly gas_wanted?: string; + /** The gas used by the execution */ + readonly gas_used?: string; + readonly timestamp: string; +} +interface SearchTxsResponse { + readonly total_count: string; + readonly count: string; + readonly page_number: string; + readonly page_total: string; + readonly limit: string; + readonly txs: readonly TxsResponse[]; +} +export interface PostTxsResponse { + readonly height: string; + readonly txhash: string; + readonly code?: number; + readonly raw_log?: string; + /** The same as `raw_log` but deserialized? */ + readonly logs?: object; + /** The gas limit as set by the user */ + readonly gas_wanted?: string; + /** The gas used by the execution */ + readonly gas_used?: string; +} +interface EncodeTxResponse { + readonly tx: string; +} +export interface CodeInfo { + readonly id: number; + /** Bech32 account address */ + readonly creator: string; + /** Hex-encoded sha256 hash of the code stored here */ + readonly data_hash: string; + readonly source?: string; + readonly builder?: string; +} +export interface CodeDetails extends CodeInfo { + /** Base64 encoded raw wasm data */ + readonly data: string; +} +export interface ContractInfo { + readonly address: string; + readonly code_id: number; + /** Bech32 account address */ + readonly creator: string; + readonly label: string; +} +export interface ContractDetails extends ContractInfo { + /** Argument passed on initialization of the contract */ + readonly init_msg: object; +} +declare type RestClientResponse = + | NodeInfoResponse + | BlockResponse + | AuthAccountsResponse + | TxsResponse + | SearchTxsResponse + | PostTxsResponse + | EncodeTxResponse + | WasmResponse + | WasmResponse + | WasmResponse + | WasmResponse + | WasmResponse; +/** + * The mode used to send transaction + * + * @see https://cosmos.network/rpc/#/Transactions/post_txs + */ +export declare enum BroadcastMode { + /** Return after tx commit */ + Block = "block", + /** Return afer CheckTx */ + Sync = "sync", + /** Return right away */ + Async = "async", +} +export declare class RestClient { + private readonly client; + private readonly broadcastMode; + /** + * Creates a new client to interact with a Cosmos SDK light client daemon. + * This class tries to be a direct mapping onto the API. Some basic decoding and normalizatin is done + * but things like caching are done at a higher level. + * + * When building apps, you should not need to use this class directly. If you do, this indicates a missing feature + * in higher level components. Feel free to raise an issue in this case. + * + * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) + * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns + */ + constructor(apiUrl: string, broadcastMode?: BroadcastMode); + get(path: string): Promise; + post(path: string, params: any): Promise; + authAccounts(address: string): Promise; + blocksLatest(): Promise; + blocks(height: number): Promise; + nodeInfo(): Promise; + txById(id: string): Promise; + txsQuery(query: string): Promise; + /** returns the amino-encoding of the transaction performed by the server */ + encodeTx(tx: CosmosSdkTx): Promise; + /** + * Broadcasts a signed transaction to into the transaction pool. + * Depending on the RestClient's broadcast mode, this might or might + * wait for checkTx or deliverTx to be executed before returning. + * + * @param tx a signed transaction as StdTx (i.e. not wrapped in type/value container) + */ + postTx(tx: StdTx): Promise; + listCodeInfo(): Promise; + getCode(id: number): Promise; + listContractsByCodeId(id: number): Promise; + /** + * Returns null when contract was not found at this address. + */ + getContractInfo(address: string): Promise; + getAllContractState(address: string): Promise; + queryContractRaw(address: string, key: Uint8Array): Promise; + /** + * Makes a smart query on the contract and parses the reponse as JSON. + * Throws error if no such contract exists, the query format is invalid or the response is invalid. + */ + queryContractSmart(address: string, query: object): Promise; +} +export {}; diff --git a/packages/cosmwasm/types/signingcosmwasmclient.d.ts b/packages/cosmwasm/types/signingcosmwasmclient.d.ts new file mode 100644 index 00000000..d3f9e00e --- /dev/null +++ b/packages/cosmwasm/types/signingcosmwasmclient.d.ts @@ -0,0 +1,89 @@ +import { Coin } from "@cosmwasm/sdk38"; +import { Account, CosmWasmClient, GetNonceResult, PostTxResult } from "./cosmwasmclient"; +import { Log } from "./logs"; +import { BroadcastMode } from "./restclient"; +import { StdFee, StdSignature } from "./types"; +export interface SigningCallback { + (signBytes: Uint8Array): Promise; +} +export interface FeeTable { + readonly upload: StdFee; + readonly init: StdFee; + readonly exec: StdFee; + readonly send: StdFee; +} +export interface UploadMeta { + /** The source URL */ + readonly source?: string; + /** The builder tag */ + readonly builder?: string; +} +export interface UploadResult { + /** 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; + /** The ID of the code asigned by the chain */ + readonly codeId: number; + readonly logs: readonly Log[]; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; +} +export interface InstantiateResult { + /** The address of the newly instantiated contract */ + readonly contractAddress: string; + readonly logs: readonly Log[]; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; +} +export interface ExecuteResult { + readonly logs: readonly Log[]; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; +} +export declare class SigningCosmWasmClient extends CosmWasmClient { + readonly senderAddress: string; + private readonly signCallback; + private readonly fees; + /** + * Creates a new client with signing capability to interact with a CosmWasm blockchain. This is the bigger brother of CosmWasmClient. + * + * This instance does a lot of caching. In order to benefit from that you should try to use one instance + * for the lifetime of your application. When switching backends, a new instance must be created. + * + * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) + * @param senderAddress The address that will sign and send transactions using this instance + * @param signCallback An asynchonous callback to create a signature for a given transaction. This can be implemented using secure key stores that require user interaction. + * @param customFees The fees that are paid for transactions + * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns + */ + constructor( + apiUrl: string, + senderAddress: string, + signCallback: SigningCallback, + customFees?: Partial, + broadcastMode?: BroadcastMode, + ); + getNonce(address?: string): Promise; + getAccount(address?: string): Promise; + /** Uploads code and returns a receipt, including the code ID */ + upload(wasmCode: Uint8Array, meta?: UploadMeta, memo?: string): Promise; + instantiate( + codeId: number, + initMsg: object, + label: string, + memo?: string, + transferAmount?: readonly Coin[], + ): Promise; + execute( + contractAddress: string, + handleMsg: object, + memo?: string, + transferAmount?: readonly Coin[], + ): Promise; + sendTokens(recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise; +} diff --git a/packages/cosmwasm/types/types.d.ts b/packages/cosmwasm/types/types.d.ts new file mode 100644 index 00000000..e7592e66 --- /dev/null +++ b/packages/cosmwasm/types/types.d.ts @@ -0,0 +1,123 @@ +import { Coin } from "@cosmwasm/sdk38"; +/** An Amino/Cosmos SDK StdTx */ +export interface StdTx { + readonly msg: ReadonlyArray; + readonly fee: StdFee; + readonly signatures: ReadonlyArray; + readonly memo: string | undefined; +} +export declare function isStdTx(txValue: unknown): txValue is StdTx; +export interface CosmosSdkTx { + readonly type: string; + readonly value: StdTx; +} +interface MsgTemplate { + readonly type: string; + readonly value: any; +} +/** A Cosmos SDK token transfer message */ +export interface MsgSend extends MsgTemplate { + readonly type: "cosmos-sdk/MsgSend"; + readonly value: { + /** Bech32 account address */ + readonly from_address: string; + /** Bech32 account address */ + readonly to_address: string; + readonly amount: ReadonlyArray; + }; +} +/** + * Uploads Wam code to the chain + * + * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L17 + */ +export interface MsgStoreCode extends MsgTemplate { + readonly type: "wasm/store-code"; + readonly value: { + /** Bech32 account address */ + readonly sender: string; + /** Base64 encoded Wasm */ + readonly wasm_byte_code: string; + /** A valid URI reference to the contract's source code. Can be empty. */ + readonly source: string; + /** A docker tag. Can be empty. */ + readonly builder: string; + }; +} +/** + * Creates an instance of contract that was uploaded before. + * + * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L73 + */ +export interface MsgInstantiateContract extends MsgTemplate { + readonly type: "wasm/instantiate"; + readonly 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; + /** Init message as JavaScript object */ + readonly init_msg: any; + readonly init_funds: ReadonlyArray; + }; +} +/** + * Creates an instance of contract that was uploaded before. + * + * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L103 + */ +export interface MsgExecuteContract extends MsgTemplate { + readonly type: "wasm/execute"; + readonly value: { + /** Bech32 account address */ + readonly sender: string; + /** Bech32 account address */ + readonly contract: string; + /** Handle message as JavaScript object */ + readonly msg: any; + readonly sent_funds: ReadonlyArray; + }; +} +export declare type Msg = MsgSend | MsgStoreCode | MsgInstantiateContract | MsgExecuteContract | MsgTemplate; +export declare function isMsgSend(msg: Msg): msg is MsgSend; +export declare function isMsgStoreCode(msg: Msg): msg is MsgStoreCode; +export declare function isMsgInstantiateContract(msg: Msg): msg is MsgInstantiateContract; +export declare function isMsgExecuteContract(msg: Msg): msg is MsgExecuteContract; +export interface StdFee { + readonly amount: ReadonlyArray; + readonly gas: string; +} +export interface StdSignature { + readonly pub_key: PubKey; + readonly signature: string; +} +export interface PubKey { + readonly type: string; + readonly value: string; +} +export declare const pubkeyType: { + /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/ed25519/ed25519.go#L22 */ + secp256k1: "tendermint/PubKeySecp256k1"; + /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/secp256k1/secp256k1.go#L23 */ + ed25519: "tendermint/PubKeyEd25519"; + /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12 */ + sr25519: "tendermint/PubKeySr25519"; +}; +export declare const pubkeyTypes: readonly string[]; +export interface WasmData { + readonly key: string; + readonly val: string; +} +export interface Model { + readonly key: Uint8Array; + readonly val: Uint8Array; +} +export declare function parseWasmData({ key, val }: WasmData): Model; +/** + * An object containing a parsed JSON document. The result of JSON.parse(). + * This doen't privide any type safety over `any` but expresses intent in the code. + */ +export declare type JsonObject = any; +export {}; diff --git a/packages/cosmwasm/webpack.web.config.js b/packages/cosmwasm/webpack.web.config.js new file mode 100644 index 00000000..7373cace --- /dev/null +++ b/packages/cosmwasm/webpack.web.config.js @@ -0,0 +1,19 @@ +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(["WASMD_ENABLED"])], + }, +]; From efb90d1f65cb31761645457b0c2631023b1aab56 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 2 Jun 2020 13:45:19 +0200 Subject: [PATCH 04/15] Sort some types between sdk38 and cosmwasm --- .../src/cosmwasmclient.searchtx.spec.ts | 5 +- packages/cosmwasm/src/cosmwasmclient.spec.ts | 5 +- packages/cosmwasm/src/cosmwasmclient.ts | 21 +---- packages/cosmwasm/src/index.ts | 13 ++- packages/cosmwasm/src/msgs.ts | 93 ++++++++++++++++++ packages/cosmwasm/src/pubkey.spec.ts | 57 ----------- packages/cosmwasm/src/pubkey.ts | 71 -------------- packages/cosmwasm/src/restclient.spec.ts | 33 ++++--- .../cosmwasm/src/signingcosmwasmclient.ts | 10 +- packages/cosmwasm/src/types.ts | 94 +------------------ packages/cosmwasm/types/cosmwasmclient.d.ts | 19 +--- packages/cosmwasm/types/index.d.ts | 13 ++- packages/cosmwasm/types/msgs.d.ts | 76 +++++++++++++++ packages/cosmwasm/types/pubkey.d.ts | 4 - packages/cosmwasm/types/types.d.ts | 76 +-------------- packages/sdk38/src/index.ts | 21 +++++ packages/sdk38/src/signingcosmosclient.ts | 38 -------- packages/sdk38/types/index.d.ts | 20 ++++ packages/sdk38/types/signingcosmosclient.d.ts | 34 ------- 19 files changed, 260 insertions(+), 443 deletions(-) create mode 100644 packages/cosmwasm/src/msgs.ts delete mode 100644 packages/cosmwasm/src/pubkey.spec.ts delete mode 100644 packages/cosmwasm/src/pubkey.ts create mode 100644 packages/cosmwasm/types/msgs.d.ts delete mode 100644 packages/cosmwasm/types/pubkey.d.ts diff --git a/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts b/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts index 1b92ecba..431ed96f 100644 --- a/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts +++ b/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts @@ -1,8 +1,9 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { Coin, Secp256k1Pen, makeSignBytes } from "@cosmwasm/sdk38"; +import { Coin, makeSignBytes, Secp256k1Pen } from "@cosmwasm/sdk38"; import { assert, sleep } from "@iov/utils"; import { CosmWasmClient } from "./cosmwasmclient"; +import { isMsgExecuteContract, isMsgInstantiateContract, isMsgSend, MsgSend } from "./msgs"; import { RestClient } from "./restclient"; import { SigningCosmWasmClient } from "./signingcosmwasmclient"; import { @@ -14,7 +15,7 @@ import { wasmd, wasmdEnabled, } from "./testutils.spec"; -import { CosmosSdkTx, isMsgExecuteContract, isMsgInstantiateContract, isMsgSend, MsgSend } from "./types"; +import { CosmosSdkTx } from "./types"; describe("CosmWasmClient.searchTx", () => { let sendSuccessful: diff --git a/packages/cosmwasm/src/cosmwasmclient.spec.ts b/packages/cosmwasm/src/cosmwasmclient.spec.ts index 7c4904ed..6fffd7b7 100644 --- a/packages/cosmwasm/src/cosmwasmclient.spec.ts +++ b/packages/cosmwasm/src/cosmwasmclient.spec.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { Secp256k1Pen, makeSignBytes } from "@cosmwasm/sdk38"; +import { makeSignBytes, Secp256k1Pen } from "@cosmwasm/sdk38"; import { Sha256 } from "@iov/crypto"; import { Bech32, Encoding } from "@iov/encoding"; import { assert, sleep } from "@iov/utils"; @@ -7,6 +7,7 @@ import { ReadonlyDate } from "readonly-date"; import { Code, CosmWasmClient, PrivateCosmWasmClient } from "./cosmwasmclient"; import { findAttribute } from "./logs"; +import { MsgSend } from "./msgs"; import { SigningCosmWasmClient } from "./signingcosmwasmclient"; import cosmoshub from "./testdata/cosmoshub.json"; import { @@ -20,7 +21,7 @@ import { wasmd, wasmdEnabled, } from "./testutils.spec"; -import { MsgSend, StdFee } from "./types"; +import { StdFee } from "./types"; const { fromHex, fromUtf8, toAscii, toBase64 } = Encoding; diff --git a/packages/cosmwasm/src/cosmwasmclient.ts b/packages/cosmwasm/src/cosmwasmclient.ts index 04c30d3c..16f438a1 100644 --- a/packages/cosmwasm/src/cosmwasmclient.ts +++ b/packages/cosmwasm/src/cosmwasmclient.ts @@ -1,9 +1,8 @@ -import { Coin } from "@cosmwasm/sdk38"; +import { Coin, decodeBech32Pubkey, IndexedTx } from "@cosmwasm/sdk38"; import { Sha256 } from "@iov/crypto"; import { Encoding } from "@iov/encoding"; import { Log, parseLogs } from "./logs"; -import { decodeBech32Pubkey } from "./pubkey"; import { BroadcastMode, RestClient } from "./restclient"; import { CosmosSdkTx, JsonObject, PubKey, StdTx } from "./types"; @@ -103,24 +102,6 @@ export interface ContractDetails extends Contract { readonly initMsg: object; } -/** A transaction that is indexed as part of the transaction history */ -export interface IndexedTx { - readonly height: number; - /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ - readonly hash: string; - /** Transaction execution error code. 0 on success. */ - readonly code: number; - readonly rawLog: string; - readonly logs: readonly Log[]; - readonly tx: CosmosSdkTx; - /** The gas limit as set by the user */ - readonly gasWanted?: number; - /** The gas used by the execution */ - readonly gasUsed?: number; - /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ - readonly timestamp: string; -} - export interface BlockHeader { readonly version: { readonly block: string; diff --git a/packages/cosmwasm/src/index.ts b/packages/cosmwasm/src/index.ts index 1a189237..1c03de9b 100644 --- a/packages/cosmwasm/src/index.ts +++ b/packages/cosmwasm/src/index.ts @@ -1,6 +1,5 @@ import * as logs from "./logs"; -import * as types from "./types"; -export { logs, types }; +export { logs }; export { BroadcastMode, RestClient, TxsResponse } from "./restclient"; export { @@ -13,7 +12,6 @@ export { ContractDetails, CosmWasmClient, GetNonceResult, - IndexedTx, PostTxResult, SearchByHeightQuery, SearchByIdQuery, @@ -22,7 +20,6 @@ export { SearchTxQuery, SearchTxFilter, } from "./cosmwasmclient"; -export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; export { ExecuteResult, FeeTable, @@ -32,3 +29,11 @@ export { UploadMeta, UploadResult, } from "./signingcosmwasmclient"; +export { + isMsgExecuteContract, + isMsgInstantiateContract, + isMsgStoreCode, + MsgStoreCode, + MsgExecuteContract, + MsgInstantiateContract, +} from "./msgs"; diff --git a/packages/cosmwasm/src/msgs.ts b/packages/cosmwasm/src/msgs.ts new file mode 100644 index 00000000..bf47a18c --- /dev/null +++ b/packages/cosmwasm/src/msgs.ts @@ -0,0 +1,93 @@ +import { Coin } from "@cosmwasm/sdk38"; + +interface MsgTemplate { + readonly type: string; + readonly value: any; +} + +/** A Cosmos SDK token transfer message */ +export interface MsgSend extends MsgTemplate { + readonly type: "cosmos-sdk/MsgSend"; + readonly value: { + /** Bech32 account address */ + readonly from_address: string; + /** Bech32 account address */ + readonly to_address: string; + readonly amount: ReadonlyArray; + }; +} + +/** + * Uploads Wam code to the chain + * + * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L17 + */ +export interface MsgStoreCode extends MsgTemplate { + readonly type: "wasm/store-code"; + readonly value: { + /** Bech32 account address */ + readonly sender: string; + /** Base64 encoded Wasm */ + readonly wasm_byte_code: string; + /** A valid URI reference to the contract's source code. Can be empty. */ + readonly source: string; + /** A docker tag. Can be empty. */ + readonly builder: string; + }; +} + +/** + * Creates an instance of contract that was uploaded before. + * + * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L73 + */ +export interface MsgInstantiateContract extends MsgTemplate { + readonly type: "wasm/instantiate"; + readonly 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; + /** Init message as JavaScript object */ + readonly init_msg: any; + readonly init_funds: ReadonlyArray; + }; +} + +/** + * Creates an instance of contract that was uploaded before. + * + * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L103 + */ +export interface MsgExecuteContract extends MsgTemplate { + readonly type: "wasm/execute"; + readonly value: { + /** Bech32 account address */ + readonly sender: string; + /** Bech32 account address */ + readonly contract: string; + /** Handle message as JavaScript object */ + readonly msg: any; + readonly sent_funds: ReadonlyArray; + }; +} + +export type Msg = MsgSend | MsgStoreCode | MsgInstantiateContract | MsgExecuteContract | MsgTemplate; + +export function isMsgSend(msg: Msg): msg is MsgSend { + return (msg as MsgSend).type === "cosmos-sdk/MsgSend"; +} + +export function isMsgStoreCode(msg: Msg): msg is MsgStoreCode { + return (msg as MsgStoreCode).type === "wasm/store-code"; +} + +export function isMsgInstantiateContract(msg: Msg): msg is MsgInstantiateContract { + return (msg as MsgInstantiateContract).type === "wasm/instantiate"; +} + +export function isMsgExecuteContract(msg: Msg): msg is MsgExecuteContract { + return (msg as MsgExecuteContract).type === "wasm/execute"; +} diff --git a/packages/cosmwasm/src/pubkey.spec.ts b/packages/cosmwasm/src/pubkey.spec.ts deleted file mode 100644 index cb344f7b..00000000 --- a/packages/cosmwasm/src/pubkey.spec.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Encoding } from "@iov/encoding"; - -import { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; -import { PubKey } from "./types"; - -const { fromBase64 } = Encoding; - -describe("pubkey", () => { - describe("encodeSecp256k1Pubkey", () => { - it("encodes a compresed pubkey", () => { - const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP"); - expect(encodeSecp256k1Pubkey(pubkey)).toEqual({ - type: "tendermint/PubKeySecp256k1", - value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP", - }); - }); - - it("throws for uncompressed public keys", () => { - const pubkey = fromBase64( - "BE8EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQE7WHpoHoNswYeoFkuYpYSKK4mzFzMV/dB0DVAy4lnNU=", - ); - expect(() => encodeSecp256k1Pubkey(pubkey)).toThrowError(/public key must be compressed secp256k1/i); - }); - }); - - describe("decodeBech32Pubkey", () => { - it("works", () => { - expect( - decodeBech32Pubkey("cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5"), - ).toEqual({ - type: "tendermint/PubKeySecp256k1", - value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", - }); - }); - - it("works for enigma pubkey", () => { - expect( - decodeBech32Pubkey("enigmapub1addwnpepqw5k9p439nw0zpg2aundx4umwx4nw233z5prpjqjv5anl5grmnchzp2xwvv"), - ).toEqual({ - type: "tendermint/PubKeySecp256k1", - value: "A6lihrEs3PEFCu8m01ebcas3KjEVAjDIEmU7P9ED3PFx", - }); - }); - }); - - describe("encodeBech32Pubkey", () => { - it("works for secp256k1", () => { - const pubkey: PubKey = { - type: "tendermint/PubKeySecp256k1", - value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", - }; - expect(encodeBech32Pubkey(pubkey, "cosmospub")).toEqual( - "cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5", - ); - }); - }); -}); diff --git a/packages/cosmwasm/src/pubkey.ts b/packages/cosmwasm/src/pubkey.ts deleted file mode 100644 index 8835b97a..00000000 --- a/packages/cosmwasm/src/pubkey.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Bech32, Encoding } from "@iov/encoding"; -import equal from "fast-deep-equal"; - -import { PubKey, pubkeyType } from "./types"; - -export function encodeSecp256k1Pubkey(pubkey: Uint8Array): PubKey { - if (pubkey.length !== 33 || (pubkey[0] !== 0x02 && pubkey[0] !== 0x03)) { - throw new Error("Public key must be compressed secp256k1, i.e. 33 bytes starting with 0x02 or 0x03"); - } - return { - type: pubkeyType.secp256k1, - value: Encoding.toBase64(pubkey), - }; -} - -// As discussed in https://github.com/binance-chain/javascript-sdk/issues/163 -// Prefixes listed here: https://github.com/tendermint/tendermint/blob/d419fffe18531317c28c29a292ad7d253f6cafdf/docs/spec/blockchain/encoding.md#public-key-cryptography -// Last bytes is varint-encoded length prefix -const pubkeyAminoPrefixSecp256k1 = Encoding.fromHex("eb5ae98721"); -const pubkeyAminoPrefixEd25519 = Encoding.fromHex("1624de6420"); -const pubkeyAminoPrefixSr25519 = Encoding.fromHex("0dfb1005"); -const pubkeyAminoPrefixLength = pubkeyAminoPrefixSecp256k1.length; - -export function decodeBech32Pubkey(bechEncoded: string): PubKey { - const { data } = Bech32.decode(bechEncoded); - - const aminoPrefix = data.slice(0, pubkeyAminoPrefixLength); - const rest = data.slice(pubkeyAminoPrefixLength); - if (equal(aminoPrefix, pubkeyAminoPrefixSecp256k1)) { - if (rest.length !== 33) { - throw new Error("Invalid rest data length. Expected 33 bytes (compressed secp256k1 pubkey)."); - } - return { - type: pubkeyType.secp256k1, - value: Encoding.toBase64(rest), - }; - } else if (equal(aminoPrefix, pubkeyAminoPrefixEd25519)) { - if (rest.length !== 32) { - throw new Error("Invalid rest data length. Expected 32 bytes (Ed25519 pubkey)."); - } - return { - type: pubkeyType.ed25519, - value: Encoding.toBase64(rest), - }; - } else if (equal(aminoPrefix, pubkeyAminoPrefixSr25519)) { - if (rest.length !== 32) { - throw new Error("Invalid rest data length. Expected 32 bytes (Sr25519 pubkey)."); - } - return { - type: pubkeyType.sr25519, - value: Encoding.toBase64(rest), - }; - } else { - throw new Error("Unsupported Pubkey type. Amino prefix: " + Encoding.toHex(aminoPrefix)); - } -} - -export function encodeBech32Pubkey(pubkey: PubKey, prefix: string): string { - let aminoPrefix: Uint8Array; - switch (pubkey.type) { - // Note: please don't add cases here without writing additional unit tests - case pubkeyType.secp256k1: - aminoPrefix = pubkeyAminoPrefixSecp256k1; - break; - default: - throw new Error("Unsupported pubkey type"); - } - - const data = new Uint8Array([...aminoPrefix, ...Encoding.fromBase64(pubkey.value)]); - return Bech32.encode(prefix, data); -} diff --git a/packages/cosmwasm/src/restclient.spec.ts b/packages/cosmwasm/src/restclient.spec.ts index dfe67396..c006d5bb 100644 --- a/packages/cosmwasm/src/restclient.spec.ts +++ b/packages/cosmwasm/src/restclient.spec.ts @@ -1,12 +1,28 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { Coin, makeCosmoshubPath, Pen, rawSecp256k1PubkeyToAddress, Secp256k1Pen, makeSignBytes } from "@cosmwasm/sdk38"; +import { + Coin, + encodeBech32Pubkey, + makeCosmoshubPath, + makeSignBytes, + Pen, + rawSecp256k1PubkeyToAddress, + Secp256k1Pen, +} from "@cosmwasm/sdk38"; import { Sha256 } from "@iov/crypto"; import { Encoding } from "@iov/encoding"; import { assert, sleep } from "@iov/utils"; import { ReadonlyDate } from "readonly-date"; import { findAttribute, parseLogs } from "./logs"; -import { encodeBech32Pubkey } from "./pubkey"; +import { + isMsgInstantiateContract, + isMsgStoreCode, + Msg, + MsgExecuteContract, + MsgInstantiateContract, + MsgSend, + MsgStoreCode, +} from "./msgs"; import { PostTxsResponse, RestClient, TxsResponse } from "./restclient"; import { SigningCosmWasmClient } from "./signingcosmwasmclient"; import cosmoshub from "./testdata/cosmoshub.json"; @@ -28,18 +44,7 @@ import { wasmd, wasmdEnabled, } from "./testutils.spec"; -import { - isMsgInstantiateContract, - isMsgStoreCode, - Msg, - MsgExecuteContract, - MsgInstantiateContract, - MsgSend, - MsgStoreCode, - StdFee, - StdSignature, - StdTx, -} from "./types"; +import { StdFee, StdSignature, StdTx } from "./types"; const { fromAscii, fromBase64, fromHex, toAscii, toBase64, toHex } = Encoding; diff --git a/packages/cosmwasm/src/signingcosmwasmclient.ts b/packages/cosmwasm/src/signingcosmwasmclient.ts index bcdd29b0..99eed14c 100644 --- a/packages/cosmwasm/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm/src/signingcosmwasmclient.ts @@ -6,15 +6,9 @@ import pako from "pako"; import { isValidBuilder } from "./builder"; import { Account, CosmWasmClient, GetNonceResult, PostTxResult } from "./cosmwasmclient"; import { findAttribute, Log } from "./logs"; +import { MsgExecuteContract, MsgInstantiateContract, MsgSend, MsgStoreCode } from "./msgs"; import { BroadcastMode } from "./restclient"; -import { - MsgExecuteContract, - MsgInstantiateContract, - MsgSend, - MsgStoreCode, - StdFee, - StdSignature, -} from "./types"; +import { StdFee, StdSignature } from "./types"; export interface SigningCallback { (signBytes: Uint8Array): Promise; diff --git a/packages/cosmwasm/src/types.ts b/packages/cosmwasm/src/types.ts index bfc8dbcf..59c9a112 100644 --- a/packages/cosmwasm/src/types.ts +++ b/packages/cosmwasm/src/types.ts @@ -1,6 +1,8 @@ import { Coin } from "@cosmwasm/sdk38"; import { Encoding } from "@iov/encoding"; +import { Msg } from "./msgs"; + const { fromBase64, fromHex } = Encoding; /** An Amino/Cosmos SDK StdTx */ @@ -23,98 +25,6 @@ export interface CosmosSdkTx { readonly value: StdTx; } -interface MsgTemplate { - readonly type: string; - readonly value: any; -} - -/** A Cosmos SDK token transfer message */ -export interface MsgSend extends MsgTemplate { - readonly type: "cosmos-sdk/MsgSend"; - readonly value: { - /** Bech32 account address */ - readonly from_address: string; - /** Bech32 account address */ - readonly to_address: string; - readonly amount: ReadonlyArray; - }; -} - -/** - * Uploads Wam code to the chain - * - * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L17 - */ -export interface MsgStoreCode extends MsgTemplate { - readonly type: "wasm/store-code"; - readonly value: { - /** Bech32 account address */ - readonly sender: string; - /** Base64 encoded Wasm */ - readonly wasm_byte_code: string; - /** A valid URI reference to the contract's source code. Can be empty. */ - readonly source: string; - /** A docker tag. Can be empty. */ - readonly builder: string; - }; -} - -/** - * Creates an instance of contract that was uploaded before. - * - * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L73 - */ -export interface MsgInstantiateContract extends MsgTemplate { - readonly type: "wasm/instantiate"; - readonly 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; - /** Init message as JavaScript object */ - readonly init_msg: any; - readonly init_funds: ReadonlyArray; - }; -} - -/** - * Creates an instance of contract that was uploaded before. - * - * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L103 - */ -export interface MsgExecuteContract extends MsgTemplate { - readonly type: "wasm/execute"; - readonly value: { - /** Bech32 account address */ - readonly sender: string; - /** Bech32 account address */ - readonly contract: string; - /** Handle message as JavaScript object */ - readonly msg: any; - readonly sent_funds: ReadonlyArray; - }; -} - -export type Msg = MsgSend | MsgStoreCode | MsgInstantiateContract | MsgExecuteContract | MsgTemplate; - -export function isMsgSend(msg: Msg): msg is MsgSend { - return (msg as MsgSend).type === "cosmos-sdk/MsgSend"; -} - -export function isMsgStoreCode(msg: Msg): msg is MsgStoreCode { - return (msg as MsgStoreCode).type === "wasm/store-code"; -} - -export function isMsgInstantiateContract(msg: Msg): msg is MsgInstantiateContract { - return (msg as MsgInstantiateContract).type === "wasm/instantiate"; -} - -export function isMsgExecuteContract(msg: Msg): msg is MsgExecuteContract { - return (msg as MsgExecuteContract).type === "wasm/execute"; -} - export interface StdFee { readonly amount: ReadonlyArray; readonly gas: string; diff --git a/packages/cosmwasm/types/cosmwasmclient.d.ts b/packages/cosmwasm/types/cosmwasmclient.d.ts index 3b57ffde..d12633cc 100644 --- a/packages/cosmwasm/types/cosmwasmclient.d.ts +++ b/packages/cosmwasm/types/cosmwasmclient.d.ts @@ -1,4 +1,4 @@ -import { Coin } from "@cosmwasm/sdk38"; +import { Coin, IndexedTx } from "@cosmwasm/sdk38"; import { Log } from "./logs"; import { BroadcastMode, RestClient } from "./restclient"; import { CosmosSdkTx, JsonObject, PubKey, StdTx } from "./types"; @@ -72,23 +72,6 @@ export interface ContractDetails extends Contract { /** Argument passed on initialization of the contract */ readonly initMsg: object; } -/** A transaction that is indexed as part of the transaction history */ -export interface IndexedTx { - readonly height: number; - /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ - readonly hash: string; - /** Transaction execution error code. 0 on success. */ - readonly code: number; - readonly rawLog: string; - readonly logs: readonly Log[]; - readonly tx: CosmosSdkTx; - /** The gas limit as set by the user */ - readonly gasWanted?: number; - /** The gas used by the execution */ - readonly gasUsed?: number; - /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ - readonly timestamp: string; -} export interface BlockHeader { readonly version: { readonly block: string; diff --git a/packages/cosmwasm/types/index.d.ts b/packages/cosmwasm/types/index.d.ts index 1241731b..e070d48c 100644 --- a/packages/cosmwasm/types/index.d.ts +++ b/packages/cosmwasm/types/index.d.ts @@ -1,6 +1,5 @@ import * as logs from "./logs"; -import * as types from "./types"; -export { logs, types }; +export { logs }; export { BroadcastMode, RestClient, TxsResponse } from "./restclient"; export { Account, @@ -12,7 +11,6 @@ export { ContractDetails, CosmWasmClient, GetNonceResult, - IndexedTx, PostTxResult, SearchByHeightQuery, SearchByIdQuery, @@ -21,7 +19,6 @@ export { SearchTxQuery, SearchTxFilter, } from "./cosmwasmclient"; -export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; export { ExecuteResult, FeeTable, @@ -31,3 +28,11 @@ export { UploadMeta, UploadResult, } from "./signingcosmwasmclient"; +export { + isMsgExecuteContract, + isMsgInstantiateContract, + isMsgStoreCode, + MsgStoreCode, + MsgExecuteContract, + MsgInstantiateContract, +} from "./msgs"; diff --git a/packages/cosmwasm/types/msgs.d.ts b/packages/cosmwasm/types/msgs.d.ts new file mode 100644 index 00000000..2fb0ae6f --- /dev/null +++ b/packages/cosmwasm/types/msgs.d.ts @@ -0,0 +1,76 @@ +import { Coin } from "@cosmwasm/sdk38"; +interface MsgTemplate { + readonly type: string; + readonly value: any; +} +/** A Cosmos SDK token transfer message */ +export interface MsgSend extends MsgTemplate { + readonly type: "cosmos-sdk/MsgSend"; + readonly value: { + /** Bech32 account address */ + readonly from_address: string; + /** Bech32 account address */ + readonly to_address: string; + readonly amount: ReadonlyArray; + }; +} +/** + * Uploads Wam code to the chain + * + * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L17 + */ +export interface MsgStoreCode extends MsgTemplate { + readonly type: "wasm/store-code"; + readonly value: { + /** Bech32 account address */ + readonly sender: string; + /** Base64 encoded Wasm */ + readonly wasm_byte_code: string; + /** A valid URI reference to the contract's source code. Can be empty. */ + readonly source: string; + /** A docker tag. Can be empty. */ + readonly builder: string; + }; +} +/** + * Creates an instance of contract that was uploaded before. + * + * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L73 + */ +export interface MsgInstantiateContract extends MsgTemplate { + readonly type: "wasm/instantiate"; + readonly 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; + /** Init message as JavaScript object */ + readonly init_msg: any; + readonly init_funds: ReadonlyArray; + }; +} +/** + * Creates an instance of contract that was uploaded before. + * + * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L103 + */ +export interface MsgExecuteContract extends MsgTemplate { + readonly type: "wasm/execute"; + readonly value: { + /** Bech32 account address */ + readonly sender: string; + /** Bech32 account address */ + readonly contract: string; + /** Handle message as JavaScript object */ + readonly msg: any; + readonly sent_funds: ReadonlyArray; + }; +} +export declare type Msg = MsgSend | MsgStoreCode | MsgInstantiateContract | MsgExecuteContract | MsgTemplate; +export declare function isMsgSend(msg: Msg): msg is MsgSend; +export declare function isMsgStoreCode(msg: Msg): msg is MsgStoreCode; +export declare function isMsgInstantiateContract(msg: Msg): msg is MsgInstantiateContract; +export declare function isMsgExecuteContract(msg: Msg): msg is MsgExecuteContract; +export {}; diff --git a/packages/cosmwasm/types/pubkey.d.ts b/packages/cosmwasm/types/pubkey.d.ts deleted file mode 100644 index b6ebdebc..00000000 --- a/packages/cosmwasm/types/pubkey.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { PubKey } from "./types"; -export declare function encodeSecp256k1Pubkey(pubkey: Uint8Array): PubKey; -export declare function decodeBech32Pubkey(bechEncoded: string): PubKey; -export declare function encodeBech32Pubkey(pubkey: PubKey, prefix: string): string; diff --git a/packages/cosmwasm/types/types.d.ts b/packages/cosmwasm/types/types.d.ts index e7592e66..b7d54776 100644 --- a/packages/cosmwasm/types/types.d.ts +++ b/packages/cosmwasm/types/types.d.ts @@ -1,4 +1,5 @@ import { Coin } from "@cosmwasm/sdk38"; +import { Msg } from "./msgs"; /** An Amino/Cosmos SDK StdTx */ export interface StdTx { readonly msg: ReadonlyArray; @@ -11,80 +12,6 @@ export interface CosmosSdkTx { readonly type: string; readonly value: StdTx; } -interface MsgTemplate { - readonly type: string; - readonly value: any; -} -/** A Cosmos SDK token transfer message */ -export interface MsgSend extends MsgTemplate { - readonly type: "cosmos-sdk/MsgSend"; - readonly value: { - /** Bech32 account address */ - readonly from_address: string; - /** Bech32 account address */ - readonly to_address: string; - readonly amount: ReadonlyArray; - }; -} -/** - * Uploads Wam code to the chain - * - * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L17 - */ -export interface MsgStoreCode extends MsgTemplate { - readonly type: "wasm/store-code"; - readonly value: { - /** Bech32 account address */ - readonly sender: string; - /** Base64 encoded Wasm */ - readonly wasm_byte_code: string; - /** A valid URI reference to the contract's source code. Can be empty. */ - readonly source: string; - /** A docker tag. Can be empty. */ - readonly builder: string; - }; -} -/** - * Creates an instance of contract that was uploaded before. - * - * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L73 - */ -export interface MsgInstantiateContract extends MsgTemplate { - readonly type: "wasm/instantiate"; - readonly 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; - /** Init message as JavaScript object */ - readonly init_msg: any; - readonly init_funds: ReadonlyArray; - }; -} -/** - * Creates an instance of contract that was uploaded before. - * - * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L103 - */ -export interface MsgExecuteContract extends MsgTemplate { - readonly type: "wasm/execute"; - readonly value: { - /** Bech32 account address */ - readonly sender: string; - /** Bech32 account address */ - readonly contract: string; - /** Handle message as JavaScript object */ - readonly msg: any; - readonly sent_funds: ReadonlyArray; - }; -} -export declare type Msg = MsgSend | MsgStoreCode | MsgInstantiateContract | MsgExecuteContract | MsgTemplate; -export declare function isMsgSend(msg: Msg): msg is MsgSend; -export declare function isMsgStoreCode(msg: Msg): msg is MsgStoreCode; -export declare function isMsgInstantiateContract(msg: Msg): msg is MsgInstantiateContract; -export declare function isMsgExecuteContract(msg: Msg): msg is MsgExecuteContract; export interface StdFee { readonly amount: ReadonlyArray; readonly gas: string; @@ -120,4 +47,3 @@ export declare function parseWasmData({ key, val }: WasmData): Model; * This doen't privide any type safety over `any` but expresses intent in the code. */ export declare type JsonObject = any; -export {}; diff --git a/packages/sdk38/src/index.ts b/packages/sdk38/src/index.ts index 8ba8ea86..d67248bf 100644 --- a/packages/sdk38/src/index.ts +++ b/packages/sdk38/src/index.ts @@ -4,6 +4,26 @@ export { logs, types }; export { pubkeyToAddress, rawSecp256k1PubkeyToAddress } from "./address"; export { Coin, coin, coins } from "./coins"; + +export { + Account, + Block, + BlockHeader, + Code, + CodeDetails, + Contract, + ContractDetails, + CosmosClient, + GetNonceResult, + IndexedTx, + PostTxResult, + SearchByHeightQuery, + SearchByIdQuery, + SearchBySentFromOrToQuery, + SearchByTagsQuery, + SearchTxQuery, + SearchTxFilter, +} from "./cosmosclient"; export { unmarshalTx } from "./decoding"; export { makeSignBytes, marshalTx } from "./encoding"; export { BroadcastMode, RestClient, TxsResponse } from "./restclient"; @@ -11,3 +31,4 @@ export { Pen, Secp256k1Pen, makeCosmoshubPath } from "./pen"; export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; export { findSequenceForSignedTx } from "./sequence"; export { encodeSecp256k1Signature, decodeSignature } from "./signature"; +export { FeeTable, SigningCallback, SigningCosmosClient } from "./signingcosmosclient"; diff --git a/packages/sdk38/src/signingcosmosclient.ts b/packages/sdk38/src/signingcosmosclient.ts index f20c7fef..c144955e 100644 --- a/packages/sdk38/src/signingcosmosclient.ts +++ b/packages/sdk38/src/signingcosmosclient.ts @@ -1,7 +1,6 @@ import { Coin, coins } from "./coins"; import { Account, CosmosClient, GetNonceResult, PostTxResult } from "./cosmosclient"; import { makeSignBytes } from "./encoding"; -import { Log } from "./logs"; import { BroadcastMode } from "./restclient"; import { MsgSend, StdFee, StdSignature } from "./types"; @@ -35,43 +34,6 @@ const defaultFees: FeeTable = { }, }; -export interface UploadMeta { - /** The source URL */ - readonly source?: string; - /** The builder tag */ - readonly builder?: string; -} - -export interface UploadResult { - /** 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; - /** The ID of the code asigned by the chain */ - readonly codeId: number; - readonly logs: readonly Log[]; - /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ - readonly transactionHash: string; -} - -export interface InstantiateResult { - /** The address of the newly instantiated contract */ - readonly contractAddress: string; - readonly logs: readonly Log[]; - /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ - readonly transactionHash: string; -} - -export interface ExecuteResult { - readonly logs: readonly Log[]; - /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ - readonly transactionHash: string; -} - export class SigningCosmosClient extends CosmosClient { public readonly senderAddress: string; diff --git a/packages/sdk38/types/index.d.ts b/packages/sdk38/types/index.d.ts index 6f9a672b..78a73f2e 100644 --- a/packages/sdk38/types/index.d.ts +++ b/packages/sdk38/types/index.d.ts @@ -3,6 +3,25 @@ import * as types from "./types"; export { logs, types }; export { pubkeyToAddress, rawSecp256k1PubkeyToAddress } from "./address"; export { Coin, coin, coins } from "./coins"; +export { + Account, + Block, + BlockHeader, + Code, + CodeDetails, + Contract, + ContractDetails, + CosmosClient, + GetNonceResult, + IndexedTx, + PostTxResult, + SearchByHeightQuery, + SearchByIdQuery, + SearchBySentFromOrToQuery, + SearchByTagsQuery, + SearchTxQuery, + SearchTxFilter, +} from "./cosmosclient"; export { unmarshalTx } from "./decoding"; export { makeSignBytes, marshalTx } from "./encoding"; export { BroadcastMode, RestClient, TxsResponse } from "./restclient"; @@ -10,3 +29,4 @@ export { Pen, Secp256k1Pen, makeCosmoshubPath } from "./pen"; export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; export { findSequenceForSignedTx } from "./sequence"; export { encodeSecp256k1Signature, decodeSignature } from "./signature"; +export { FeeTable, SigningCallback, SigningCosmosClient } from "./signingcosmosclient"; diff --git a/packages/sdk38/types/signingcosmosclient.d.ts b/packages/sdk38/types/signingcosmosclient.d.ts index 00202b7c..d9ac523a 100644 --- a/packages/sdk38/types/signingcosmosclient.d.ts +++ b/packages/sdk38/types/signingcosmosclient.d.ts @@ -1,6 +1,5 @@ import { Coin } from "./coins"; import { Account, CosmosClient, GetNonceResult, PostTxResult } from "./cosmosclient"; -import { Log } from "./logs"; import { BroadcastMode } from "./restclient"; import { StdFee, StdSignature } from "./types"; export interface SigningCallback { @@ -12,39 +11,6 @@ export interface FeeTable { readonly exec: StdFee; readonly send: StdFee; } -export interface UploadMeta { - /** The source URL */ - readonly source?: string; - /** The builder tag */ - readonly builder?: string; -} -export interface UploadResult { - /** 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; - /** The ID of the code asigned by the chain */ - readonly codeId: number; - readonly logs: readonly Log[]; - /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ - readonly transactionHash: string; -} -export interface InstantiateResult { - /** The address of the newly instantiated contract */ - readonly contractAddress: string; - readonly logs: readonly Log[]; - /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ - readonly transactionHash: string; -} -export interface ExecuteResult { - readonly logs: readonly Log[]; - /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ - readonly transactionHash: string; -} export declare class SigningCosmosClient extends CosmosClient { readonly senderAddress: string; private readonly signCallback; From ec9bae9028e40a3363ae8045601360e9c68b6981 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 2 Jun 2020 13:45:44 +0200 Subject: [PATCH 05/15] Migrate BCP to @cosmwasm/cosmwasm and @cosmwasm/sdk38 --- packages/bcp/package.json | 3 ++- packages/bcp/src/address.ts | 2 +- packages/bcp/src/cosmwasmcodec.ts | 2 +- packages/bcp/src/cosmwasmconnection.spec.ts | 2 +- packages/bcp/src/cosmwasmconnection.ts | 14 ++++++++++---- packages/bcp/src/decode.spec.ts | 7 ++++--- packages/bcp/src/decode.ts | 5 +++-- packages/bcp/src/encode.ts | 2 +- packages/bcp/types/decode.d.ts | 2 +- packages/bcp/types/encode.d.ts | 2 +- 10 files changed, 25 insertions(+), 16 deletions(-) diff --git a/packages/bcp/package.json b/packages/bcp/package.json index f62bcf4c..5fc202bf 100644 --- a/packages/bcp/package.json +++ b/packages/bcp/package.json @@ -36,7 +36,8 @@ "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" }, "dependencies": { - "@cosmwasm/sdk": "^0.8.0", + "@cosmwasm/cosmwasm": "^0.8.0", + "@cosmwasm/sdk38": "^0.8.0", "@iov/bcp": "^2.1.0", "@iov/crypto": "^2.1.0", "@iov/encoding": "^2.1.0", diff --git a/packages/bcp/src/address.ts b/packages/bcp/src/address.ts index e26cf556..c0cb7b9a 100644 --- a/packages/bcp/src/address.ts +++ b/packages/bcp/src/address.ts @@ -1,4 +1,4 @@ -import { pubkeyToAddress as sdkPubkeyToAddress, types } from "@cosmwasm/sdk"; +import { pubkeyToAddress as sdkPubkeyToAddress, types } from "@cosmwasm/sdk38"; import { Address, Algorithm, PubkeyBundle } from "@iov/bcp"; import { Secp256k1 } from "@iov/crypto"; import { Encoding } from "@iov/encoding"; diff --git a/packages/bcp/src/cosmwasmcodec.ts b/packages/bcp/src/cosmwasmcodec.ts index d7c00754..ddaa88d9 100644 --- a/packages/bcp/src/cosmwasmcodec.ts +++ b/packages/bcp/src/cosmwasmcodec.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { makeSignBytes, marshalTx, unmarshalTx } from "@cosmwasm/sdk"; +import { makeSignBytes, marshalTx, unmarshalTx } from "@cosmwasm/sdk38"; import { Address, ChainId, diff --git a/packages/bcp/src/cosmwasmconnection.spec.ts b/packages/bcp/src/cosmwasmconnection.spec.ts index 4f4b716f..a661a87c 100644 --- a/packages/bcp/src/cosmwasmconnection.spec.ts +++ b/packages/bcp/src/cosmwasmconnection.spec.ts @@ -1,4 +1,4 @@ -import { decodeSignature } from "@cosmwasm/sdk"; +import { decodeSignature } from "@cosmwasm/sdk38"; import { Account, Address, diff --git a/packages/bcp/src/cosmwasmconnection.ts b/packages/bcp/src/cosmwasmconnection.ts index f458413c..56e97ef6 100644 --- a/packages/bcp/src/cosmwasmconnection.ts +++ b/packages/bcp/src/cosmwasmconnection.ts @@ -1,4 +1,10 @@ -import { CosmWasmClient, findSequenceForSignedTx, IndexedTx, SearchTxFilter, types } from "@cosmwasm/sdk"; +import { + CosmWasmClient, + isMsgExecuteContract, + isMsgInstantiateContract, + isMsgStoreCode, +} from "@cosmwasm/cosmwasm"; +import { findSequenceForSignedTx, IndexedTx, SearchTxFilter, types } from "@cosmwasm/sdk38"; import { Account, AccountQuery, @@ -476,9 +482,9 @@ export class CosmWasmConnection implements BlockchainConnection { if (types.isMsgSend(firstMsg)) { senderAddress = firstMsg.value.from_address; } else if ( - types.isMsgStoreCode(firstMsg) || - types.isMsgInstantiateContract(firstMsg) || - types.isMsgExecuteContract(firstMsg) + isMsgStoreCode(firstMsg) || + isMsgInstantiateContract(firstMsg) || + isMsgExecuteContract(firstMsg) ) { senderAddress = firstMsg.value.sender; } else { diff --git a/packages/bcp/src/decode.spec.ts b/packages/bcp/src/decode.spec.ts index 5a641c4f..d40e178d 100644 --- a/packages/bcp/src/decode.spec.ts +++ b/packages/bcp/src/decode.spec.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { Coin, IndexedTx, types } from "@cosmwasm/sdk"; +import { MsgExecuteContract } from "@cosmwasm/cosmwasm"; +import { Coin, IndexedTx, types } from "@cosmwasm/sdk38"; import { Address, Algorithm, isSendTransaction, SendTransaction, TokenTicker } from "@iov/bcp"; import { Encoding } from "@iov/encoding"; import { assert } from "@iov/utils"; @@ -164,7 +165,7 @@ describe("decode", () => { }); it("works for ERC20 send transaction", () => { - const msg: types.MsgExecuteContract = { + const msg: MsgExecuteContract = { type: "wasm/execute", value: { sender: "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r", @@ -218,7 +219,7 @@ describe("decode", () => { }); it("works for ERC20 send transaction", () => { - const msg: types.MsgExecuteContract = { + const msg: MsgExecuteContract = { type: "wasm/execute", value: { sender: "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r", diff --git a/packages/bcp/src/decode.ts b/packages/bcp/src/decode.ts index d258a5d2..0adc918b 100644 --- a/packages/bcp/src/decode.ts +++ b/packages/bcp/src/decode.ts @@ -1,4 +1,5 @@ -import { Coin, IndexedTx, types } from "@cosmwasm/sdk"; +import { isMsgExecuteContract } from "@cosmwasm/cosmwasm"; +import { Coin, IndexedTx, types } from "@cosmwasm/sdk38"; import { Address, Algorithm, @@ -92,7 +93,7 @@ export function parseMsg( memo: memo, }; return send; - } else if (types.isMsgExecuteContract(msg)) { + } else if (isMsgExecuteContract(msg)) { const matchingTokenContract = erc20Tokens.find((t) => t.contractAddress === msg.value.contract); if (!matchingTokenContract) { return { diff --git a/packages/bcp/src/encode.ts b/packages/bcp/src/encode.ts index 9d20f3d8..61006eeb 100644 --- a/packages/bcp/src/encode.ts +++ b/packages/bcp/src/encode.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { Coin, encodeSecp256k1Pubkey, encodeSecp256k1Signature, types } from "@cosmwasm/sdk"; +import { Coin, encodeSecp256k1Pubkey, encodeSecp256k1Signature, types } from "@cosmwasm/sdk38"; import { Algorithm, Amount, diff --git a/packages/bcp/types/decode.d.ts b/packages/bcp/types/decode.d.ts index ec69e99e..cc52190a 100644 --- a/packages/bcp/types/decode.d.ts +++ b/packages/bcp/types/decode.d.ts @@ -1,4 +1,4 @@ -import { Coin, IndexedTx, types } from "@cosmwasm/sdk"; +import { Coin, IndexedTx, types } from "@cosmwasm/sdk38"; import { Amount, ChainId, diff --git a/packages/bcp/types/encode.d.ts b/packages/bcp/types/encode.d.ts index f34e16f6..316e09d2 100644 --- a/packages/bcp/types/encode.d.ts +++ b/packages/bcp/types/encode.d.ts @@ -1,4 +1,4 @@ -import { Coin, types } from "@cosmwasm/sdk"; +import { Coin, types } from "@cosmwasm/sdk38"; import { Amount, Fee, FullSignature, PubkeyBundle, SignedTransaction, UnsignedTransaction } from "@iov/bcp"; import { BankToken, Erc20Token } from "./types"; export declare function encodePubkey(pubkey: PubkeyBundle): types.PubKey; From 948d24aef7c316e70c66c50d4b0fd7140f6d241e Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 2 Jun 2020 13:50:01 +0200 Subject: [PATCH 06/15] Migrate demo-staking to @cosmwasm/cosmwasm and @cosmwasm/sdk38 --- packages/demo-staking/package.json | 3 ++- packages/demo-staking/src/index.spec.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/demo-staking/package.json b/packages/demo-staking/package.json index 4ea57d57..4d50ff08 100644 --- a/packages/demo-staking/package.json +++ b/packages/demo-staking/package.json @@ -34,7 +34,8 @@ "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" }, "dependencies": { - "@cosmwasm/sdk": "^0.8.0", + "@cosmwasm/cosmwasm": "^0.8.0", + "@cosmwasm/sdk38": "^0.8.0", "@iov/crypto": "^2.1.0", "@iov/encoding": "^2.1.0", "@iov/stream": "^2.0.2", diff --git a/packages/demo-staking/src/index.spec.ts b/packages/demo-staking/src/index.spec.ts index 23a86ad3..1328918f 100644 --- a/packages/demo-staking/src/index.spec.ts +++ b/packages/demo-staking/src/index.spec.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { Coin, coins, makeCosmoshubPath, Secp256k1Pen, SigningCosmWasmClient } from "@cosmwasm/sdk"; +import { SigningCosmWasmClient } from "@cosmwasm/cosmwasm"; +import { Coin, coins, makeCosmoshubPath, Secp256k1Pen } from "@cosmwasm/sdk38"; import { BalanceResponse, From 2c8349a10c52945c3338eda4d51367c63aa37b78 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 2 Jun 2020 13:57:28 +0200 Subject: [PATCH 07/15] Migrate cli to @cosmwasm/cosmwasm and @cosmwasm/sdk38 --- packages/cli/package.json | 3 ++- packages/cli/src/cli.ts | 31 ++++++++++++++++++------------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 74e028f4..41231aa9 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -38,7 +38,8 @@ "!**/testdata/" ], "dependencies": { - "@cosmwasm/sdk": "^0.8.0", + "@cosmwasm/cosmwasm": "^0.8.0", + "@cosmwasm/sdk38": "^0.8.0", "@iov/crypto": "^2.1.0", "@iov/encoding": "^2.1.0", "@iov/utils": "^2.0.2", diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index d31b0eac..83932e1f 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -30,19 +30,8 @@ export function main(originalArgs: readonly string[]): void { const imports = new Map([ [ - "@cosmwasm/sdk", + "@cosmwasm/cosmwasm", [ - "encodeSecp256k1Pubkey", - "encodeSecp256k1Signature", - "logs", - "makeCosmoshubPath", - "makeSignBytes", - "marshalTx", - "Pen", - "pubkeyToAddress", - "RestClient", - "Secp256k1Pen", - "types", // cosmwasmclient "Account", "Block", @@ -53,7 +42,6 @@ export function main(originalArgs: readonly string[]): void { "ContractDetails", "CosmWasmClient", "GetNonceResult", - "IndexedTx", "PostTxResult", "SearchByHeightQuery", "SearchByIdQuery", @@ -71,6 +59,23 @@ export function main(originalArgs: readonly string[]): void { "UploadResult", ], ], + [ + "@cosmwasm/sdk38", + [ + "encodeSecp256k1Pubkey", + "encodeSecp256k1Signature", + "logs", + "makeCosmoshubPath", + "makeSignBytes", + "marshalTx", + "Pen", + "pubkeyToAddress", + "RestClient", + "Secp256k1Pen", + "types", + "IndexedTx", + ], + ], [ "@iov/crypto", [ From 5c6c501d3aee9ee1344eb830674e637c95c34838 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 2 Jun 2020 13:57:47 +0200 Subject: [PATCH 08/15] Fix title in faucet README --- packages/faucet/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/faucet/README.md b/packages/faucet/README.md index ec983ca5..3daafd70 100644 --- a/packages/faucet/README.md +++ b/packages/faucet/README.md @@ -1,4 +1,4 @@ -# @cosmwasm/sdk +# @cosmwasm/faucet [![npm version](https://img.shields.io/npm/v/@cosmwasm/faucet.svg)](https://www.npmjs.com/package/@cosmwasm/faucet) From cd632a290e85e9ffd7cbeb3d83e1b3c7b56e7a7e Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 2 Jun 2020 14:57:52 +0200 Subject: [PATCH 09/15] Adapt deployment scrips --- scripts/wasmd/deploy_erc20.js | 3 ++- scripts/wasmd/deploy_nameservice.js | 3 ++- scripts/wasmd/deploy_staking.js | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/wasmd/deploy_erc20.js b/scripts/wasmd/deploy_erc20.js index 95b2db50..8fc612a9 100755 --- a/scripts/wasmd/deploy_erc20.js +++ b/scripts/wasmd/deploy_erc20.js @@ -1,7 +1,8 @@ #!/usr/bin/env node /* eslint-disable @typescript-eslint/camelcase */ -const { SigningCosmWasmClient, Secp256k1Pen } = require("@cosmwasm/sdk"); +const { SigningCosmWasmClient } = require("@cosmwasm/cosmwasm"); +const { Secp256k1Pen } = require("@cosmwasm/sdk38"); const fs = require("fs"); const httpUrl = "http://localhost:1317"; diff --git a/scripts/wasmd/deploy_nameservice.js b/scripts/wasmd/deploy_nameservice.js index ca9de2ef..d2e3c196 100755 --- a/scripts/wasmd/deploy_nameservice.js +++ b/scripts/wasmd/deploy_nameservice.js @@ -1,7 +1,8 @@ #!/usr/bin/env node /* eslint-disable @typescript-eslint/camelcase */ -const { SigningCosmWasmClient, Secp256k1Pen } = require("@cosmwasm/sdk"); +const { SigningCosmWasmClient } = require("@cosmwasm/cosmwasm"); +const { Secp256k1Pen } = require("@cosmwasm/sdk38"); const fs = require("fs"); const httpUrl = "http://localhost:1317"; diff --git a/scripts/wasmd/deploy_staking.js b/scripts/wasmd/deploy_staking.js index 1369fddf..56341cbc 100755 --- a/scripts/wasmd/deploy_staking.js +++ b/scripts/wasmd/deploy_staking.js @@ -1,7 +1,8 @@ #!/usr/bin/env node /* eslint-disable @typescript-eslint/camelcase */ -const { SigningCosmWasmClient, Secp256k1Pen } = require("@cosmwasm/sdk"); +const { SigningCosmWasmClient } = require("@cosmwasm/cosmwasm"); +const { Secp256k1Pen } = require("@cosmwasm/sdk38"); const fs = require("fs"); const httpUrl = "http://localhost:1317"; From 0fe3e5eb4afe44131454792f85d1c622a370d5fd Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 2 Jun 2020 14:58:15 +0200 Subject: [PATCH 10/15] Remove @cosmwasm/sdk --- packages/sdk/.eslintignore | 1 - packages/sdk/.gitignore | 3 - packages/sdk/README.md | 12 - packages/sdk/jasmine-testrunner.js | 26 - packages/sdk/karma.conf.js | 54 - packages/sdk/nonces/1580975947 | 0 packages/sdk/nonces/1580980830 | 0 packages/sdk/nonces/1581790180 | 0 packages/sdk/nonces/1582043857 | 0 packages/sdk/nonces/1582062520 | 0 packages/sdk/nonces/1582198421 | 0 packages/sdk/nonces/1582208955 | 0 packages/sdk/nonces/1582291410 | 0 packages/sdk/nonces/1583183690 | 0 packages/sdk/nonces/1583495327 | 0 packages/sdk/nonces/1583509277 | 0 packages/sdk/nonces/1583541822 | 0 packages/sdk/nonces/1583854671 | 0 packages/sdk/nonces/1583941462 | 0 packages/sdk/nonces/1584541301 | 0 packages/sdk/nonces/1584959328 | 0 packages/sdk/nonces/1589983882 | 0 packages/sdk/nonces/1589986013 | 0 packages/sdk/nonces/1590408566 | 0 packages/sdk/nonces/README.txt | 1 - packages/sdk/package.json | 50 - packages/sdk/src/address.spec.ts | 27 - packages/sdk/src/address.ts | 42 - packages/sdk/src/builder.spec.ts | 63 - packages/sdk/src/builder.ts | 20 - packages/sdk/src/coins.ts | 14 - .../sdk/src/cosmwasmclient.searchtx.spec.ts | 467 ------ packages/sdk/src/cosmwasmclient.spec.ts | 467 ------ packages/sdk/src/cosmwasmclient.ts | 439 ------ packages/sdk/src/decoding.ts | 11 - packages/sdk/src/encoding.spec.ts | 1 - packages/sdk/src/encoding.ts | 59 - packages/sdk/src/index.ts | 41 - packages/sdk/src/logs.spec.ts | 165 -- packages/sdk/src/logs.ts | 86 - packages/sdk/src/pen.spec.ts | 56 - packages/sdk/src/pen.ts | 92 -- packages/sdk/src/pubkey.spec.ts | 57 - packages/sdk/src/pubkey.ts | 71 - packages/sdk/src/restclient.spec.ts | 1378 ----------------- packages/sdk/src/restclient.ts | 457 ------ packages/sdk/src/sequence.spec.ts | 30 - packages/sdk/src/sequence.ts | 46 - packages/sdk/src/signature.spec.ts | 69 - packages/sdk/src/signature.ts | 39 - .../sdk/src/signingcosmwasmclient.spec.ts | 230 --- packages/sdk/src/signingcosmwasmclient.ts | 284 ---- packages/sdk/src/testdata/contract.json | 4 - packages/sdk/src/testdata/cosmoshub.json | 44 - packages/sdk/src/testdata/txresponse1.json | 57 - packages/sdk/src/testdata/txresponse2.json | 57 - packages/sdk/src/testdata/txresponse3.json | 57 - packages/sdk/src/testutils.spec.ts | 77 - packages/sdk/src/types.ts | 174 --- packages/sdk/tsconfig.json | 12 - packages/sdk/typedoc.js | 14 - packages/sdk/types/address.d.ts | 3 - packages/sdk/types/builder.d.ts | 1 - packages/sdk/types/coins.d.ts | 8 - packages/sdk/types/cosmwasmclient.d.ts | 175 --- packages/sdk/types/decoding.d.ts | 2 - packages/sdk/types/encoding.d.ts | 10 - packages/sdk/types/index.d.ts | 40 - packages/sdk/types/logs.d.ts | 28 - packages/sdk/types/pen.d.ts | 33 - packages/sdk/types/pubkey.d.ts | 4 - packages/sdk/types/restclient.d.ts | 231 --- packages/sdk/types/sequence.d.ts | 19 - packages/sdk/types/signature.d.ts | 14 - packages/sdk/types/signingcosmwasmclient.d.ts | 89 -- packages/sdk/types/types.d.ts | 123 -- packages/sdk/webpack.web.config.js | 19 - 77 files changed, 6153 deletions(-) delete mode 120000 packages/sdk/.eslintignore delete mode 100644 packages/sdk/.gitignore delete mode 100644 packages/sdk/README.md delete mode 100755 packages/sdk/jasmine-testrunner.js delete mode 100644 packages/sdk/karma.conf.js delete mode 100644 packages/sdk/nonces/1580975947 delete mode 100644 packages/sdk/nonces/1580980830 delete mode 100644 packages/sdk/nonces/1581790180 delete mode 100644 packages/sdk/nonces/1582043857 delete mode 100644 packages/sdk/nonces/1582062520 delete mode 100644 packages/sdk/nonces/1582198421 delete mode 100644 packages/sdk/nonces/1582208955 delete mode 100644 packages/sdk/nonces/1582291410 delete mode 100644 packages/sdk/nonces/1583183690 delete mode 100644 packages/sdk/nonces/1583495327 delete mode 100644 packages/sdk/nonces/1583509277 delete mode 100644 packages/sdk/nonces/1583541822 delete mode 100644 packages/sdk/nonces/1583854671 delete mode 100644 packages/sdk/nonces/1583941462 delete mode 100644 packages/sdk/nonces/1584541301 delete mode 100644 packages/sdk/nonces/1584959328 delete mode 100644 packages/sdk/nonces/1589983882 delete mode 100644 packages/sdk/nonces/1589986013 delete mode 100644 packages/sdk/nonces/1590408566 delete mode 100644 packages/sdk/nonces/README.txt delete mode 100644 packages/sdk/package.json delete mode 100644 packages/sdk/src/address.spec.ts delete mode 100644 packages/sdk/src/address.ts delete mode 100644 packages/sdk/src/builder.spec.ts delete mode 100644 packages/sdk/src/builder.ts delete mode 100644 packages/sdk/src/coins.ts delete mode 100644 packages/sdk/src/cosmwasmclient.searchtx.spec.ts delete mode 100644 packages/sdk/src/cosmwasmclient.spec.ts delete mode 100644 packages/sdk/src/cosmwasmclient.ts delete mode 100644 packages/sdk/src/decoding.ts delete mode 100644 packages/sdk/src/encoding.spec.ts delete mode 100644 packages/sdk/src/encoding.ts delete mode 100644 packages/sdk/src/index.ts delete mode 100644 packages/sdk/src/logs.spec.ts delete mode 100644 packages/sdk/src/logs.ts delete mode 100644 packages/sdk/src/pen.spec.ts delete mode 100644 packages/sdk/src/pen.ts delete mode 100644 packages/sdk/src/pubkey.spec.ts delete mode 100644 packages/sdk/src/pubkey.ts delete mode 100644 packages/sdk/src/restclient.spec.ts delete mode 100644 packages/sdk/src/restclient.ts delete mode 100644 packages/sdk/src/sequence.spec.ts delete mode 100644 packages/sdk/src/sequence.ts delete mode 100644 packages/sdk/src/signature.spec.ts delete mode 100644 packages/sdk/src/signature.ts delete mode 100644 packages/sdk/src/signingcosmwasmclient.spec.ts delete mode 100644 packages/sdk/src/signingcosmwasmclient.ts delete mode 100644 packages/sdk/src/testdata/contract.json delete mode 100644 packages/sdk/src/testdata/cosmoshub.json delete mode 100644 packages/sdk/src/testdata/txresponse1.json delete mode 100644 packages/sdk/src/testdata/txresponse2.json delete mode 100644 packages/sdk/src/testdata/txresponse3.json delete mode 100644 packages/sdk/src/testutils.spec.ts delete mode 100644 packages/sdk/src/types.ts delete mode 100644 packages/sdk/tsconfig.json delete mode 100644 packages/sdk/typedoc.js delete mode 100644 packages/sdk/types/address.d.ts delete mode 100644 packages/sdk/types/builder.d.ts delete mode 100644 packages/sdk/types/coins.d.ts delete mode 100644 packages/sdk/types/cosmwasmclient.d.ts delete mode 100644 packages/sdk/types/decoding.d.ts delete mode 100644 packages/sdk/types/encoding.d.ts delete mode 100644 packages/sdk/types/index.d.ts delete mode 100644 packages/sdk/types/logs.d.ts delete mode 100644 packages/sdk/types/pen.d.ts delete mode 100644 packages/sdk/types/pubkey.d.ts delete mode 100644 packages/sdk/types/restclient.d.ts delete mode 100644 packages/sdk/types/sequence.d.ts delete mode 100644 packages/sdk/types/signature.d.ts delete mode 100644 packages/sdk/types/signingcosmwasmclient.d.ts delete mode 100644 packages/sdk/types/types.d.ts delete mode 100644 packages/sdk/webpack.web.config.js diff --git a/packages/sdk/.eslintignore b/packages/sdk/.eslintignore deleted file mode 120000 index 86039baf..00000000 --- a/packages/sdk/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -../../.eslintignore \ No newline at end of file diff --git a/packages/sdk/.gitignore b/packages/sdk/.gitignore deleted file mode 100644 index 68bf3735..00000000 --- a/packages/sdk/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -build/ -dist/ -docs/ diff --git a/packages/sdk/README.md b/packages/sdk/README.md deleted file mode 100644 index 763ef5e6..00000000 --- a/packages/sdk/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# @cosmwasm/sdk - -[![npm version](https://img.shields.io/npm/v/@cosmwasm/sdk.svg)](https://www.npmjs.com/package/@cosmwasm/sdk) - -An SDK to build CosmWasm clients. - -## License - -This package is part of the cosmwasm-js repository, licensed under the Apache -License 2.0 (see -[NOTICE](https://github.com/confio/cosmwasm-js/blob/master/NOTICE) and -[LICENSE](https://github.com/confio/cosmwasm-js/blob/master/LICENSE)). diff --git a/packages/sdk/jasmine-testrunner.js b/packages/sdk/jasmine-testrunner.js deleted file mode 100755 index 9fada59b..00000000 --- a/packages/sdk/jasmine-testrunner.js +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env node - -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 }); - -// initialize and execute -jasmine.env.clearReporters(); -jasmine.addReporter(reporter); -jasmine.execute(); diff --git a/packages/sdk/karma.conf.js b/packages/sdk/karma.conf.js deleted file mode 100644 index e68db403..00000000 --- a/packages/sdk/karma.conf.js +++ /dev/null @@ -1,54 +0,0 @@ -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://npmjs.org/browse/keyword/karma-adapter - frameworks: ["jasmine"], - - // list of files / patterns to load in the browser - files: ["dist/web/tests.js"], - - client: { - jasmine: { - random: false, - timeoutInterval: 15000, - }, - }, - - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ["progress", "kjhtml"], - - // 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: false, - - // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ["Firefox"], - - browserNoActivityTimeout: 90000, - - // Keep brower open for debugging. This is overridden by yarn scripts - singleRun: false, - - customLaunchers: { - ChromeHeadlessInsecure: { - base: "ChromeHeadless", - flags: ["--disable-web-security"], - }, - }, - }); -}; diff --git a/packages/sdk/nonces/1580975947 b/packages/sdk/nonces/1580975947 deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/sdk/nonces/1580980830 b/packages/sdk/nonces/1580980830 deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/sdk/nonces/1581790180 b/packages/sdk/nonces/1581790180 deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/sdk/nonces/1582043857 b/packages/sdk/nonces/1582043857 deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/sdk/nonces/1582062520 b/packages/sdk/nonces/1582062520 deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/sdk/nonces/1582198421 b/packages/sdk/nonces/1582198421 deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/sdk/nonces/1582208955 b/packages/sdk/nonces/1582208955 deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/sdk/nonces/1582291410 b/packages/sdk/nonces/1582291410 deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/sdk/nonces/1583183690 b/packages/sdk/nonces/1583183690 deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/sdk/nonces/1583495327 b/packages/sdk/nonces/1583495327 deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/sdk/nonces/1583509277 b/packages/sdk/nonces/1583509277 deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/sdk/nonces/1583541822 b/packages/sdk/nonces/1583541822 deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/sdk/nonces/1583854671 b/packages/sdk/nonces/1583854671 deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/sdk/nonces/1583941462 b/packages/sdk/nonces/1583941462 deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/sdk/nonces/1584541301 b/packages/sdk/nonces/1584541301 deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/sdk/nonces/1584959328 b/packages/sdk/nonces/1584959328 deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/sdk/nonces/1589983882 b/packages/sdk/nonces/1589983882 deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/sdk/nonces/1589986013 b/packages/sdk/nonces/1589986013 deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/sdk/nonces/1590408566 b/packages/sdk/nonces/1590408566 deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/sdk/nonces/README.txt b/packages/sdk/nonces/README.txt deleted file mode 100644 index 092fe732..00000000 --- a/packages/sdk/nonces/README.txt +++ /dev/null @@ -1 +0,0 @@ -Directory used to trigger lerna package updates for all packages diff --git a/packages/sdk/package.json b/packages/sdk/package.json deleted file mode 100644 index 8b5a4f04..00000000 --- a/packages/sdk/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "@cosmwasm/sdk", - "version": "0.8.0", - "description": "CosmWasm SDK", - "author": "Ethan Frey ", - "license": "Apache-2.0", - "main": "build/index.js", - "types": "types/index.d.ts", - "files": [ - "build/", - "types/", - "*.md", - "!*.spec.*", - "!**/testdata/" - ], - "repository": { - "type": "git", - "url": "https://github.com/confio/cosmwasm-js/tree/master/packages/sdk" - }, - "publishConfig": { - "access": "public" - }, - "scripts": { - "docs": "shx rm -rf docs && typedoc --options typedoc.js", - "format": "prettier --write --loglevel warn \"./src/**/*.ts\"", - "lint": "eslint --max-warnings 0 \"**/*.{js,ts}\"", - "lint-fix": "eslint --max-warnings 0 \"**/*.{js,ts}\" --fix", - "move-types": "shx rm -rf ./types/* && shx mv build/types/* ./types && rm -rf ./types/testdata && shx rm -f ./types/*.spec.d.ts", - "format-types": "prettier --write --loglevel warn \"./types/**/*.d.ts\"", - "build": "shx rm -rf ./build && tsc && yarn move-types && yarn format-types", - "build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build", - "test-node": "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 ChromeHeadlessInsecure", - "test": "yarn build-or-skip && yarn test-node", - "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" - }, - "dependencies": { - "@iov/crypto": "^2.1.0", - "@iov/encoding": "^2.1.0", - "@iov/utils": "^2.0.2", - "axios": "^0.19.0", - "fast-deep-equal": "^3.1.1", - "pako": "^1.0.11" - }, - "devDependencies": { - "@types/pako": "^1.0.1", - "readonly-date": "^1.0.0" - } -} diff --git a/packages/sdk/src/address.spec.ts b/packages/sdk/src/address.spec.ts deleted file mode 100644 index 713ffd03..00000000 --- a/packages/sdk/src/address.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Encoding } from "@iov/encoding"; - -import { pubkeyToAddress } from "./address"; - -const { toBase64, fromHex } = Encoding; - -describe("address", () => { - describe("pubkeyToAddress", () => { - it("works for Secp256k1 compressed", () => { - const prefix = "cosmos"; - const pubkey = { - type: "tendermint/PubKeySecp256k1", - value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP", - }; - expect(pubkeyToAddress(pubkey, prefix)).toEqual("cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r"); - }); - - it("works for Ed25519", () => { - const prefix = "cosmos"; - const pubkey = { - type: "tendermint/PubKeyEd25519", - value: toBase64(fromHex("12ee6f581fe55673a1e9e1382a0829e32075a0aa4763c968bc526e1852e78c95")), - }; - expect(pubkeyToAddress(pubkey, prefix)).toEqual("cosmos1pfq05em6sfkls66ut4m2257p7qwlk448h8mysz"); - }); - }); -}); diff --git a/packages/sdk/src/address.ts b/packages/sdk/src/address.ts deleted file mode 100644 index 126d8fb8..00000000 --- a/packages/sdk/src/address.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Ripemd160, Sha256 } from "@iov/crypto"; -import { Bech32, Encoding } from "@iov/encoding"; - -import { PubKey, pubkeyType } from "./types"; - -const { fromBase64 } = Encoding; - -export function rawSecp256k1PubkeyToAddress(pubkeyRaw: Uint8Array, prefix: string): string { - if (pubkeyRaw.length !== 33) { - throw new Error(`Invalid Secp256k1 pubkey length (compressed): ${pubkeyRaw.length}`); - } - const hash1 = new Sha256(pubkeyRaw).digest(); - const hash2 = new Ripemd160(hash1).digest(); - return Bech32.encode(prefix, hash2); -} - -// See https://github.com/tendermint/tendermint/blob/f2ada0a604b4c0763bda2f64fac53d506d3beca7/docs/spec/blockchain/encoding.md#public-key-cryptography -// This assumes we already have a cosmos-compressed pubkey -export function pubkeyToAddress(pubkey: PubKey, prefix: string): string { - const pubkeyBytes = fromBase64(pubkey.value); - switch (pubkey.type) { - case pubkeyType.secp256k1: { - return rawSecp256k1PubkeyToAddress(pubkeyBytes, prefix); - } - case pubkeyType.ed25519: { - if (pubkeyBytes.length !== 32) { - throw new Error(`Invalid Ed25519 pubkey length: ${pubkeyBytes.length}`); - } - const hash = new Sha256(pubkeyBytes).digest(); - return Bech32.encode(prefix, hash.slice(0, 20)); - } - case pubkeyType.sr25519: { - if (pubkeyBytes.length !== 32) { - throw new Error(`Invalid Sr25519 pubkey length: ${pubkeyBytes.length}`); - } - const hash = new Sha256(pubkeyBytes).digest(); - return Bech32.encode(prefix, hash.slice(0, 20)); - } - default: - throw new Error("Unrecognized public key algorithm"); - } -} diff --git a/packages/sdk/src/builder.spec.ts b/packages/sdk/src/builder.spec.ts deleted file mode 100644 index 20926f4c..00000000 --- a/packages/sdk/src/builder.spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { isValidBuilder } from "./builder"; - -describe("builder", () => { - describe("isValidBuilder", () => { - // Valid cases - - it("returns true for simple examples", () => { - expect(isValidBuilder("myorg/super-optimizer:0.1.2")).toEqual(true); - expect(isValidBuilder("myorg/super-optimizer:42")).toEqual(true); - }); - - it("supports images with multi level names", () => { - expect(isValidBuilder("myorg/department-x/office-y/technology-z/super-optimizer:0.1.2")).toEqual(true); - }); - - it("returns true for tags with lower and upper chars", () => { - expect(isValidBuilder("myorg/super-optimizer:0.1.2-alpha")).toEqual(true); - expect(isValidBuilder("myorg/super-optimizer:0.1.2-Alpha")).toEqual(true); - }); - - // Invalid cases - - it("returns false for missing or empty tag", () => { - expect(isValidBuilder("myorg/super-optimizer")).toEqual(false); - expect(isValidBuilder("myorg/super-optimizer:")).toEqual(false); - }); - - it("returns false for name components starting or ending with a separator", () => { - expect(isValidBuilder(".myorg/super-optimizer:42")).toEqual(false); - expect(isValidBuilder("-myorg/super-optimizer:42")).toEqual(false); - expect(isValidBuilder("_myorg/super-optimizer:42")).toEqual(false); - expect(isValidBuilder("myorg./super-optimizer:42")).toEqual(false); - expect(isValidBuilder("myorg-/super-optimizer:42")).toEqual(false); - expect(isValidBuilder("myorg_/super-optimizer:42")).toEqual(false); - expect(isValidBuilder("myorg/.super-optimizer:42")).toEqual(false); - expect(isValidBuilder("myorg/-super-optimizer:42")).toEqual(false); - expect(isValidBuilder("myorg/_super-optimizer:42")).toEqual(false); - expect(isValidBuilder("myorg/super-optimizer.:42")).toEqual(false); - expect(isValidBuilder("myorg/super-optimizer-:42")).toEqual(false); - expect(isValidBuilder("myorg/super-optimizer_:42")).toEqual(false); - }); - - it("returns false for upper case character in name component", () => { - expect(isValidBuilder("mYorg/super-optimizer:42")).toEqual(false); - expect(isValidBuilder("myorg/super-Optimizer:42")).toEqual(false); - }); - - it("returns false for long images", () => { - expect( - isValidBuilder( - "myorgisnicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenice/super-optimizer:42", - ), - ).toEqual(false); - }); - - it("returns false for images with no organization", () => { - // Those are valid dockerhub images from https://hub.docker.com/_/ubuntu and https://hub.docker.com/_/rust - // but not valid in the context of CosmWasm Verify - expect(isValidBuilder("ubuntu:xenial-20200212")).toEqual(false); - expect(isValidBuilder("rust:1.40.0")).toEqual(false); - }); - }); -}); diff --git a/packages/sdk/src/builder.ts b/packages/sdk/src/builder.ts deleted file mode 100644 index 31a790c7..00000000 --- a/packages/sdk/src/builder.ts +++ /dev/null @@ -1,20 +0,0 @@ -// A docker image regexp. We remove support for non-standard registries for simplicity. -// https://docs.docker.com/engine/reference/commandline/tag/#extended-description -// -// An image name is made up of slash-separated name components (optionally prefixed by a registry hostname). -// Name components may contain lowercase characters, digits and separators. -// A separator is defined as a period, one or two underscores, or one or more dashes. A name component may not start or end with a separator. -// -// A tag name must be valid ASCII and may contain lowercase and uppercase letters, digits, underscores, periods and dashes. -// A tag name may not start with a period or a dash and may contain a maximum of 128 characters. -const dockerImagePattern = new RegExp( - "^[a-z0-9][a-z0-9._-]*[a-z0-9](/[a-z0-9][a-z0-9._-]*[a-z0-9])+:[a-zA-Z0-9_][a-zA-Z0-9_.-]{0,127}$", -); - -/** Max length in bytes/characters (regexp enforces all ASCII, even if that is not required by the standard) */ -const builderMaxLength = 128; - -export function isValidBuilder(builder: string): boolean { - if (builder.length > builderMaxLength) return false; - return !!builder.match(dockerImagePattern); -} diff --git a/packages/sdk/src/coins.ts b/packages/sdk/src/coins.ts deleted file mode 100644 index 62f5f9ce..00000000 --- a/packages/sdk/src/coins.ts +++ /dev/null @@ -1,14 +0,0 @@ -export interface Coin { - readonly denom: string; - readonly amount: string; -} - -/** Creates a coin */ -export function coin(amount: number, denom: string): Coin { - return { amount: amount.toString(), denom: denom }; -} - -/** Creates a list of coins with one element */ -export function coins(amount: number, denom: string): Coin[] { - return [coin(amount, denom)]; -} diff --git a/packages/sdk/src/cosmwasmclient.searchtx.spec.ts b/packages/sdk/src/cosmwasmclient.searchtx.spec.ts deleted file mode 100644 index cfe91678..00000000 --- a/packages/sdk/src/cosmwasmclient.searchtx.spec.ts +++ /dev/null @@ -1,467 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { assert, sleep } from "@iov/utils"; - -import { Coin } from "./coins"; -import { CosmWasmClient } from "./cosmwasmclient"; -import { makeSignBytes } from "./encoding"; -import { Secp256k1Pen } from "./pen"; -import { RestClient } from "./restclient"; -import { SigningCosmWasmClient } from "./signingcosmwasmclient"; -import { - deployedErc20, - faucet, - fromOneElementArray, - makeRandomAddress, - pendingWithoutWasmd, - wasmd, - wasmdEnabled, -} from "./testutils.spec"; -import { CosmosSdkTx, isMsgExecuteContract, isMsgInstantiateContract, isMsgSend, MsgSend } from "./types"; - -describe("CosmWasmClient.searchTx", () => { - let sendSuccessful: - | { - readonly sender: string; - readonly recipient: string; - readonly hash: string; - readonly height: number; - readonly tx: CosmosSdkTx; - } - | undefined; - let sendUnsuccessful: - | { - readonly sender: string; - readonly recipient: string; - readonly hash: string; - readonly height: number; - readonly tx: CosmosSdkTx; - } - | undefined; - let postedExecute: - | { - readonly sender: string; - readonly contract: string; - readonly hash: string; - readonly height: number; - readonly tx: CosmosSdkTx; - } - | undefined; - - beforeAll(async () => { - if (wasmdEnabled()) { - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmWasmClient(wasmd.endpoint, faucet.address, (signBytes) => - pen.sign(signBytes), - ); - - { - const recipient = makeRandomAddress(); - const transferAmount: Coin = { - denom: "ucosm", - amount: "1234567", - }; - const result = await client.sendTokens(recipient, [transferAmount]); - await sleep(50); // wait until tx is indexed - const txDetails = await new RestClient(wasmd.endpoint).txById(result.transactionHash); - sendSuccessful = { - sender: faucet.address, - recipient: recipient, - hash: result.transactionHash, - height: Number.parseInt(txDetails.height, 10), - tx: txDetails.tx, - }; - } - - { - const memo = "Sending more than I can afford"; - const recipient = makeRandomAddress(); - const transferAmount = [ - { - denom: "ucosm", - amount: "123456700000000", - }, - ]; - const sendMsg: MsgSend = { - type: "cosmos-sdk/MsgSend", - value: { - // eslint-disable-next-line @typescript-eslint/camelcase - from_address: faucet.address, - // eslint-disable-next-line @typescript-eslint/camelcase - to_address: recipient, - amount: transferAmount, - }, - }; - const fee = { - amount: [ - { - denom: "ucosm", - amount: "2000", - }, - ], - gas: "80000", // 80k - }; - const { accountNumber, sequence } = await client.getNonce(); - const chainId = await client.getChainId(); - const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); - const signature = await pen.sign(signBytes); - const tx: CosmosSdkTx = { - type: "cosmos-sdk/StdTx", - value: { - msg: [sendMsg], - fee: fee, - memo: memo, - signatures: [signature], - }, - }; - const transactionId = await client.getIdentifier(tx); - const heightBeforeThis = await client.getHeight(); - try { - await client.postTx(tx.value); - } catch (error) { - // postTx() throws on execution failures, which is a questionable design. Ignore for now. - // console.log(error); - } - sendUnsuccessful = { - sender: faucet.address, - recipient: recipient, - hash: transactionId, - height: heightBeforeThis + 1, - tx: tx, - }; - } - - { - const hashInstance = deployedErc20.instances[0]; - const msg = { - approve: { - spender: makeRandomAddress(), - amount: "12", - }, - }; - const result = await client.execute(hashInstance, msg); - await sleep(50); // wait until tx is indexed - const txDetails = await new RestClient(wasmd.endpoint).txById(result.transactionHash); - postedExecute = { - sender: faucet.address, - contract: hashInstance, - hash: result.transactionHash, - height: Number.parseInt(txDetails.height, 10), - tx: txDetails.tx, - }; - } - } - }); - - describe("with SearchByIdQuery", () => { - it("can search successful tx by ID", async () => { - pendingWithoutWasmd(); - assert(sendSuccessful, "value must be set in beforeAll()"); - const client = new CosmWasmClient(wasmd.endpoint); - const result = await client.searchTx({ id: sendSuccessful.hash }); - expect(result.length).toEqual(1); - expect(result[0]).toEqual( - jasmine.objectContaining({ - height: sendSuccessful.height, - hash: sendSuccessful.hash, - code: 0, - tx: sendSuccessful.tx, - }), - ); - }); - - it("can search unsuccessful tx by ID", async () => { - pendingWithoutWasmd(); - assert(sendUnsuccessful, "value must be set in beforeAll()"); - const client = new CosmWasmClient(wasmd.endpoint); - const result = await client.searchTx({ id: sendUnsuccessful.hash }); - expect(result.length).toEqual(1); - expect(result[0]).toEqual( - jasmine.objectContaining({ - height: sendUnsuccessful.height, - hash: sendUnsuccessful.hash, - code: 5, - tx: sendUnsuccessful.tx, - }), - ); - }); - - it("can search by ID (non existent)", async () => { - pendingWithoutWasmd(); - const client = new CosmWasmClient(wasmd.endpoint); - const nonExistentId = "0000000000000000000000000000000000000000000000000000000000000000"; - const result = await client.searchTx({ id: nonExistentId }); - expect(result.length).toEqual(0); - }); - - it("can search by ID and filter by minHeight", async () => { - pendingWithoutWasmd(); - assert(sendSuccessful); - const client = new CosmWasmClient(wasmd.endpoint); - const query = { id: sendSuccessful.hash }; - - { - 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); - } - }); - }); - - describe("with SearchByHeightQuery", () => { - it("can search successful tx by height", async () => { - pendingWithoutWasmd(); - assert(sendSuccessful, "value must be set in beforeAll()"); - const client = new CosmWasmClient(wasmd.endpoint); - const result = await client.searchTx({ height: sendSuccessful.height }); - expect(result.length).toEqual(1); - expect(result[0]).toEqual( - jasmine.objectContaining({ - height: sendSuccessful.height, - hash: sendSuccessful.hash, - code: 0, - tx: sendSuccessful.tx, - }), - ); - }); - - it("can search unsuccessful tx by height", async () => { - pendingWithoutWasmd(); - assert(sendUnsuccessful, "value must be set in beforeAll()"); - const client = new CosmWasmClient(wasmd.endpoint); - const result = await client.searchTx({ height: sendUnsuccessful.height }); - expect(result.length).toEqual(1); - expect(result[0]).toEqual( - jasmine.objectContaining({ - height: sendUnsuccessful.height, - hash: sendUnsuccessful.hash, - code: 5, - tx: sendUnsuccessful.tx, - }), - ); - }); - }); - - describe("with SearchBySentFromOrToQuery", () => { - it("can search by sender", async () => { - pendingWithoutWasmd(); - assert(sendSuccessful, "value must be set in beforeAll()"); - const client = new CosmWasmClient(wasmd.endpoint); - 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 containsMsgWithSender = !!result.tx.value.msg.find( - (msg) => isMsgSend(msg) && msg.value.from_address == sendSuccessful!.sender, - ); - const containsMsgWithRecipient = !!result.tx.value.msg.find( - (msg) => isMsgSend(msg) && msg.value.to_address === sendSuccessful!.sender, - ); - expect(containsMsgWithSender || containsMsgWithRecipient).toEqual(true); - } - - // 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 () => { - pendingWithoutWasmd(); - assert(sendSuccessful, "value must be set in beforeAll()"); - const client = new CosmWasmClient(wasmd.endpoint); - 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 msg = fromOneElementArray(result.tx.value.msg); - assert(isMsgSend(msg), `${result.hash} (height ${result.height}) is not a bank send transaction`); - expect( - msg.value.to_address === sendSuccessful.recipient || - msg.value.from_address == sendSuccessful.recipient, - ).toEqual(true); - } - - // 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 () => { - pendingWithoutWasmd(); - assert(sendSuccessful); - const client = new CosmWasmClient(wasmd.endpoint); - 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 () => { - pendingWithoutWasmd(); - assert(sendSuccessful); - const client = new CosmWasmClient(wasmd.endpoint); - 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 () => { - pendingWithoutWasmd(); - assert(sendSuccessful, "value must be set in beforeAll()"); - const client = new CosmWasmClient(wasmd.endpoint); - 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 msg = fromOneElementArray(result.tx.value.msg); - assert(isMsgSend(msg), `${result.hash} (height ${result.height}) is not a bank send transaction`); - expect(msg.value.to_address).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, - }), - ); - }); - - it("can search by message.contract_address", async () => { - pendingWithoutWasmd(); - assert(postedExecute, "value must be set in beforeAll()"); - const client = new CosmWasmClient(wasmd.endpoint); - const results = await client.searchTx({ - tags: [{ key: "message.contract_address", value: postedExecute.contract }], - }); - expect(results.length).toBeGreaterThanOrEqual(1); - - // Check basic structure of all results - for (const result of results) { - const msg = fromOneElementArray(result.tx.value.msg); - assert( - isMsgExecuteContract(msg) || isMsgInstantiateContract(msg), - `${result.hash} (at ${result.height}) not an execute or instantiate msg`, - ); - } - - // Check that the first result is the instantiation - const first = fromOneElementArray(results[0].tx.value.msg); - assert(isMsgInstantiateContract(first), "First contract search result must be an instantiation"); - expect(first).toEqual({ - type: "wasm/instantiate", - value: { - sender: faucet.address, - code_id: deployedErc20.codeId.toString(), - label: "HASH", - init_msg: jasmine.objectContaining({ symbol: "HASH" }), - init_funds: [], - }, - }); - - // Check details of most recent result - expect(results[results.length - 1]).toEqual( - jasmine.objectContaining({ - height: postedExecute.height, - hash: postedExecute.hash, - tx: postedExecute.tx, - }), - ); - }); - - it("can search by message.contract_address + message.action", async () => { - pendingWithoutWasmd(); - assert(postedExecute, "value must be set in beforeAll()"); - const client = new CosmWasmClient(wasmd.endpoint); - const results = await client.searchTx({ - tags: [ - { key: "message.contract_address", value: postedExecute.contract }, - { key: "message.action", value: "execute" }, - ], - }); - expect(results.length).toBeGreaterThanOrEqual(1); - - // Check basic structure of all results - for (const result of results) { - const msg = fromOneElementArray(result.tx.value.msg); - assert(isMsgExecuteContract(msg), `${result.hash} (at ${result.height}) not an execute msg`); - expect(msg.value.contract).toEqual(postedExecute.contract); - } - - // Check details of most recent result - expect(results[results.length - 1]).toEqual( - jasmine.objectContaining({ - height: postedExecute.height, - hash: postedExecute.hash, - tx: postedExecute.tx, - }), - ); - }); - }); -}); diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts deleted file mode 100644 index 2980df89..00000000 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ /dev/null @@ -1,467 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { Sha256 } from "@iov/crypto"; -import { Bech32, Encoding } from "@iov/encoding"; -import { assert, sleep } from "@iov/utils"; -import { ReadonlyDate } from "readonly-date"; - -import { Code, CosmWasmClient, PrivateCosmWasmClient } from "./cosmwasmclient"; -import { makeSignBytes } from "./encoding"; -import { findAttribute } from "./logs"; -import { Secp256k1Pen } from "./pen"; -import { SigningCosmWasmClient } from "./signingcosmwasmclient"; -import cosmoshub from "./testdata/cosmoshub.json"; -import { - deployedErc20, - faucet, - getHackatom, - makeRandomAddress, - pendingWithoutWasmd, - tendermintIdMatcher, - unused, - wasmd, - wasmdEnabled, -} from "./testutils.spec"; -import { MsgSend, StdFee } from "./types"; - -const { fromHex, fromUtf8, toAscii, toBase64 } = Encoding; - -const guest = { - address: "cosmos17d0jcz59jf68g52vq38tuuncmwwjk42u6mcxej", -}; - -interface HackatomInstance { - readonly initMsg: { - readonly verifier: string; - readonly beneficiary: string; - }; - readonly address: string; -} - -describe("CosmWasmClient", () => { - describe("makeReadOnly", () => { - it("can be constructed", () => { - const client = new CosmWasmClient(wasmd.endpoint); - expect(client).toBeTruthy(); - }); - }); - - describe("getChainId", () => { - it("works", async () => { - pendingWithoutWasmd(); - const client = new CosmWasmClient(wasmd.endpoint); - expect(await client.getChainId()).toEqual(wasmd.chainId); - }); - - it("caches chain ID", async () => { - pendingWithoutWasmd(); - const client = new CosmWasmClient(wasmd.endpoint); - const openedClient = (client as unknown) as PrivateCosmWasmClient; - const getCodeSpy = spyOn(openedClient.restClient, "nodeInfo").and.callThrough(); - - expect(await client.getChainId()).toEqual(wasmd.chainId); // from network - expect(await client.getChainId()).toEqual(wasmd.chainId); // from cache - - expect(getCodeSpy).toHaveBeenCalledTimes(1); - }); - }); - - describe("getHeight", () => { - it("gets height via last block", async () => { - pendingWithoutWasmd(); - const client = new CosmWasmClient(wasmd.endpoint); - const openedClient = (client as unknown) as PrivateCosmWasmClient; - const blockLatestSpy = spyOn(openedClient.restClient, "blocksLatest").and.callThrough(); - - const height1 = await client.getHeight(); - expect(height1).toBeGreaterThan(0); - await sleep(1_000); - const height2 = await client.getHeight(); - expect(height2).toEqual(height1 + 1); - - expect(blockLatestSpy).toHaveBeenCalledTimes(2); - }); - - it("gets height via authAccount once an address is known", async () => { - pendingWithoutWasmd(); - const client = new CosmWasmClient(wasmd.endpoint); - - const openedClient = (client as unknown) as PrivateCosmWasmClient; - const blockLatestSpy = spyOn(openedClient.restClient, "blocksLatest").and.callThrough(); - const authAccountsSpy = spyOn(openedClient.restClient, "authAccounts").and.callThrough(); - - const height1 = await client.getHeight(); - expect(height1).toBeGreaterThan(0); - - await client.getCodes(); // warm up the client - - const height2 = await client.getHeight(); - expect(height2).toBeGreaterThan(0); - await sleep(1_000); - const height3 = await client.getHeight(); - expect(height3).toEqual(height2 + 1); - - expect(blockLatestSpy).toHaveBeenCalledTimes(1); - expect(authAccountsSpy).toHaveBeenCalledTimes(2); - }); - }); - - describe("getNonce", () => { - it("works", async () => { - pendingWithoutWasmd(); - const client = new CosmWasmClient(wasmd.endpoint); - expect(await client.getNonce(unused.address)).toEqual({ - accountNumber: unused.accountNumber, - sequence: unused.sequence, - }); - }); - - it("throws for missing accounts", async () => { - pendingWithoutWasmd(); - const client = new CosmWasmClient(wasmd.endpoint); - const missing = makeRandomAddress(); - await client.getNonce(missing).then( - () => fail("this must not succeed"), - (error) => expect(error).toMatch(/account does not exist on chain/i), - ); - }); - }); - - describe("getAccount", () => { - it("works", async () => { - pendingWithoutWasmd(); - const client = new CosmWasmClient(wasmd.endpoint); - expect(await client.getAccount(unused.address)).toEqual({ - address: unused.address, - accountNumber: unused.accountNumber, - sequence: unused.sequence, - pubkey: undefined, - balance: [ - { denom: "ucosm", amount: "1000000000" }, - { denom: "ustake", amount: "1000000000" }, - ], - }); - }); - - it("returns undefined for missing accounts", async () => { - pendingWithoutWasmd(); - const client = new CosmWasmClient(wasmd.endpoint); - const missing = makeRandomAddress(); - expect(await client.getAccount(missing)).toBeUndefined(); - }); - }); - - describe("getBlock", () => { - it("works for latest block", async () => { - pendingWithoutWasmd(); - const client = new CosmWasmClient(wasmd.endpoint); - 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 () => { - pendingWithoutWasmd(); - const client = new CosmWasmClient(wasmd.endpoint); - 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("getIdentifier", () => { - it("works", async () => { - pendingWithoutWasmd(); - const client = new CosmWasmClient(wasmd.endpoint); - expect(await client.getIdentifier(cosmoshub.tx)).toEqual(cosmoshub.id); - }); - }); - - describe("postTx", () => { - it("works", async () => { - pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new CosmWasmClient(wasmd.endpoint); - - const memo = "My first contract on chain"; - const sendMsg: MsgSend = { - type: "cosmos-sdk/MsgSend", - value: { - from_address: faucet.address, - to_address: makeRandomAddress(), - amount: [ - { - denom: "ucosm", - amount: "1234567", - }, - ], - }, - }; - - const fee: StdFee = { - amount: [ - { - amount: "5000", - denom: "ucosm", - }, - ], - gas: "890000", - }; - - const chainId = await client.getChainId(); - const { accountNumber, sequence } = await client.getNonce(faucet.address); - const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); - const signature = await pen.sign(signBytes); - const signedTx = { - msg: [sendMsg], - fee: fee, - memo: memo, - signatures: [signature], - }; - const { logs, transactionHash } = await client.postTx(signedTx); - const amountAttr = findAttribute(logs, "transfer", "amount"); - expect(amountAttr.value).toEqual("1234567ucosm"); - expect(transactionHash).toMatch(/^[0-9A-F]{64}$/); - }); - }); - - describe("getCodes", () => { - it("works", async () => { - pendingWithoutWasmd(); - const client = new CosmWasmClient(wasmd.endpoint); - const result = await client.getCodes(); - expect(result.length).toBeGreaterThanOrEqual(1); - const [first] = result; - expect(first).toEqual({ - id: deployedErc20.codeId, - source: deployedErc20.source, - builder: deployedErc20.builder, - checksum: deployedErc20.checksum, - creator: faucet.address, - }); - }); - }); - - describe("getCodeDetails", () => { - it("works", async () => { - pendingWithoutWasmd(); - const client = new CosmWasmClient(wasmd.endpoint); - const result = await client.getCodeDetails(1); - - const expectedInfo: Code = { - id: deployedErc20.codeId, - source: deployedErc20.source, - builder: deployedErc20.builder, - checksum: deployedErc20.checksum, - creator: faucet.address, - }; - - // check info - expect(result).toEqual(jasmine.objectContaining(expectedInfo)); - // check data - expect(new Sha256(result.data).digest()).toEqual(fromHex(expectedInfo.checksum)); - }); - - it("caches downloads", async () => { - pendingWithoutWasmd(); - const client = new CosmWasmClient(wasmd.endpoint); - const openedClient = (client as unknown) as PrivateCosmWasmClient; - const getCodeSpy = spyOn(openedClient.restClient, "getCode").and.callThrough(); - - const result1 = await client.getCodeDetails(deployedErc20.codeId); // from network - const result2 = await client.getCodeDetails(deployedErc20.codeId); // from cache - expect(result2).toEqual(result1); - - expect(getCodeSpy).toHaveBeenCalledTimes(1); - }); - }); - - describe("getContracts", () => { - it("works", async () => { - pendingWithoutWasmd(); - const client = new CosmWasmClient(wasmd.endpoint); - const result = await client.getContracts(1); - expect(result.length).toBeGreaterThanOrEqual(3); - const [hash, isa, jade] = result; - expect(hash).toEqual({ - address: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", - codeId: 1, - creator: faucet.address, - label: "HASH", - }); - expect(isa).toEqual({ - address: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd", - codeId: 1, - creator: faucet.address, - label: "ISA", - }); - expect(jade).toEqual({ - address: "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c", - codeId: 1, - creator: faucet.address, - label: "JADE", - }); - }); - }); - - describe("getContract", () => { - it("works for HASH instance", async () => { - pendingWithoutWasmd(); - const client = new CosmWasmClient(wasmd.endpoint); - const hash = await client.getContract("cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5"); - expect(hash).toEqual({ - address: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", - codeId: 1, - creator: faucet.address, - label: "HASH", - initMsg: { - decimals: 5, - name: "Hash token", - symbol: "HASH", - initial_balances: jasmine.arrayContaining([ - { - address: faucet.address, - amount: "11", - }, - { - address: unused.address, - amount: "12812345", - }, - { - address: guest.address, - amount: "22004000000", - }, - ]), - }, - }); - }); - }); - - describe("queryContractRaw", () => { - const configKey = toAscii("config"); - const otherKey = toAscii("this_does_not_exist"); - let contract: HackatomInstance | undefined; - - beforeAll(async () => { - if (wasmdEnabled()) { - pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmWasmClient(wasmd.endpoint, faucet.address, (signBytes) => - pen.sign(signBytes), - ); - const { codeId } = await client.upload(getHackatom()); - const initMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() }; - const { contractAddress } = await client.instantiate(codeId, initMsg, "random hackatom"); - contract = { initMsg: initMsg, address: contractAddress }; - } - }); - - it("can query existing key", async () => { - pendingWithoutWasmd(); - assert(contract); - - const client = new CosmWasmClient(wasmd.endpoint); - const raw = await client.queryContractRaw(contract.address, configKey); - assert(raw, "must get result"); - expect(JSON.parse(fromUtf8(raw))).toEqual({ - verifier: toBase64(Bech32.decode(contract.initMsg.verifier).data), - beneficiary: toBase64(Bech32.decode(contract.initMsg.beneficiary).data), - funder: toBase64(Bech32.decode(faucet.address).data), - }); - }); - - it("can query non-existent key", async () => { - pendingWithoutWasmd(); - assert(contract); - - const client = new CosmWasmClient(wasmd.endpoint); - const raw = await client.queryContractRaw(contract.address, otherKey); - expect(raw).toBeNull(); - }); - - it("errors for non-existent contract", async () => { - pendingWithoutWasmd(); - assert(contract); - - const nonExistentAddress = makeRandomAddress(); - const client = new CosmWasmClient(wasmd.endpoint); - await client.queryContractRaw(nonExistentAddress, configKey).then( - () => fail("must not succeed"), - (error) => expect(error).toMatch(`No contract found at address "${nonExistentAddress}"`), - ); - }); - }); - - describe("queryContractSmart", () => { - let contract: HackatomInstance | undefined; - - beforeAll(async () => { - if (wasmdEnabled()) { - pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmWasmClient(wasmd.endpoint, faucet.address, (signBytes) => - pen.sign(signBytes), - ); - const { codeId } = await client.upload(getHackatom()); - const initMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() }; - const { contractAddress } = await client.instantiate(codeId, initMsg, "a different hackatom"); - contract = { initMsg: initMsg, address: contractAddress }; - } - }); - - it("works", async () => { - pendingWithoutWasmd(); - assert(contract); - - const client = new CosmWasmClient(wasmd.endpoint); - const resultDocument = await client.queryContractSmart(contract.address, { verifier: {} }); - expect(resultDocument).toEqual({ verifier: contract.initMsg.verifier }); - }); - - it("errors for malformed query message", async () => { - pendingWithoutWasmd(); - assert(contract); - - const client = new CosmWasmClient(wasmd.endpoint); - await client.queryContractSmart(contract.address, { broken: {} }).then( - () => fail("must not succeed"), - (error) => expect(error).toMatch(/query wasm contract failed: parsing hackatom::contract::QueryMsg/i), - ); - }); - - it("errors for non-existent contract", async () => { - pendingWithoutWasmd(); - - const nonExistentAddress = makeRandomAddress(); - const client = new CosmWasmClient(wasmd.endpoint); - await client.queryContractSmart(nonExistentAddress, { verifier: {} }).then( - () => fail("must not succeed"), - (error) => expect(error).toMatch(`No contract found at address "${nonExistentAddress}"`), - ); - }); - }); -}); diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts deleted file mode 100644 index 73d81a45..00000000 --- a/packages/sdk/src/cosmwasmclient.ts +++ /dev/null @@ -1,439 +0,0 @@ -import { Sha256 } from "@iov/crypto"; -import { Encoding } from "@iov/encoding"; - -import { Coin } from "./coins"; -import { Log, parseLogs } from "./logs"; -import { decodeBech32Pubkey } from "./pubkey"; -import { BroadcastMode, RestClient } from "./restclient"; -import { CosmosSdkTx, JsonObject, PubKey, StdTx } from "./types"; - -export interface GetNonceResult { - readonly accountNumber: number; - readonly sequence: number; -} - -export interface Account { - /** Bech32 account address */ - readonly address: string; - readonly balance: ReadonlyArray; - readonly pubkey: PubKey | undefined; - readonly accountNumber: number; - readonly sequence: number; -} - -export interface PostTxResult { - readonly logs: readonly Log[]; - readonly rawLog: string; - /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ - readonly transactionHash: string; -} - -export interface SearchByIdQuery { - readonly id: string; -} - -export interface SearchByHeightQuery { - readonly height: number; -} - -export interface SearchBySentFromOrToQuery { - readonly sentFromOrTo: string; -} - -/** - * This query type allows you to pass arbitrary key/value pairs to the backend. It is - * more powerful and slightly lower level than the other search options. - */ -export interface SearchByTagsQuery { - readonly tags: readonly { readonly key: string; readonly value: string }[]; -} - -export type SearchTxQuery = - | SearchByIdQuery - | SearchByHeightQuery - | SearchBySentFromOrToQuery - | SearchByTagsQuery; - -function isSearchByIdQuery(query: SearchTxQuery): query is SearchByIdQuery { - return (query as SearchByIdQuery).id !== undefined; -} - -function isSearchByHeightQuery(query: SearchTxQuery): query is SearchByHeightQuery { - return (query as SearchByHeightQuery).height !== undefined; -} - -function isSearchBySentFromOrToQuery(query: SearchTxQuery): query is SearchBySentFromOrToQuery { - return (query as SearchBySentFromOrToQuery).sentFromOrTo !== undefined; -} - -function isSearchByTagsQuery(query: SearchTxQuery): query is SearchByTagsQuery { - return (query as SearchByTagsQuery).tags !== undefined; -} - -export interface SearchTxFilter { - readonly minHeight?: number; - readonly maxHeight?: number; -} - -export interface Code { - readonly id: number; - /** Bech32 account address */ - readonly creator: string; - /** Hex-encoded sha256 hash of the code stored here */ - readonly checksum: string; - readonly source?: string; - readonly builder?: string; -} - -export interface CodeDetails extends Code { - /** The original wasm bytes */ - readonly data: Uint8Array; -} - -export interface Contract { - readonly address: string; - readonly codeId: number; - /** Bech32 account address */ - readonly creator: string; - readonly label: string; -} - -export interface ContractDetails extends Contract { - /** Argument passed on initialization of the contract */ - readonly initMsg: object; -} - -/** A transaction that is indexed as part of the transaction history */ -export interface IndexedTx { - readonly height: number; - /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ - readonly hash: string; - /** Transaction execution error code. 0 on success. */ - readonly code: number; - readonly rawLog: string; - readonly logs: readonly Log[]; - readonly tx: CosmosSdkTx; - /** The gas limit as set by the user */ - readonly gasWanted?: number; - /** The gas used by the execution */ - readonly gasUsed?: number; - /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ - readonly timestamp: string; -} - -export interface BlockHeader { - readonly version: { - readonly block: string; - readonly app: string; - }; - readonly height: number; - readonly chainId: string; - /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ - readonly time: string; -} - -export interface Block { - /** The ID is a hash of the block header (uppercase hex) */ - readonly id: string; - readonly header: BlockHeader; - /** Array of raw transactions */ - readonly txs: ReadonlyArray; -} - -/** Use for testing only */ -export interface PrivateCosmWasmClient { - readonly restClient: RestClient; -} - -export class CosmWasmClient { - protected readonly restClient: RestClient; - /** Any address the chain considers valid (valid bech32 with proper prefix) */ - protected anyValidAddress: string | undefined; - - private readonly codesCache = new Map(); - private chainId: string | undefined; - - /** - * Creates a new client to interact with a CosmWasm blockchain. - * - * This instance does a lot of caching. In order to benefit from that you should try to use one instance - * for the lifetime of your application. When switching backends, a new instance must be created. - * - * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) - * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns - */ - public constructor(apiUrl: string, broadcastMode = BroadcastMode.Block) { - this.restClient = new RestClient(apiUrl, broadcastMode); - } - - public async getChainId(): Promise { - if (!this.chainId) { - const response = await this.restClient.nodeInfo(); - const chainId = response.node_info.network; - if (!chainId) throw new Error("Chain ID must not be empty"); - this.chainId = chainId; - } - - return this.chainId; - } - - public async getHeight(): Promise { - if (this.anyValidAddress) { - const { height } = await this.restClient.authAccounts(this.anyValidAddress); - return parseInt(height, 10); - } else { - // Note: this gets inefficient when blocks contain a lot of transactions since it - // requires downloading and deserializing all transactions in the block. - const latest = await this.restClient.blocksLatest(); - return parseInt(latest.block.header.height, 10); - } - } - - /** - * Returns a 32 byte upper-case hex transaction hash (typically used as the transaction ID) - */ - public async getIdentifier(tx: CosmosSdkTx): Promise { - // We consult the REST API because we don't have a local amino encoder - const bytes = await this.restClient.encodeTx(tx); - const hash = new Sha256(bytes).digest(); - return Encoding.toHex(hash).toUpperCase(); - } - - /** - * Returns account number and sequence. - * - * Throws if the account does not exist on chain. - * - * @param address returns data for this address. When unset, the client's sender adddress is used. - */ - public async getNonce(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 nonces.", - ); - } - return { - accountNumber: account.accountNumber, - sequence: account.sequence, - }; - } - - public async getAccount(address: string): Promise { - const account = await this.restClient.authAccounts(address); - const value = account.result.value; - if (value.address === "") { - return undefined; - } else { - this.anyValidAddress = value.address; - return { - address: value.address, - balance: value.coins, - pubkey: value.public_key ? decodeBech32Pubkey(value.public_key) : undefined, - accountNumber: value.account_number, - sequence: value.sequence, - }; - } - } - - /** - * Gets block header and meta - * - * @param height The height of the block. If undefined, the latest height is used. - */ - public async getBlock(height?: number): Promise { - const response = - height !== undefined ? await this.restClient.blocks(height) : await this.restClient.blocksLatest(); - - return { - id: response.block_id.hash, - header: { - version: response.block.header.version, - time: response.block.header.time, - height: parseInt(response.block.header.height, 10), - chainId: response.block.header.chain_id, - }, - txs: (response.block.data.txs || []).map((encoded) => Encoding.fromBase64(encoded)), - }; - } - - 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}&tx.minheight=${minHeight}&tx.maxheight=${maxHeight}`; - } - - let txs: readonly IndexedTx[]; - if (isSearchByIdQuery(query)) { - txs = await this.txsQuery(`tx.hash=${query.id}`); - } else if (isSearchByHeightQuery(query)) { - // optional optimization to avoid network request - if (query.height < minHeight || query.height > maxHeight) { - txs = []; - } else { - txs = await this.txsQuery(`tx.height=${query.height}`); - } - } else if (isSearchBySentFromOrToQuery(query)) { - // We cannot get both in one request (see https://github.com/cosmos/gaia/issues/75) - const sentQuery = withFilters(`message.module=bank&message.sender=${query.sentFromOrTo}`); - const receivedQuery = withFilters(`message.module=bank&transfer.recipient=${query.sentFromOrTo}`); - const sent = await this.txsQuery(sentQuery); - const received = await this.txsQuery(receivedQuery); - - 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("&")); - txs = await this.txsQuery(rawQuery); - } else { - throw new Error("Unknown query type"); - } - - // backend sometimes messes up with min/max height filtering - const filtered = txs.filter((tx) => tx.height >= minHeight && tx.height <= maxHeight); - - return filtered; - } - - public async postTx(tx: StdTx): Promise { - const result = await this.restClient.postTx(tx); - if (!result.txhash.match(/^([0-9A-F][0-9A-F])+$/)) { - throw new Error("Received ill-formatted txhash. Must be non-empty upper-case hex"); - } - - if (result.code) { - throw new Error( - `Error when posting tx ${result.txhash}. Code: ${result.code}; Raw log: ${result.raw_log}`, - ); - } - - return { - logs: result.logs ? parseLogs(result.logs) : [], - rawLog: result.raw_log || "", - transactionHash: result.txhash, - }; - } - - public async getCodes(): Promise { - const result = await this.restClient.listCodeInfo(); - return result.map( - (entry): Code => { - this.anyValidAddress = entry.creator; - return { - id: entry.id, - creator: entry.creator, - checksum: Encoding.toHex(Encoding.fromHex(entry.data_hash)), - source: entry.source || undefined, - builder: entry.builder || undefined, - }; - }, - ); - } - - public async getCodeDetails(codeId: number): Promise { - const cached = this.codesCache.get(codeId); - if (cached) return cached; - - const getCodeResult = await this.restClient.getCode(codeId); - const codeDetails: CodeDetails = { - id: getCodeResult.id, - creator: getCodeResult.creator, - checksum: Encoding.toHex(Encoding.fromHex(getCodeResult.data_hash)), - source: getCodeResult.source || undefined, - builder: getCodeResult.builder || undefined, - data: Encoding.fromBase64(getCodeResult.data), - }; - this.codesCache.set(codeId, codeDetails); - return codeDetails; - } - - public async getContracts(codeId: number): Promise { - const result = await this.restClient.listContractsByCodeId(codeId); - return result.map( - (entry): Contract => ({ - address: entry.address, - codeId: entry.code_id, - creator: entry.creator, - label: entry.label, - }), - ); - } - - /** - * Throws an error if no contract was found at the address - */ - public async getContract(address: string): Promise { - const result = await this.restClient.getContractInfo(address); - if (!result) throw new Error(`No contract found at address "${address}"`); - return { - address: result.address, - codeId: result.code_id, - creator: result.creator, - label: result.label, - initMsg: result.init_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 - const _info = await this.getContract(address); - - return this.restClient.queryContractRaw(address, key); - } - - /** - * 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: object): Promise { - try { - return await this.restClient.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 { - // TODO: we need proper pagination support - const limit = 100; - const result = await this.restClient.txsQuery(`${query}&limit=${limit}`); - const pages = parseInt(result.page_total, 10); - if (pages > 1) { - throw new Error( - `Found more results on the backend than we can process currently. Results: ${result.total_count}, supported: ${limit}`, - ); - } - return result.txs.map( - (restItem): IndexedTx => ({ - height: parseInt(restItem.height, 10), - hash: restItem.txhash, - code: restItem.code || 0, - rawLog: restItem.raw_log, - logs: parseLogs(restItem.logs || []), - tx: restItem.tx, - timestamp: restItem.timestamp, - }), - ); - } -} diff --git a/packages/sdk/src/decoding.ts b/packages/sdk/src/decoding.ts deleted file mode 100644 index 78286018..00000000 --- a/packages/sdk/src/decoding.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Encoding } from "@iov/encoding"; - -import { isStdTx, StdTx } from "./types"; - -export function unmarshalTx(data: Uint8Array): StdTx { - const decoded = JSON.parse(Encoding.fromUtf8(data)); - if (!isStdTx(decoded)) { - throw new Error("Must be json encoded StdTx"); - } - return decoded; -} diff --git a/packages/sdk/src/encoding.spec.ts b/packages/sdk/src/encoding.spec.ts deleted file mode 100644 index ccdb8a4a..00000000 --- a/packages/sdk/src/encoding.spec.ts +++ /dev/null @@ -1 +0,0 @@ -describe("encoding", () => {}); diff --git a/packages/sdk/src/encoding.ts b/packages/sdk/src/encoding.ts deleted file mode 100644 index 8a782944..00000000 --- a/packages/sdk/src/encoding.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Encoding } from "@iov/encoding"; - -import { Msg, StdFee, StdTx } from "./types"; - -const { toUtf8 } = Encoding; - -function sortJson(json: any): any { - if (typeof json !== "object" || json === null) { - return json; - } - if (Array.isArray(json)) { - return json.map(sortJson); - } - const sortedKeys = Object.keys(json).sort(); - const result = sortedKeys.reduce( - (accumulator, key) => ({ - ...accumulator, - [key]: sortJson(json[key]), - }), - {}, - ); - return result; -} - -export function marshalTx(tx: StdTx): Uint8Array { - const json = JSON.stringify(tx); - return Encoding.toUtf8(json); -} - -interface SignJson { - readonly account_number: string; - readonly chain_id: string; - readonly fee: StdFee; - readonly memo: string; - readonly msgs: readonly Msg[]; - readonly sequence: string; -} - -export function makeSignBytes( - msgs: readonly Msg[], - fee: StdFee, - chainId: string, - memo: string, - accountNumber: number, - sequence: number, -): Uint8Array { - const signJson: SignJson = { - // eslint-disable-next-line @typescript-eslint/camelcase - account_number: accountNumber.toString(), - // eslint-disable-next-line @typescript-eslint/camelcase - chain_id: chainId, - fee: fee, - memo: memo, - msgs: msgs, - sequence: sequence.toString(), - }; - const signMsg = sortJson(signJson); - return toUtf8(JSON.stringify(signMsg)); -} diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts deleted file mode 100644 index 5162bdab..00000000 --- a/packages/sdk/src/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -import * as logs from "./logs"; -import * as types from "./types"; -export { logs, types }; - -export { pubkeyToAddress } from "./address"; -export { Coin, coin, coins } from "./coins"; -export { unmarshalTx } from "./decoding"; -export { makeSignBytes, marshalTx } from "./encoding"; -export { BroadcastMode, RestClient, TxsResponse } from "./restclient"; -export { - Account, - Block, - BlockHeader, - Code, - CodeDetails, - Contract, - ContractDetails, - CosmWasmClient, - GetNonceResult, - IndexedTx, - PostTxResult, - SearchByHeightQuery, - SearchByIdQuery, - SearchBySentFromOrToQuery, - SearchByTagsQuery, - SearchTxQuery, - SearchTxFilter, -} from "./cosmwasmclient"; -export { makeCosmoshubPath, Pen, PrehashType, Secp256k1Pen } from "./pen"; -export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; -export { findSequenceForSignedTx } from "./sequence"; -export { encodeSecp256k1Signature, decodeSignature } from "./signature"; -export { - ExecuteResult, - FeeTable, - InstantiateResult, - SigningCallback, - SigningCosmWasmClient, - UploadMeta, - UploadResult, -} from "./signingcosmwasmclient"; diff --git a/packages/sdk/src/logs.spec.ts b/packages/sdk/src/logs.spec.ts deleted file mode 100644 index 584d9ef2..00000000 --- a/packages/sdk/src/logs.spec.ts +++ /dev/null @@ -1,165 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { parseAttribute, parseEvent, parseLog, parseLogs } from "./logs"; - -describe("logs", () => { - describe("parseAttribute", () => { - it("works", () => { - const attr = parseAttribute({ key: "a", value: "b" }); - expect(attr).toEqual({ key: "a", value: "b" }); - }); - - it("works for empty value", () => { - const attr = parseAttribute({ key: "foobar", value: "" }); - expect(attr).toEqual({ key: "foobar", value: "" }); - }); - - it("normalized unset value to empty string", () => { - const attr = parseAttribute({ key: "amount" }); - expect(attr).toEqual({ key: "amount", value: "" }); - }); - }); - - describe("parseEvent", () => { - it("works", () => { - const original = { - type: "message", - attributes: [ - { - key: "action", - value: "store-code", - }, - { - key: "module", - value: "wasm", - }, - { - key: "action", - value: "store-code", - }, - { - key: "sender", - value: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - }, - { - key: "code_id", - value: "1", - }, - ], - } as const; - - const event = parseEvent(original); - expect(event).toEqual(original); - }); - - it("works for transfer event", () => { - const original = { - type: "transfer", - attributes: [ - { - key: "recipient", - value: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", - }, - { - key: "amount", - }, - ], - } as const; - const expected = { - type: "transfer", - attributes: [ - { - key: "recipient", - value: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", - }, - { - key: "amount", - value: "", - }, - ], - } as const; - - const event = parseEvent(original); - expect(event).toEqual(expected); - }); - }); - - describe("parseLog", () => { - it("works", () => { - const original = { - msg_index: 0, - log: "", - events: [ - { - type: "message", - attributes: [ - { - key: "action", - value: "store-code", - }, - { - key: "module", - value: "wasm", - }, - { - key: "action", - value: "store-code", - }, - { - key: "sender", - value: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - }, - { - key: "code_id", - value: "1", - }, - ], - }, - ], - } as const; - - const log = parseLog(original); - expect(log).toEqual(original); - }); - }); - - describe("parseLogs", () => { - it("works", () => { - const original = [ - { - msg_index: 0, - log: "", - events: [ - { - type: "message", - attributes: [ - { - key: "action", - value: "store-code", - }, - { - key: "module", - value: "wasm", - }, - { - key: "action", - value: "store-code", - }, - { - key: "sender", - value: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - }, - { - key: "code_id", - value: "1", - }, - ], - }, - ], - }, - ] as const; - - const logs = parseLogs(original); - expect(logs).toEqual(original); - }); - }); -}); diff --git a/packages/sdk/src/logs.ts b/packages/sdk/src/logs.ts deleted file mode 100644 index e1eaa1cb..00000000 --- a/packages/sdk/src/logs.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { isNonNullObject } from "@iov/encoding"; - -export interface Attribute { - readonly key: string; - readonly value: string; -} - -export interface Event { - readonly type: string; - readonly attributes: readonly Attribute[]; -} - -export interface Log { - readonly msg_index: number; - readonly log: string; - readonly events: readonly Event[]; -} - -export function parseAttribute(input: unknown): Attribute { - if (!isNonNullObject(input)) throw new Error("Attribute must be a non-null object"); - const { key, value } = input as any; - if (typeof key !== "string" || !key) throw new Error("Attribute's key must be a non-empty string"); - if (typeof value !== "string" && typeof value !== "undefined") { - throw new Error("Attribute's value must be a string or unset"); - } - - return { - key: key, - value: value || "", - }; -} - -export function parseEvent(input: unknown): Event { - if (!isNonNullObject(input)) throw new Error("Event must be a non-null object"); - const { type, attributes } = input as any; - if (typeof type !== "string" || type === "") { - throw new Error(`Event type must be a non-empty string`); - } - if (!Array.isArray(attributes)) throw new Error("Event's attributes must be an array"); - return { - type: type, - attributes: attributes.map(parseAttribute), - }; -} - -export function parseLog(input: unknown): Log { - if (!isNonNullObject(input)) throw new Error("Log must be a non-null object"); - const { msg_index, log, events } = input as any; - if (typeof msg_index !== "number") throw new Error("Log's msg_index must be a number"); - if (typeof log !== "string") throw new Error("Log's log must be a string"); - if (!Array.isArray(events)) throw new Error("Log's events must be an array"); - return { - msg_index: msg_index, - log: log, - events: events.map(parseEvent), - }; -} - -export function parseLogs(input: unknown): readonly Log[] { - if (!Array.isArray(input)) throw new Error("Logs must be an array"); - return input.map(parseLog); -} - -/** - * Searches in logs for the first event of the given event type and in that event - * for the first first attribute with the given attribute key. - * - * Throws if the attribute was not found. - */ -export function findAttribute( - logs: readonly Log[], - eventType: "message" | "transfer", - attrKey: string, -): Attribute { - const firstLogs = logs.find(() => true); - const out = firstLogs?.events - .find((event) => event.type === eventType) - ?.attributes.find((attr) => attr.key === attrKey); - if (!out) { - throw new Error( - `Could not find attribute '${attrKey}' in first event of type '${eventType}' in first log.`, - ); - } - return out; -} diff --git a/packages/sdk/src/pen.spec.ts b/packages/sdk/src/pen.spec.ts deleted file mode 100644 index 549177a8..00000000 --- a/packages/sdk/src/pen.spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Secp256k1, Secp256k1Signature, Sha256 } from "@iov/crypto"; -import { Encoding } from "@iov/encoding"; - -import { Secp256k1Pen } from "./pen"; -import { decodeSignature } from "./signature"; - -const { fromHex } = Encoding; - -describe("Sec256k1Pen", () => { - it("can be constructed", async () => { - const pen = await Secp256k1Pen.fromMnemonic( - "zebra slush diet army arrest purpose hawk source west glimpse custom record", - ); - expect(pen).toBeTruthy(); - }); - - describe("pubkey", () => { - it("returns compressed pubkey", async () => { - // special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling - // m/44'/118'/0'/0/0 - // pubkey: 02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6 - const pen = await Secp256k1Pen.fromMnemonic( - "special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling", - ); - expect(pen.pubkey).toEqual( - fromHex("02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6"), - ); - }); - }); - - describe("sign", () => { - it("creates correct signatures", async () => { - const pen = await Secp256k1Pen.fromMnemonic( - "special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling", - ); - const data = Encoding.toAscii("foo bar"); - const { pubkey, signature } = decodeSignature(await pen.sign(data)); - - const valid = await Secp256k1.verifySignature( - Secp256k1Signature.fromFixedLength(signature), - new Sha256(data).digest(), - pubkey, - ); - expect(valid).toEqual(true); - }); - }); - - describe("address", () => { - it("creates same address as Go imlementation", async () => { - const pen = await Secp256k1Pen.fromMnemonic( - "oyster design unusual machine spread century engine gravity focus cave carry slot", - ); - expect(pen.address("cosmos")).toEqual("cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u"); - }); - }); -}); diff --git a/packages/sdk/src/pen.ts b/packages/sdk/src/pen.ts deleted file mode 100644 index 936abc0e..00000000 --- a/packages/sdk/src/pen.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { - Bip39, - EnglishMnemonic, - Secp256k1, - Sha256, - Sha512, - Slip10, - Slip10Curve, - Slip10RawIndex, -} from "@iov/crypto"; - -import { rawSecp256k1PubkeyToAddress } from "./address"; -import { encodeSecp256k1Signature } from "./signature"; -import { StdSignature } from "./types"; - -export type PrehashType = "sha256" | "sha512" | null; - -/** - * A pen is the most basic tool you can think of for signing. It works - * everywhere and can be used intuitively by everyone. However, it does not - * come with a great amount of features. End of semi suitable metaphor. - * - * This wraps a single keypair and allows for signing. - * - * Non-goals of this types are: multi account support, persistency, data migrations, - * obfuscation of sensitive data. - */ -export interface Pen { - readonly pubkey: Uint8Array; - readonly sign: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise; -} - -function prehash(bytes: Uint8Array, type: PrehashType): Uint8Array { - switch (type) { - case null: - return new Uint8Array([...bytes]); - case "sha256": - return new Sha256(bytes).digest(); - case "sha512": - return new Sha512(bytes).digest(); - default: - throw new Error("Unknown prehash type"); - } -} - -/** - * The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a` - * with 0-based account index `a`. - */ -export function makeCosmoshubPath(a: number): readonly Slip10RawIndex[] { - return [ - Slip10RawIndex.hardened(44), - Slip10RawIndex.hardened(118), - Slip10RawIndex.hardened(0), - Slip10RawIndex.normal(0), - Slip10RawIndex.normal(a), - ]; -} - -export class Secp256k1Pen implements Pen { - public static async fromMnemonic( - mnemonic: string, - hdPath: readonly Slip10RawIndex[] = makeCosmoshubPath(0), - ): Promise { - const seed = await Bip39.mnemonicToSeed(new EnglishMnemonic(mnemonic)); - const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, hdPath); - const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey; - return new Secp256k1Pen(privkey, Secp256k1.compressPubkey(uncompressed)); - } - - public readonly pubkey: Uint8Array; - private readonly privkey: Uint8Array; - - private constructor(privkey: Uint8Array, pubkey: Uint8Array) { - this.privkey = privkey; - this.pubkey = pubkey; - } - - /** - * Creates and returns a signature - */ - public async sign(signBytes: Uint8Array, prehashType: PrehashType = "sha256"): Promise { - const message = prehash(signBytes, prehashType); - const signature = await Secp256k1.createSignature(message, this.privkey); - const fixedLengthSignature = new Uint8Array([...signature.r(32), ...signature.s(32)]); - return encodeSecp256k1Signature(this.pubkey, fixedLengthSignature); - } - - public address(prefix: string): string { - return rawSecp256k1PubkeyToAddress(this.pubkey, prefix); - } -} diff --git a/packages/sdk/src/pubkey.spec.ts b/packages/sdk/src/pubkey.spec.ts deleted file mode 100644 index cb344f7b..00000000 --- a/packages/sdk/src/pubkey.spec.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Encoding } from "@iov/encoding"; - -import { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; -import { PubKey } from "./types"; - -const { fromBase64 } = Encoding; - -describe("pubkey", () => { - describe("encodeSecp256k1Pubkey", () => { - it("encodes a compresed pubkey", () => { - const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP"); - expect(encodeSecp256k1Pubkey(pubkey)).toEqual({ - type: "tendermint/PubKeySecp256k1", - value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP", - }); - }); - - it("throws for uncompressed public keys", () => { - const pubkey = fromBase64( - "BE8EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQE7WHpoHoNswYeoFkuYpYSKK4mzFzMV/dB0DVAy4lnNU=", - ); - expect(() => encodeSecp256k1Pubkey(pubkey)).toThrowError(/public key must be compressed secp256k1/i); - }); - }); - - describe("decodeBech32Pubkey", () => { - it("works", () => { - expect( - decodeBech32Pubkey("cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5"), - ).toEqual({ - type: "tendermint/PubKeySecp256k1", - value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", - }); - }); - - it("works for enigma pubkey", () => { - expect( - decodeBech32Pubkey("enigmapub1addwnpepqw5k9p439nw0zpg2aundx4umwx4nw233z5prpjqjv5anl5grmnchzp2xwvv"), - ).toEqual({ - type: "tendermint/PubKeySecp256k1", - value: "A6lihrEs3PEFCu8m01ebcas3KjEVAjDIEmU7P9ED3PFx", - }); - }); - }); - - describe("encodeBech32Pubkey", () => { - it("works for secp256k1", () => { - const pubkey: PubKey = { - type: "tendermint/PubKeySecp256k1", - value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", - }; - expect(encodeBech32Pubkey(pubkey, "cosmospub")).toEqual( - "cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5", - ); - }); - }); -}); diff --git a/packages/sdk/src/pubkey.ts b/packages/sdk/src/pubkey.ts deleted file mode 100644 index 8835b97a..00000000 --- a/packages/sdk/src/pubkey.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Bech32, Encoding } from "@iov/encoding"; -import equal from "fast-deep-equal"; - -import { PubKey, pubkeyType } from "./types"; - -export function encodeSecp256k1Pubkey(pubkey: Uint8Array): PubKey { - if (pubkey.length !== 33 || (pubkey[0] !== 0x02 && pubkey[0] !== 0x03)) { - throw new Error("Public key must be compressed secp256k1, i.e. 33 bytes starting with 0x02 or 0x03"); - } - return { - type: pubkeyType.secp256k1, - value: Encoding.toBase64(pubkey), - }; -} - -// As discussed in https://github.com/binance-chain/javascript-sdk/issues/163 -// Prefixes listed here: https://github.com/tendermint/tendermint/blob/d419fffe18531317c28c29a292ad7d253f6cafdf/docs/spec/blockchain/encoding.md#public-key-cryptography -// Last bytes is varint-encoded length prefix -const pubkeyAminoPrefixSecp256k1 = Encoding.fromHex("eb5ae98721"); -const pubkeyAminoPrefixEd25519 = Encoding.fromHex("1624de6420"); -const pubkeyAminoPrefixSr25519 = Encoding.fromHex("0dfb1005"); -const pubkeyAminoPrefixLength = pubkeyAminoPrefixSecp256k1.length; - -export function decodeBech32Pubkey(bechEncoded: string): PubKey { - const { data } = Bech32.decode(bechEncoded); - - const aminoPrefix = data.slice(0, pubkeyAminoPrefixLength); - const rest = data.slice(pubkeyAminoPrefixLength); - if (equal(aminoPrefix, pubkeyAminoPrefixSecp256k1)) { - if (rest.length !== 33) { - throw new Error("Invalid rest data length. Expected 33 bytes (compressed secp256k1 pubkey)."); - } - return { - type: pubkeyType.secp256k1, - value: Encoding.toBase64(rest), - }; - } else if (equal(aminoPrefix, pubkeyAminoPrefixEd25519)) { - if (rest.length !== 32) { - throw new Error("Invalid rest data length. Expected 32 bytes (Ed25519 pubkey)."); - } - return { - type: pubkeyType.ed25519, - value: Encoding.toBase64(rest), - }; - } else if (equal(aminoPrefix, pubkeyAminoPrefixSr25519)) { - if (rest.length !== 32) { - throw new Error("Invalid rest data length. Expected 32 bytes (Sr25519 pubkey)."); - } - return { - type: pubkeyType.sr25519, - value: Encoding.toBase64(rest), - }; - } else { - throw new Error("Unsupported Pubkey type. Amino prefix: " + Encoding.toHex(aminoPrefix)); - } -} - -export function encodeBech32Pubkey(pubkey: PubKey, prefix: string): string { - let aminoPrefix: Uint8Array; - switch (pubkey.type) { - // Note: please don't add cases here without writing additional unit tests - case pubkeyType.secp256k1: - aminoPrefix = pubkeyAminoPrefixSecp256k1; - break; - default: - throw new Error("Unsupported pubkey type"); - } - - const data = new Uint8Array([...aminoPrefix, ...Encoding.fromBase64(pubkey.value)]); - return Bech32.encode(prefix, data); -} diff --git a/packages/sdk/src/restclient.spec.ts b/packages/sdk/src/restclient.spec.ts deleted file mode 100644 index b65a9d90..00000000 --- a/packages/sdk/src/restclient.spec.ts +++ /dev/null @@ -1,1378 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { Sha256 } from "@iov/crypto"; -import { Encoding } from "@iov/encoding"; -import { assert, sleep } from "@iov/utils"; -import { ReadonlyDate } from "readonly-date"; - -import { rawSecp256k1PubkeyToAddress } from "./address"; -import { Coin } from "./coins"; -import { makeSignBytes } from "./encoding"; -import { findAttribute, parseLogs } from "./logs"; -import { makeCosmoshubPath, Pen, Secp256k1Pen } from "./pen"; -import { encodeBech32Pubkey } from "./pubkey"; -import { PostTxsResponse, RestClient, TxsResponse } from "./restclient"; -import { SigningCosmWasmClient } from "./signingcosmwasmclient"; -import cosmoshub from "./testdata/cosmoshub.json"; -import { - bech32AddressMatcher, - deployedErc20, - faucet, - fromOneElementArray, - getHackatom, - makeRandomAddress, - nonNegativeIntegerMatcher, - pendingWithoutWasmd, - semverMatcher, - tendermintAddressMatcher, - tendermintIdMatcher, - tendermintOptionalIdMatcher, - tendermintShortHashMatcher, - unused, - wasmd, - wasmdEnabled, -} from "./testutils.spec"; -import { - isMsgInstantiateContract, - isMsgStoreCode, - Msg, - MsgExecuteContract, - MsgInstantiateContract, - MsgSend, - MsgStoreCode, - StdFee, - StdSignature, - StdTx, -} from "./types"; - -const { fromAscii, fromBase64, fromHex, toAscii, toBase64, toHex } = Encoding; - -const emptyAddress = "cosmos1ltkhnmdcqemmd2tkhnx7qx66tq7e0wykw2j85k"; - -function makeSignedTx(firstMsg: Msg, fee: StdFee, memo: string, firstSignature: StdSignature): StdTx { - return { - msg: [firstMsg], - fee: fee, - memo: memo, - signatures: [firstSignature], - }; -} - -async function uploadCustomContract( - client: RestClient, - pen: Pen, - wasmCode: Uint8Array, -): Promise { - const memo = "My first contract on chain"; - const theMsg: MsgStoreCode = { - type: "wasm/store-code", - value: { - sender: faucet.address, - wasm_byte_code: toBase64(wasmCode), - source: "https://github.com/confio/cosmwasm/raw/0.7/lib/vm/testdata/contract_0.6.wasm", - builder: "confio/cosmwasm-opt:0.6.2", - }, - }; - const fee: StdFee = { - amount: [ - { - amount: "5000000", - denom: "ucosm", - }, - ], - gas: "89000000", - }; - - const { account_number, sequence } = (await client.authAccounts(faucet.address)).result.value; - const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); - const signature = await pen.sign(signBytes); - const signedTx = makeSignedTx(theMsg, fee, memo, signature); - return client.postTx(signedTx); -} - -async function uploadContract(client: RestClient, pen: Pen): Promise { - return uploadCustomContract(client, pen, getHackatom()); -} - -async function instantiateContract( - client: RestClient, - pen: Pen, - codeId: number, - beneficiaryAddress: string, - transferAmount?: readonly Coin[], -): Promise { - const memo = "Create an escrow instance"; - const theMsg: MsgInstantiateContract = { - type: "wasm/instantiate", - value: { - sender: faucet.address, - code_id: codeId.toString(), - label: "my escrow", - init_msg: { - verifier: faucet.address, - beneficiary: beneficiaryAddress, - }, - init_funds: transferAmount || [], - }, - }; - const fee: StdFee = { - amount: [ - { - amount: "5000000", - denom: "ucosm", - }, - ], - gas: "89000000", - }; - - const { account_number, sequence } = (await client.authAccounts(faucet.address)).result.value; - const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); - const signature = await pen.sign(signBytes); - const signedTx = makeSignedTx(theMsg, fee, memo, signature); - return client.postTx(signedTx); -} - -async function executeContract( - client: RestClient, - pen: Pen, - contractAddress: string, -): Promise { - const memo = "Time for action"; - const theMsg: MsgExecuteContract = { - type: "wasm/execute", - value: { - sender: faucet.address, - contract: contractAddress, - msg: { release: {} }, - sent_funds: [], - }, - }; - const fee: StdFee = { - amount: [ - { - amount: "5000000", - denom: "ucosm", - }, - ], - gas: "89000000", - }; - - const { account_number, sequence } = (await client.authAccounts(faucet.address)).result.value; - const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); - const signature = await pen.sign(signBytes); - const signedTx = makeSignedTx(theMsg, fee, memo, signature); - return client.postTx(signedTx); -} - -describe("RestClient", () => { - it("can be constructed", () => { - const client = new RestClient(wasmd.endpoint); - expect(client).toBeTruthy(); - }); - - // The /auth endpoints - - describe("authAccounts", () => { - it("works for unused account without pubkey", async () => { - pendingWithoutWasmd(); - const client = new RestClient(wasmd.endpoint); - const { height, result } = await client.authAccounts(unused.address); - expect(height).toMatch(nonNegativeIntegerMatcher); - expect(result).toEqual({ - type: "cosmos-sdk/Account", - value: { - address: unused.address, - public_key: "", // not known to the chain - coins: [ - { - amount: "1000000000", - denom: "ucosm", - }, - { - amount: "1000000000", - denom: "ustake", - }, - ], - account_number: unused.accountNumber, - sequence: 0, - }, - }); - }); - - // This fails in the first test run if you forget to run `./scripts/wasmd/init.sh` - it("has correct pubkey for faucet", async () => { - pendingWithoutWasmd(); - const client = new RestClient(wasmd.endpoint); - const { result } = await client.authAccounts(faucet.address); - expect(result.value).toEqual( - jasmine.objectContaining({ - public_key: encodeBech32Pubkey(faucet.pubkey, "cosmospub"), - }), - ); - }); - - // This property is used by CosmWasmClient.getAccount - it("returns empty address for non-existent account", async () => { - pendingWithoutWasmd(); - const client = new RestClient(wasmd.endpoint); - const nonExistentAccount = makeRandomAddress(); - const { result } = await client.authAccounts(nonExistentAccount); - expect(result).toEqual({ - type: "cosmos-sdk/Account", - value: jasmine.objectContaining({ address: "" }), - }); - }); - }); - - // The /blocks endpoints - - describe("blocksLatest", () => { - it("works", async () => { - pendingWithoutWasmd(); - const client = new RestClient(wasmd.endpoint); - const response = await client.blocksLatest(); - - // id - expect(response.block_id.hash).toMatch(tendermintIdMatcher); - - // header - expect(response.block.header.version).toEqual({ block: "10", app: "0" }); - expect(parseInt(response.block.header.height, 10)).toBeGreaterThanOrEqual(1); - expect(response.block.header.chain_id).toEqual(wasmd.chainId); - expect(new ReadonlyDate(response.block.header.time).getTime()).toBeLessThan(ReadonlyDate.now()); - expect(new ReadonlyDate(response.block.header.time).getTime()).toBeGreaterThanOrEqual( - ReadonlyDate.now() - 5_000, - ); - expect(response.block.header.last_commit_hash).toMatch(tendermintIdMatcher); - expect(response.block.header.last_block_id.hash).toMatch(tendermintIdMatcher); - expect(response.block.header.data_hash).toMatch(tendermintOptionalIdMatcher); - expect(response.block.header.validators_hash).toMatch(tendermintIdMatcher); - expect(response.block.header.next_validators_hash).toMatch(tendermintIdMatcher); - expect(response.block.header.consensus_hash).toMatch(tendermintIdMatcher); - expect(response.block.header.app_hash).toMatch(tendermintIdMatcher); - expect(response.block.header.last_results_hash).toMatch(tendermintOptionalIdMatcher); - expect(response.block.header.evidence_hash).toMatch(tendermintOptionalIdMatcher); - expect(response.block.header.proposer_address).toMatch(tendermintAddressMatcher); - - // data - expect(response.block.data.txs === null || Array.isArray(response.block.data.txs)).toEqual(true); - }); - }); - - describe("blocks", () => { - it("works for block by height", async () => { - pendingWithoutWasmd(); - const client = new RestClient(wasmd.endpoint); - const height = parseInt((await client.blocksLatest()).block.header.height, 10); - const response = await client.blocks(height - 1); - - // id - expect(response.block_id.hash).toMatch(tendermintIdMatcher); - - // header - expect(response.block.header.version).toEqual({ block: "10", app: "0" }); - expect(response.block.header.height).toEqual(`${height - 1}`); - expect(response.block.header.chain_id).toEqual(wasmd.chainId); - expect(new ReadonlyDate(response.block.header.time).getTime()).toBeLessThan(ReadonlyDate.now()); - expect(new ReadonlyDate(response.block.header.time).getTime()).toBeGreaterThanOrEqual( - ReadonlyDate.now() - 5_000, - ); - expect(response.block.header.last_commit_hash).toMatch(tendermintIdMatcher); - expect(response.block.header.last_block_id.hash).toMatch(tendermintIdMatcher); - expect(response.block.header.data_hash).toMatch(tendermintOptionalIdMatcher); - expect(response.block.header.validators_hash).toMatch(tendermintIdMatcher); - expect(response.block.header.next_validators_hash).toMatch(tendermintIdMatcher); - expect(response.block.header.consensus_hash).toMatch(tendermintIdMatcher); - expect(response.block.header.app_hash).toMatch(tendermintIdMatcher); - expect(response.block.header.last_results_hash).toMatch(tendermintOptionalIdMatcher); - expect(response.block.header.evidence_hash).toMatch(tendermintOptionalIdMatcher); - expect(response.block.header.proposer_address).toMatch(tendermintAddressMatcher); - - // data - expect(response.block.data.txs === null || Array.isArray(response.block.data.txs)).toEqual(true); - }); - }); - - // The /node_info endpoint - - describe("nodeInfo", () => { - it("works", async () => { - pendingWithoutWasmd(); - const client = new RestClient(wasmd.endpoint); - const { node_info, application_version } = await client.nodeInfo(); - - expect(node_info).toEqual({ - protocol_version: { p2p: "7", block: "10", app: "0" }, - id: jasmine.stringMatching(tendermintShortHashMatcher), - listen_addr: "tcp://0.0.0.0:26656", - network: wasmd.chainId, - version: jasmine.stringMatching(/^0\.33\.[0-9]+$/), - channels: "4020212223303800", - moniker: wasmd.chainId, - other: { tx_index: "on", rpc_address: "tcp://0.0.0.0:26657" }, - }); - expect(application_version).toEqual({ - name: "wasm", - server_name: "wasmd", - client_name: "wasmcli", - version: jasmine.stringMatching(semverMatcher), - commit: jasmine.stringMatching(tendermintShortHashMatcher), - build_tags: "netgo,ledger", - go: jasmine.stringMatching(/^go version go1\.[0-9]+\.[0-9]+ linux\/amd64$/), - }); - }); - }); - - // The /txs endpoints - - describe("txById", () => { - let successful: - | { - readonly sender: string; - readonly recipient: string; - readonly hash: string; - } - | undefined; - let unsuccessful: - | { - readonly sender: string; - readonly recipient: string; - readonly hash: string; - } - | undefined; - - beforeAll(async () => { - if (wasmdEnabled()) { - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmWasmClient(wasmd.endpoint, faucet.address, (signBytes) => - pen.sign(signBytes), - ); - - { - const recipient = makeRandomAddress(); - const transferAmount = { - denom: "ucosm", - amount: "1234567", - }; - const result = await client.sendTokens(recipient, [transferAmount]); - successful = { - sender: faucet.address, - recipient: recipient, - hash: result.transactionHash, - }; - } - - { - const memo = "Sending more than I can afford"; - const recipient = makeRandomAddress(); - const transferAmount = [ - { - denom: "ucosm", - amount: "123456700000000", - }, - ]; - const sendMsg: MsgSend = { - type: "cosmos-sdk/MsgSend", - value: { - // eslint-disable-next-line @typescript-eslint/camelcase - from_address: faucet.address, - // eslint-disable-next-line @typescript-eslint/camelcase - to_address: recipient, - amount: transferAmount, - }, - }; - const fee = { - amount: [ - { - denom: "ucosm", - amount: "2000", - }, - ], - gas: "80000", // 80k - }; - const { accountNumber, sequence } = await client.getNonce(); - const chainId = await client.getChainId(); - const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); - const signature = await pen.sign(signBytes); - const signedTx = { - msg: [sendMsg], - fee: fee, - memo: memo, - signatures: [signature], - }; - const transactionId = await client.getIdentifier({ type: "cosmos-sdk/StdTx", value: signedTx }); - try { - await client.postTx(signedTx); - } catch (error) { - // postTx() throws on execution failures, which is a questionable design. Ignore for now. - // console.log(error); - } - unsuccessful = { - sender: faucet.address, - recipient: recipient, - hash: transactionId, - }; - } - - await sleep(50); // wait until transactions are indexed - } - }); - - it("works for successful transaction", async () => { - pendingWithoutWasmd(); - assert(successful); - const client = new RestClient(wasmd.endpoint); - const result = await client.txById(successful.hash); - expect(result.height).toBeGreaterThanOrEqual(1); - expect(result.txhash).toEqual(successful.hash); - expect(result.codespace).toBeUndefined(); - expect(result.code).toBeUndefined(); - const logs = parseLogs(result.logs); - expect(logs).toEqual([ - { - msg_index: 0, - log: "", - events: [ - { - type: "message", - attributes: [ - { key: "action", value: "send" }, - { key: "sender", value: successful.sender }, - { key: "module", value: "bank" }, - ], - }, - { - type: "transfer", - attributes: [ - { key: "recipient", value: successful.recipient }, - { key: "sender", value: successful.sender }, - { key: "amount", value: "1234567ucosm" }, - ], - }, - ], - }, - ]); - }); - - it("works for unsuccessful transaction", async () => { - pendingWithoutWasmd(); - assert(unsuccessful); - const client = new RestClient(wasmd.endpoint); - const result = await client.txById(unsuccessful.hash); - expect(result.height).toBeGreaterThanOrEqual(1); - expect(result.txhash).toEqual(unsuccessful.hash); - expect(result.codespace).toEqual("sdk"); - expect(result.code).toEqual(5); - expect(result.logs).toBeUndefined(); - expect(result.raw_log).toContain("insufficient funds"); - }); - }); - - describe("txsQuery", () => { - let posted: - | { - readonly sender: string; - readonly recipient: string; - readonly hash: string; - readonly height: number; - readonly tx: TxsResponse; - } - | undefined; - - beforeAll(async () => { - if (wasmdEnabled()) { - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmWasmClient(wasmd.endpoint, faucet.address, (signBytes) => - pen.sign(signBytes), - ); - - const recipient = makeRandomAddress(); - const transferAmount = [ - { - denom: "ucosm", - amount: "1234567", - }, - ]; - const result = await client.sendTokens(recipient, transferAmount); - - await sleep(50); // wait until tx is indexed - const txDetails = await new RestClient(wasmd.endpoint).txById(result.transactionHash); - posted = { - sender: faucet.address, - recipient: recipient, - hash: result.transactionHash, - height: Number.parseInt(txDetails.height, 10), - tx: txDetails, - }; - } - }); - - it("can query transactions by height", async () => { - pendingWithoutWasmd(); - assert(posted); - const client = new RestClient(wasmd.endpoint); - const result = await client.txsQuery(`tx.height=${posted.height}&limit=26`); - expect(result).toEqual({ - count: "1", - limit: "26", - page_number: "1", - page_total: "1", - total_count: "1", - txs: [posted.tx], - }); - }); - - it("can query transactions by ID", async () => { - pendingWithoutWasmd(); - assert(posted); - const client = new RestClient(wasmd.endpoint); - const result = await client.txsQuery(`tx.hash=${posted.hash}&limit=26`); - expect(result).toEqual({ - count: "1", - limit: "26", - page_number: "1", - page_total: "1", - total_count: "1", - txs: [posted.tx], - }); - }); - - it("can query transactions by sender", async () => { - pendingWithoutWasmd(); - assert(posted); - const client = new RestClient(wasmd.endpoint); - const result = await client.txsQuery(`message.sender=${posted.sender}&limit=200`); - expect(parseInt(result.count, 10)).toBeGreaterThanOrEqual(1); - expect(parseInt(result.limit, 10)).toEqual(200); - expect(parseInt(result.page_number, 10)).toEqual(1); - expect(parseInt(result.page_total, 10)).toEqual(1); - expect(parseInt(result.total_count, 10)).toBeGreaterThanOrEqual(1); - expect(result.txs.length).toBeGreaterThanOrEqual(1); - expect(result.txs[result.txs.length - 1]).toEqual(posted.tx); - }); - - it("can query transactions by recipient", async () => { - pendingWithoutWasmd(); - assert(posted); - const client = new RestClient(wasmd.endpoint); - const result = await client.txsQuery(`transfer.recipient=${posted.recipient}&limit=200`); - expect(parseInt(result.count, 10)).toEqual(1); - expect(parseInt(result.limit, 10)).toEqual(200); - expect(parseInt(result.page_number, 10)).toEqual(1); - expect(parseInt(result.page_total, 10)).toEqual(1); - expect(parseInt(result.total_count, 10)).toEqual(1); - expect(result.txs.length).toBeGreaterThanOrEqual(1); - expect(result.txs[result.txs.length - 1]).toEqual(posted.tx); - }); - - it("can filter by tx.hash and tx.minheight", async () => { - pending("This combination is broken 🤷‍♂️. Handle client-side at higher level."); - pendingWithoutWasmd(); - assert(posted); - const client = new RestClient(wasmd.endpoint); - const hashQuery = `tx.hash=${posted.hash}`; - - { - const { count } = await client.txsQuery(`${hashQuery}&tx.minheight=0`); - expect(count).toEqual("1"); - } - - { - const { count } = await client.txsQuery(`${hashQuery}&tx.minheight=${posted.height - 1}`); - expect(count).toEqual("1"); - } - - { - const { count } = await client.txsQuery(`${hashQuery}&tx.minheight=${posted.height}`); - expect(count).toEqual("1"); - } - - { - const { count } = await client.txsQuery(`${hashQuery}&tx.minheight=${posted.height + 1}`); - expect(count).toEqual("0"); - } - }); - - it("can filter by recipient and tx.minheight", async () => { - pendingWithoutWasmd(); - assert(posted); - const client = new RestClient(wasmd.endpoint); - const recipientQuery = `transfer.recipient=${posted.recipient}`; - - { - const { count } = await client.txsQuery(`${recipientQuery}&tx.minheight=0`); - expect(count).toEqual("1"); - } - - { - const { count } = await client.txsQuery(`${recipientQuery}&tx.minheight=${posted.height - 1}`); - expect(count).toEqual("1"); - } - - { - const { count } = await client.txsQuery(`${recipientQuery}&tx.minheight=${posted.height}`); - expect(count).toEqual("1"); - } - - { - const { count } = await client.txsQuery(`${recipientQuery}&tx.minheight=${posted.height + 1}`); - expect(count).toEqual("0"); - } - }); - - it("can filter by recipient and tx.maxheight", async () => { - pendingWithoutWasmd(); - assert(posted); - const client = new RestClient(wasmd.endpoint); - const recipientQuery = `transfer.recipient=${posted.recipient}`; - - { - const { count } = await client.txsQuery(`${recipientQuery}&tx.maxheight=9999999999999`); - expect(count).toEqual("1"); - } - - { - const { count } = await client.txsQuery(`${recipientQuery}&tx.maxheight=${posted.height + 1}`); - expect(count).toEqual("1"); - } - - { - const { count } = await client.txsQuery(`${recipientQuery}&tx.maxheight=${posted.height}`); - expect(count).toEqual("1"); - } - - { - const { count } = await client.txsQuery(`${recipientQuery}&tx.maxheight=${posted.height - 1}`); - expect(count).toEqual("0"); - } - }); - - it("can query by tags (module + code_id)", async () => { - pendingWithoutWasmd(); - assert(posted); - const client = new RestClient(wasmd.endpoint); - const result = await client.txsQuery(`message.module=wasm&message.code_id=${deployedErc20.codeId}`); - expect(parseInt(result.count, 10)).toBeGreaterThanOrEqual(4); - - // Check first 4 results - const [store, hash, isa, jade] = result.txs.map((tx) => fromOneElementArray(tx.tx.value.msg)); - assert(isMsgStoreCode(store)); - assert(isMsgInstantiateContract(hash)); - assert(isMsgInstantiateContract(isa)); - assert(isMsgInstantiateContract(jade)); - expect(store.value).toEqual( - jasmine.objectContaining({ - sender: faucet.address, - source: deployedErc20.source, - builder: deployedErc20.builder, - }), - ); - expect(hash.value).toEqual({ - code_id: deployedErc20.codeId.toString(), - init_funds: [], - init_msg: jasmine.objectContaining({ - symbol: "HASH", - }), - label: "HASH", - sender: faucet.address, - }); - expect(isa.value).toEqual({ - code_id: deployedErc20.codeId.toString(), - init_funds: [], - init_msg: jasmine.objectContaining({ symbol: "ISA" }), - label: "ISA", - sender: faucet.address, - }); - expect(jade.value).toEqual({ - code_id: deployedErc20.codeId.toString(), - init_funds: [], - init_msg: jasmine.objectContaining({ symbol: "JADE" }), - label: "JADE", - sender: faucet.address, - }); - }); - - // Like previous test but filtered by message.action=store-code and message.action=instantiate - it("can query by tags (module + code_id + action)", async () => { - pendingWithoutWasmd(); - assert(posted); - const client = new RestClient(wasmd.endpoint); - - { - const uploads = await client.txsQuery( - `message.module=wasm&message.code_id=${deployedErc20.codeId}&message.action=store-code`, - ); - expect(parseInt(uploads.count, 10)).toEqual(1); - const store = fromOneElementArray(uploads.txs[0].tx.value.msg); - assert(isMsgStoreCode(store)); - expect(store.value).toEqual( - jasmine.objectContaining({ - sender: faucet.address, - source: deployedErc20.source, - builder: deployedErc20.builder, - }), - ); - } - - { - const instantiations = await client.txsQuery( - `message.module=wasm&message.code_id=${deployedErc20.codeId}&message.action=instantiate`, - ); - expect(parseInt(instantiations.count, 10)).toBeGreaterThanOrEqual(3); - const [hash, isa, jade] = instantiations.txs.map((tx) => fromOneElementArray(tx.tx.value.msg)); - assert(isMsgInstantiateContract(hash)); - assert(isMsgInstantiateContract(isa)); - assert(isMsgInstantiateContract(jade)); - expect(hash.value).toEqual({ - code_id: deployedErc20.codeId.toString(), - init_funds: [], - init_msg: jasmine.objectContaining({ - symbol: "HASH", - }), - label: "HASH", - sender: faucet.address, - }); - expect(isa.value).toEqual({ - code_id: deployedErc20.codeId.toString(), - init_funds: [], - init_msg: jasmine.objectContaining({ symbol: "ISA" }), - label: "ISA", - sender: faucet.address, - }); - expect(jade.value).toEqual({ - code_id: deployedErc20.codeId.toString(), - init_funds: [], - init_msg: jasmine.objectContaining({ symbol: "JADE" }), - label: "JADE", - sender: faucet.address, - }); - } - }); - }); - - describe("encodeTx", () => { - it("works for cosmoshub example", async () => { - pendingWithoutWasmd(); - const client = new RestClient(wasmd.endpoint); - expect(await client.encodeTx(cosmoshub.tx)).toEqual(fromBase64(cosmoshub.tx_data)); - }); - }); - - describe("postTx", () => { - it("can send tokens", async () => { - pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - - const memo = "My first contract on chain"; - const theMsg: MsgSend = { - type: "cosmos-sdk/MsgSend", - value: { - from_address: faucet.address, - to_address: emptyAddress, - amount: [ - { - denom: "ucosm", - amount: "1234567", - }, - ], - }, - }; - - const fee: StdFee = { - amount: [ - { - amount: "5000", - denom: "ucosm", - }, - ], - gas: "890000", - }; - - const client = new RestClient(wasmd.endpoint); - const { account_number, sequence } = (await client.authAccounts(faucet.address)).result.value; - - const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); - const signature = await pen.sign(signBytes); - const signedTx = makeSignedTx(theMsg, fee, memo, signature); - const result = await client.postTx(signedTx); - expect(result.code).toBeUndefined(); - expect(result).toEqual({ - height: jasmine.stringMatching(nonNegativeIntegerMatcher), - txhash: jasmine.stringMatching(tendermintIdMatcher), - // code is not set - raw_log: jasmine.stringMatching(/^\[.+\]$/i), - logs: jasmine.any(Array), - gas_wanted: jasmine.stringMatching(nonNegativeIntegerMatcher), - gas_used: jasmine.stringMatching(nonNegativeIntegerMatcher), - }); - }); - - it("can't send transaction with additional signatures", async () => { - pendingWithoutWasmd(); - const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); - const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); - const account3 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(2)); - const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos"); - const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos"); - const address3 = rawSecp256k1PubkeyToAddress(account3.pubkey, "cosmos"); - - const memo = "My first contract on chain"; - const theMsg: MsgSend = { - type: "cosmos-sdk/MsgSend", - value: { - from_address: address1, - to_address: emptyAddress, - amount: [ - { - denom: "ucosm", - amount: "1234567", - }, - ], - }, - }; - - const fee: StdFee = { - amount: [ - { - amount: "5000", - denom: "ucosm", - }, - ], - gas: "890000", - }; - - const client = new RestClient(wasmd.endpoint); - const { account_number: an1, sequence: sequence1 } = (await client.authAccounts(address1)).result.value; - const { account_number: an2, sequence: sequence2 } = (await client.authAccounts(address2)).result.value; - const { account_number: an3, sequence: sequence3 } = (await client.authAccounts(address3)).result.value; - - const signBytes1 = makeSignBytes([theMsg], fee, wasmd.chainId, memo, an1, sequence1); - const signBytes2 = makeSignBytes([theMsg], fee, wasmd.chainId, memo, an2, sequence2); - const signBytes3 = makeSignBytes([theMsg], fee, wasmd.chainId, memo, an3, sequence3); - const signature1 = await account1.sign(signBytes1); - const signature2 = await account2.sign(signBytes2); - const signature3 = await account3.sign(signBytes3); - const signedTx = { - msg: [theMsg], - fee: fee, - memo: memo, - signatures: [signature1, signature2, signature3], - }; - const postResult = await client.postTx(signedTx); - // console.log(postResult.raw_log); - expect(postResult.code).toEqual(4); - expect(postResult.raw_log).toContain("wrong number of signers"); - }); - - it("can send multiple messages with one signature", async () => { - pendingWithoutWasmd(); - const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); - const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos"); - - const memo = "My first contract on chain"; - const msg1: MsgSend = { - type: "cosmos-sdk/MsgSend", - value: { - from_address: address1, - to_address: emptyAddress, - amount: [ - { - denom: "ucosm", - amount: "1234567", - }, - ], - }, - }; - const msg2: MsgSend = { - type: "cosmos-sdk/MsgSend", - value: { - from_address: address1, - to_address: emptyAddress, - amount: [ - { - denom: "ucosm", - amount: "7654321", - }, - ], - }, - }; - - const fee: StdFee = { - amount: [ - { - amount: "5000", - denom: "ucosm", - }, - ], - gas: "890000", - }; - - const client = new RestClient(wasmd.endpoint); - const { account_number, sequence } = (await client.authAccounts(address1)).result.value; - - const signBytes = makeSignBytes([msg1, msg2], fee, wasmd.chainId, memo, account_number, sequence); - const signature1 = await account1.sign(signBytes); - const signedTx = { - msg: [msg1, msg2], - fee: fee, - memo: memo, - signatures: [signature1], - }; - const postResult = await client.postTx(signedTx); - // console.log(postResult.raw_log); - expect(postResult.code).toBeUndefined(); - }); - - it("can send multiple messages with multiple signatures", async () => { - pendingWithoutWasmd(); - const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); - const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); - const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos"); - const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos"); - - const memo = "My first contract on chain"; - const msg1: MsgSend = { - type: "cosmos-sdk/MsgSend", - value: { - from_address: address1, - to_address: emptyAddress, - amount: [ - { - denom: "ucosm", - amount: "1234567", - }, - ], - }, - }; - const msg2: MsgSend = { - type: "cosmos-sdk/MsgSend", - value: { - from_address: address2, - to_address: emptyAddress, - amount: [ - { - denom: "ucosm", - amount: "7654321", - }, - ], - }, - }; - - const fee: StdFee = { - amount: [ - { - amount: "5000", - denom: "ucosm", - }, - ], - gas: "890000", - }; - - const client = new RestClient(wasmd.endpoint); - const { account_number: an1, sequence: sequence1 } = (await client.authAccounts(address1)).result.value; - const { account_number: an2, sequence: sequence2 } = (await client.authAccounts(address2)).result.value; - - const signBytes1 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an1, sequence1); - const signBytes2 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an2, sequence2); - const signature1 = await account1.sign(signBytes1); - const signature2 = await account2.sign(signBytes2); - const signedTx = { - msg: [msg2, msg1], - fee: fee, - memo: memo, - signatures: [signature2, signature1], - }; - const postResult = await client.postTx(signedTx); - // console.log(postResult.raw_log); - expect(postResult.code).toBeUndefined(); - - await sleep(500); - const searched = await client.txsQuery(`tx.hash=${postResult.txhash}`); - expect(searched.txs.length).toEqual(1); - expect(searched.txs[0].tx.value.signatures).toEqual([signature2, signature1]); - }); - - it("can't send transaction with wrong signature order (1)", async () => { - pendingWithoutWasmd(); - const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); - const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); - const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos"); - const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos"); - - const memo = "My first contract on chain"; - const msg1: MsgSend = { - type: "cosmos-sdk/MsgSend", - value: { - from_address: address1, - to_address: emptyAddress, - amount: [ - { - denom: "ucosm", - amount: "1234567", - }, - ], - }, - }; - const msg2: MsgSend = { - type: "cosmos-sdk/MsgSend", - value: { - from_address: address2, - to_address: emptyAddress, - amount: [ - { - denom: "ucosm", - amount: "7654321", - }, - ], - }, - }; - - const fee: StdFee = { - amount: [ - { - amount: "5000", - denom: "ucosm", - }, - ], - gas: "890000", - }; - - const client = new RestClient(wasmd.endpoint); - const { account_number: an1, sequence: sequence1 } = (await client.authAccounts(address1)).result.value; - const { account_number: an2, sequence: sequence2 } = (await client.authAccounts(address2)).result.value; - - const signBytes1 = makeSignBytes([msg1, msg2], fee, wasmd.chainId, memo, an1, sequence1); - const signBytes2 = makeSignBytes([msg1, msg2], fee, wasmd.chainId, memo, an2, sequence2); - const signature1 = await account1.sign(signBytes1); - const signature2 = await account2.sign(signBytes2); - const signedTx = { - msg: [msg1, msg2], - fee: fee, - memo: memo, - signatures: [signature2, signature1], - }; - const postResult = await client.postTx(signedTx); - // console.log(postResult.raw_log); - expect(postResult.code).toEqual(8); - }); - - it("can't send transaction with wrong signature order (2)", async () => { - pendingWithoutWasmd(); - const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); - const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); - const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos"); - const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos"); - - const memo = "My first contract on chain"; - const msg1: MsgSend = { - type: "cosmos-sdk/MsgSend", - value: { - from_address: address1, - to_address: emptyAddress, - amount: [ - { - denom: "ucosm", - amount: "1234567", - }, - ], - }, - }; - const msg2: MsgSend = { - type: "cosmos-sdk/MsgSend", - value: { - from_address: address2, - to_address: emptyAddress, - amount: [ - { - denom: "ucosm", - amount: "7654321", - }, - ], - }, - }; - - const fee: StdFee = { - amount: [ - { - amount: "5000", - denom: "ucosm", - }, - ], - gas: "890000", - }; - - const client = new RestClient(wasmd.endpoint); - const { account_number: an1, sequence: sequence1 } = (await client.authAccounts(address1)).result.value; - const { account_number: an2, sequence: sequence2 } = (await client.authAccounts(address2)).result.value; - - const signBytes1 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an1, sequence1); - const signBytes2 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an2, sequence2); - const signature1 = await account1.sign(signBytes1); - const signature2 = await account2.sign(signBytes2); - const signedTx = { - msg: [msg2, msg1], - fee: fee, - memo: memo, - signatures: [signature1, signature2], - }; - const postResult = await client.postTx(signedTx); - // console.log(postResult.raw_log); - expect(postResult.code).toEqual(8); - }); - - it("can upload, instantiate and execute wasm", async () => { - pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new RestClient(wasmd.endpoint); - - const transferAmount: readonly Coin[] = [ - { - amount: "1234", - denom: "ucosm", - }, - { - amount: "321", - denom: "ustake", - }, - ]; - const beneficiaryAddress = makeRandomAddress(); - - let codeId: number; - - // upload - { - // console.log("Raw log:", result.raw_log); - const result = await uploadContract(client, pen); - expect(result.code).toBeFalsy(); - const logs = parseLogs(result.logs); - const codeIdAttr = findAttribute(logs, "message", "code_id"); - codeId = Number.parseInt(codeIdAttr.value, 10); - expect(codeId).toBeGreaterThanOrEqual(1); - expect(codeId).toBeLessThanOrEqual(200); - } - - let contractAddress: string; - - // instantiate - { - const result = await instantiateContract(client, pen, codeId, beneficiaryAddress, transferAmount); - expect(result.code).toBeFalsy(); - // console.log("Raw log:", result.raw_log); - const logs = parseLogs(result.logs); - const contractAddressAttr = findAttribute(logs, "message", "contract_address"); - contractAddress = contractAddressAttr.value; - const amountAttr = findAttribute(logs, "transfer", "amount"); - expect(amountAttr.value).toEqual("1234ucosm,321ustake"); - - const balance = (await client.authAccounts(contractAddress)).result.value.coins; - expect(balance).toEqual(transferAmount); - } - - // execute - { - const result = await executeContract(client, pen, contractAddress); - expect(result.code).toBeFalsy(); - // console.log("Raw log:", result.logs); - const logs = parseLogs(result.logs); - const wasmEvent = logs.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 beneficiaryBalance = (await client.authAccounts(beneficiaryAddress)).result.value.coins; - expect(beneficiaryBalance).toEqual(transferAmount); - const contractBalance = (await client.authAccounts(contractAddress)).result.value.coins; - expect(contractBalance).toEqual([]); - } - }); - }); - - // The /wasm endpoints - - describe("query", () => { - it("can list upload code", async () => { - pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new RestClient(wasmd.endpoint); - - // check with contracts were here first to compare - const existingInfos = await client.listCodeInfo(); - existingInfos.forEach((val, idx) => expect(val.id).toEqual(idx + 1)); - const numExisting = existingInfos.length; - - // upload data - const wasmCode = getHackatom(); - const result = await uploadCustomContract(client, pen, wasmCode); - expect(result.code).toBeFalsy(); - const logs = parseLogs(result.logs); - const codeIdAttr = findAttribute(logs, "message", "code_id"); - const codeId = Number.parseInt(codeIdAttr.value, 10); - - // ensure we were added to the end of the list - const newInfos = await client.listCodeInfo(); - expect(newInfos.length).toEqual(numExisting + 1); - const lastInfo = newInfos[newInfos.length - 1]; - expect(lastInfo.id).toEqual(codeId); - expect(lastInfo.creator).toEqual(faucet.address); - - // ensure metadata is present - expect(lastInfo.source).toEqual( - "https://github.com/confio/cosmwasm/raw/0.7/lib/vm/testdata/contract_0.6.wasm", - ); - expect(lastInfo.builder).toEqual("confio/cosmwasm-opt:0.6.2"); - - // check code hash matches expectation - const wasmHash = new Sha256(wasmCode).digest(); - expect(lastInfo.data_hash.toLowerCase()).toEqual(toHex(wasmHash)); - - // download code and check against auto-gen - const { data } = await client.getCode(codeId); - expect(fromBase64(data)).toEqual(wasmCode); - }); - - it("can list contracts and get info", async () => { - pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new RestClient(wasmd.endpoint); - const beneficiaryAddress = makeRandomAddress(); - const transferAmount: readonly Coin[] = [ - { - amount: "707707", - denom: "ucosm", - }, - ]; - - // reuse an existing contract, or upload if needed - let codeId: number; - const existingInfos = await client.listCodeInfo(); - if (existingInfos.length > 0) { - codeId = existingInfos[existingInfos.length - 1].id; - } else { - const uploadResult = await uploadContract(client, pen); - expect(uploadResult.code).toBeFalsy(); - const uploadLogs = parseLogs(uploadResult.logs); - const codeIdAttr = findAttribute(uploadLogs, "message", "code_id"); - codeId = Number.parseInt(codeIdAttr.value, 10); - } - - // create new instance and compare before and after - const existingContractsByCode = await client.listContractsByCodeId(codeId); - for (const contract of existingContractsByCode) { - expect(contract.address).toMatch(bech32AddressMatcher); - expect(contract.code_id).toEqual(codeId); - expect(contract.creator).toMatch(bech32AddressMatcher); - expect(contract.label).toMatch(/^.+$/); - } - - const result = await instantiateContract(client, pen, codeId, beneficiaryAddress, transferAmount); - expect(result.code).toBeFalsy(); - const logs = parseLogs(result.logs); - const contractAddressAttr = findAttribute(logs, "message", "contract_address"); - const myAddress = contractAddressAttr.value; - - const newContractsByCode = await client.listContractsByCodeId(codeId); - expect(newContractsByCode.length).toEqual(existingContractsByCode.length + 1); - const newContract = newContractsByCode[newContractsByCode.length - 1]; - expect(newContract).toEqual( - jasmine.objectContaining({ - code_id: codeId, - creator: faucet.address, - label: "my escrow", - }), - ); - - // check out info - const myInfo = await client.getContractInfo(myAddress); - assert(myInfo); - expect(myInfo.code_id).toEqual(codeId); - expect(myInfo.creator).toEqual(faucet.address); - expect((myInfo.init_msg as any).beneficiary).toEqual(beneficiaryAddress); - - // make sure random addresses don't give useful info - const nonExistentAddress = makeRandomAddress(); - expect(await client.getContractInfo(nonExistentAddress)).toBeNull(); - }); - - describe("contract state", () => { - const client = new RestClient(wasmd.endpoint); - const noContract = makeRandomAddress(); - const expectedKey = toAscii("config"); - let contractAddress: string | undefined; - - beforeAll(async () => { - if (wasmdEnabled()) { - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const uploadResult = await uploadContract(client, pen); - assert(!uploadResult.code); - const uploadLogs = parseLogs(uploadResult.logs); - const codeId = Number.parseInt(findAttribute(uploadLogs, "message", "code_id").value, 10); - const instantiateResult = await instantiateContract(client, pen, codeId, makeRandomAddress()); - assert(!instantiateResult.code); - const instantiateLogs = parseLogs(instantiateResult.logs); - const contractAddressAttr = findAttribute(instantiateLogs, "message", "contract_address"); - contractAddress = contractAddressAttr.value; - } - }); - - it("can get all state", async () => { - pendingWithoutWasmd(); - - // get contract state - const state = await client.getAllContractState(contractAddress!); - expect(state.length).toEqual(1); - const data = state[0]; - expect(data.key).toEqual(expectedKey); - const value = JSON.parse(fromAscii(data.val)); - expect(value.verifier).toBeDefined(); - expect(value.beneficiary).toBeDefined(); - - // bad address is empty array - const noContractState = await client.getAllContractState(noContract); - expect(noContractState).toEqual([]); - }); - - it("can query by key", async () => { - pendingWithoutWasmd(); - - // query by one key - const raw = await client.queryContractRaw(contractAddress!, expectedKey); - assert(raw, "must get result"); - const model = JSON.parse(fromAscii(raw)); - expect(model.verifier).toBeDefined(); - expect(model.beneficiary).toBeDefined(); - - // missing key is null - const missing = await client.queryContractRaw(contractAddress!, fromHex("cafe0dad")); - expect(missing).toBeNull(); - - // bad address is null - const noContractModel = await client.queryContractRaw(noContract, expectedKey); - expect(noContractModel).toBeNull(); - }); - - it("can make smart queries", async () => { - pendingWithoutWasmd(); - - // we can query the verifier properly - const resultDocument = await client.queryContractSmart(contractAddress!, { verifier: {} }); - expect(resultDocument).toEqual({ verifier: faucet.address }); - - // invalid query syntax throws an error - await client.queryContractSmart(contractAddress!, { nosuchkey: {} }).then( - () => fail("shouldn't succeed"), - (error) => - expect(error).toMatch(/query wasm contract failed: parsing hackatom::contract::QueryMsg/), - ); - - // invalid address throws an error - await client.queryContractSmart(noContract, { verifier: {} }).then( - () => fail("shouldn't succeed"), - (error) => expect(error).toMatch("not found"), - ); - }); - }); - }); -}); diff --git a/packages/sdk/src/restclient.ts b/packages/sdk/src/restclient.ts deleted file mode 100644 index cb6316e9..00000000 --- a/packages/sdk/src/restclient.ts +++ /dev/null @@ -1,457 +0,0 @@ -import { Encoding, isNonNullObject } from "@iov/encoding"; -import axios, { AxiosError, AxiosInstance } from "axios"; - -import { Coin } from "./coins"; -import { CosmosSdkTx, JsonObject, Model, parseWasmData, StdTx, WasmData } from "./types"; - -const { fromBase64, fromUtf8, toHex, toUtf8 } = Encoding; - -export interface CosmosSdkAccount { - /** Bech32 account address */ - readonly address: string; - readonly coins: ReadonlyArray; - /** Bech32 encoded pubkey */ - readonly public_key: string; - readonly account_number: number; - readonly sequence: number; -} - -export interface NodeInfo { - readonly protocol_version: { - readonly p2p: string; - readonly block: string; - readonly app: string; - }; - readonly id: string; - readonly listen_addr: string; - readonly network: string; - readonly version: string; - readonly channels: string; - readonly moniker: string; - readonly other: { - readonly tx_index: string; - readonly rpc_address: string; - }; -} - -export interface ApplicationVersion { - readonly name: string; - readonly server_name: string; - readonly client_name: string; - readonly version: string; - readonly commit: string; - readonly build_tags: string; - readonly go: string; -} - -export interface NodeInfoResponse { - readonly node_info: NodeInfo; - readonly application_version: ApplicationVersion; -} - -export interface BlockId { - readonly hash: string; - // TODO: here we also have this - // parts: { - // total: '1', - // hash: '7AF200C78FBF9236944E1AB270F4045CD60972B7C265E3A9DA42973397572931' - // } -} - -export interface BlockHeader { - readonly version: { - readonly block: string; - readonly app: string; - }; - readonly height: string; - readonly chain_id: string; - /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ - readonly time: string; - readonly last_commit_hash: string; - readonly last_block_id: BlockId; - /** Can be empty */ - readonly data_hash: string; - readonly validators_hash: string; - readonly next_validators_hash: string; - readonly consensus_hash: string; - readonly app_hash: string; - /** Can be empty */ - readonly last_results_hash: string; - /** Can be empty */ - readonly evidence_hash: string; - readonly proposer_address: string; -} - -export interface Block { - readonly header: BlockHeader; - readonly data: { - /** Array of base64 encoded transactions */ - readonly txs: ReadonlyArray | null; - }; -} - -export interface BlockResponse { - readonly block_id: BlockId; - readonly block: Block; -} - -interface AuthAccountsResponse { - readonly height: string; - readonly result: { - readonly type: "cosmos-sdk/Account"; - readonly value: CosmosSdkAccount; - }; -} - -// Currently all wasm query responses return json-encoded strings... -// later deprecate this and use the specific types for result -// (assuming it is inlined, no second parse needed) -type WasmResponse = WasmSuccess | WasmError; - -interface WasmSuccess { - readonly height: string; - readonly result: T; -} - -interface WasmError { - readonly error: string; -} - -export interface TxsResponse { - readonly height: string; - readonly txhash: string; - /** 🤷‍♂️ */ - readonly codespace?: string; - /** Falsy when transaction execution succeeded. Contains error code on error. */ - readonly code?: number; - readonly raw_log: string; - readonly logs?: object; - readonly tx: CosmosSdkTx; - /** The gas limit as set by the user */ - readonly gas_wanted?: string; - /** The gas used by the execution */ - readonly gas_used?: string; - readonly timestamp: string; -} - -interface SearchTxsResponse { - readonly total_count: string; - readonly count: string; - readonly page_number: string; - readonly page_total: string; - readonly limit: string; - readonly txs: readonly TxsResponse[]; -} - -export interface PostTxsResponse { - readonly height: string; - readonly txhash: string; - readonly code?: number; - readonly raw_log?: string; - /** The same as `raw_log` but deserialized? */ - readonly logs?: object; - /** The gas limit as set by the user */ - readonly gas_wanted?: string; - /** The gas used by the execution */ - readonly gas_used?: string; -} - -interface EncodeTxResponse { - // base64-encoded amino-binary encoded representation - readonly tx: string; -} - -export interface CodeInfo { - readonly id: number; - /** Bech32 account address */ - readonly creator: string; - /** Hex-encoded sha256 hash of the code stored here */ - readonly data_hash: string; - // TODO: these are not supported in current wasmd - readonly source?: string; - readonly builder?: string; -} - -export interface CodeDetails extends CodeInfo { - /** Base64 encoded raw wasm data */ - readonly data: string; -} - -// This is list view, without contract info -export interface ContractInfo { - readonly address: string; - readonly code_id: number; - /** Bech32 account address */ - readonly creator: string; - readonly label: string; -} - -export interface ContractDetails extends ContractInfo { - /** Argument passed on initialization of the contract */ - readonly init_msg: object; -} - -interface SmartQueryResponse { - // base64 encoded response - readonly smart: string; -} - -type RestClientResponse = - | NodeInfoResponse - | BlockResponse - | AuthAccountsResponse - | TxsResponse - | SearchTxsResponse - | PostTxsResponse - | EncodeTxResponse - | WasmResponse - | WasmResponse - | WasmResponse - | WasmResponse - | WasmResponse; - -/** Unfortunately, Cosmos SDK encodes empty arrays as null */ -type CosmosSdkArray = ReadonlyArray | null; - -function normalizeArray(backend: CosmosSdkArray): ReadonlyArray { - return backend || []; -} - -/** - * The mode used to send transaction - * - * @see https://cosmos.network/rpc/#/Transactions/post_txs - */ -export enum BroadcastMode { - /** Return after tx commit */ - Block = "block", - /** Return afer CheckTx */ - Sync = "sync", - /** Return right away */ - Async = "async", -} - -function isWasmError(resp: WasmResponse): resp is WasmError { - return (resp as WasmError).error !== undefined; -} - -function unwrapWasmResponse(response: WasmResponse): T { - if (isWasmError(response)) { - throw new Error(response.error); - } - return response.result; -} - -// We want to get message data from 500 errors -// https://stackoverflow.com/questions/56577124/how-to-handle-500-error-message-with-axios -// this should be chained to catch one error and throw a more informative one -function parseAxiosError(err: AxiosError): never { - // use the error message sent from server, not default 500 msg - if (err.response?.data) { - let errorText: string; - const data = err.response.data; - // expect { error: string }, but otherwise dump - if (data.error && typeof data.error === "string") { - errorText = data.error; - } else if (typeof data === "string") { - errorText = data; - } else { - errorText = JSON.stringify(data); - } - throw new Error(`${errorText} (HTTP ${err.response.status})`); - } else { - throw err; - } -} - -export class RestClient { - private readonly client: AxiosInstance; - private readonly broadcastMode: BroadcastMode; - - /** - * Creates a new client to interact with a Cosmos SDK light client daemon. - * This class tries to be a direct mapping onto the API. Some basic decoding and normalizatin is done - * but things like caching are done at a higher level. - * - * When building apps, you should not need to use this class directly. If you do, this indicates a missing feature - * in higher level components. Feel free to raise an issue in this case. - * - * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) - * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns - */ - public constructor(apiUrl: string, broadcastMode = BroadcastMode.Block) { - const headers = { - post: { "Content-Type": "application/json" }, - }; - this.client = axios.create({ - baseURL: apiUrl, - headers: headers, - }); - this.broadcastMode = broadcastMode; - } - - public async get(path: string): Promise { - const { data } = await this.client.get(path).catch(parseAxiosError); - if (data === null) { - throw new Error("Received null response from server"); - } - return data; - } - - public async post(path: string, params: any): Promise { - if (!isNonNullObject(params)) throw new Error("Got unexpected type of params. Expected object."); - const { data } = await this.client.post(path, params).catch(parseAxiosError); - if (data === null) { - throw new Error("Received null response from server"); - } - return data; - } - - // The /auth endpoints - - public async authAccounts(address: string): Promise { - const path = `/auth/accounts/${address}`; - const responseData = await this.get(path); - if ((responseData as any).result.type !== "cosmos-sdk/Account") { - throw new Error("Unexpected response data format"); - } - return responseData as AuthAccountsResponse; - } - - // The /blocks endpoints - - public async blocksLatest(): Promise { - const responseData = await this.get("/blocks/latest"); - if (!(responseData as any).block) { - throw new Error("Unexpected response data format"); - } - return responseData as BlockResponse; - } - - public async blocks(height: number): Promise { - const responseData = await this.get(`/blocks/${height}`); - if (!(responseData as any).block) { - throw new Error("Unexpected response data format"); - } - return responseData as BlockResponse; - } - - // The /node_info endpoint - - public async nodeInfo(): Promise { - const responseData = await this.get("/node_info"); - if (!(responseData as any).node_info) { - throw new Error("Unexpected response data format"); - } - return responseData as NodeInfoResponse; - } - - // The /txs endpoints - - public async txById(id: string): Promise { - const responseData = await this.get(`/txs/${id}`); - if (!(responseData as any).tx) { - throw new Error("Unexpected response data format"); - } - return responseData as TxsResponse; - } - - public async txsQuery(query: string): Promise { - const responseData = await this.get(`/txs?${query}`); - if (!(responseData as any).txs) { - throw new Error("Unexpected response data format"); - } - return responseData as SearchTxsResponse; - } - - /** returns the amino-encoding of the transaction performed by the server */ - public async encodeTx(tx: CosmosSdkTx): Promise { - const responseData = await this.post("/txs/encode", tx); - if (!(responseData as any).tx) { - throw new Error("Unexpected response data format"); - } - return Encoding.fromBase64((responseData as EncodeTxResponse).tx); - } - - /** - * Broadcasts a signed transaction to into the transaction pool. - * Depending on the RestClient's broadcast mode, this might or might - * wait for checkTx or deliverTx to be executed before returning. - * - * @param tx a signed transaction as StdTx (i.e. not wrapped in type/value container) - */ - public async postTx(tx: StdTx): Promise { - const params = { - tx: tx, - mode: this.broadcastMode, - }; - const responseData = await this.post("/txs", params); - if (!(responseData as any).txhash) { - throw new Error("Unexpected response data format"); - } - return responseData as PostTxsResponse; - } - - // The /wasm endpoints - - // wasm rest queries are listed here: https://github.com/cosmwasm/wasmd/blob/master/x/wasm/client/rest/query.go#L19-L27 - public async listCodeInfo(): Promise { - const path = `/wasm/code`; - const responseData = (await this.get(path)) as WasmResponse>; - return normalizeArray(unwrapWasmResponse(responseData)); - } - - // this will download the original wasm bytecode by code id - // throws error if no code with this id - public async getCode(id: number): Promise { - const path = `/wasm/code/${id}`; - const responseData = (await this.get(path)) as WasmResponse; - return unwrapWasmResponse(responseData); - } - - public async listContractsByCodeId(id: number): Promise { - const path = `/wasm/code/${id}/contracts`; - const responseData = (await this.get(path)) as WasmResponse>; - return normalizeArray(unwrapWasmResponse(responseData)); - } - - /** - * Returns null when contract was not found at this address. - */ - public async getContractInfo(address: string): Promise { - const path = `/wasm/contract/${address}`; - const response = (await this.get(path)) as WasmResponse; - return unwrapWasmResponse(response); - } - - // Returns all contract state. - // This is an empty array if no such contract, or contract has no data. - public async getAllContractState(address: string): Promise { - const path = `/wasm/contract/${address}/state`; - const responseData = (await this.get(path)) as WasmResponse>; - return normalizeArray(unwrapWasmResponse(responseData)).map(parseWasmData); - } - - // Returns the data at the key if present (unknown decoded json), - // or null if no data at this (contract address, key) pair - public async queryContractRaw(address: string, key: Uint8Array): Promise { - const hexKey = toHex(key); - const path = `/wasm/contract/${address}/raw/${hexKey}?encoding=hex`; - const responseData = (await this.get(path)) as WasmResponse; - const data = unwrapWasmResponse(responseData); - return data.length === 0 ? null : fromBase64(data[0].val); - } - - /** - * Makes a smart query on the contract and parses the reponse as JSON. - * Throws error if no such contract exists, the query format is invalid or the response is invalid. - */ - public async queryContractSmart(address: string, query: object): Promise { - const encoded = toHex(toUtf8(JSON.stringify(query))); - const path = `/wasm/contract/${address}/smart/${encoded}?encoding=hex`; - const responseData = (await this.get(path)) as WasmResponse; - const result = unwrapWasmResponse(responseData); - // By convention, smart queries must return a valid JSON document (see https://github.com/CosmWasm/cosmwasm/issues/144) - return JSON.parse(fromUtf8(fromBase64(result.smart))); - } -} diff --git a/packages/sdk/src/sequence.spec.ts b/packages/sdk/src/sequence.spec.ts deleted file mode 100644 index e5d8f4cd..00000000 --- a/packages/sdk/src/sequence.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { findSequenceForSignedTx } from "./sequence"; -import response1 from "./testdata/txresponse1.json"; -import response2 from "./testdata/txresponse2.json"; -import response3 from "./testdata/txresponse3.json"; - -// Those values must match ./testdata/txresponse*.json -const chainId = "testing"; -const accountNumber = 4; - -describe("sequence", () => { - describe("findSequenceForSignedTx", () => { - it("works", async () => { - const current = 100; // what we get from GET /auth/accounts/{address} - expect(await findSequenceForSignedTx(response1.tx, chainId, accountNumber, current)).toEqual(10); - // We know response3.height > response1.height, so the sequence must be at least 10+1 - expect(await findSequenceForSignedTx(response3.tx, chainId, accountNumber, current, 11)).toEqual(19); - // We know response3.height > response2.height > response1.height, so the sequence must be at least 10+1 and smaller than 19 - expect(await findSequenceForSignedTx(response2.tx, chainId, accountNumber, 19, 11)).toEqual(13); - }); - - it("returns undefined when sequence is not in range", async () => { - expect(await findSequenceForSignedTx(response1.tx, chainId, accountNumber, 5)).toBeUndefined(); - expect(await findSequenceForSignedTx(response1.tx, chainId, accountNumber, 20, 11)).toBeUndefined(); - expect(await findSequenceForSignedTx(response1.tx, chainId, accountNumber, 20, 50)).toBeUndefined(); - - // upper bound is not included in the possible results - expect(await findSequenceForSignedTx(response1.tx, chainId, accountNumber, 10)).toBeUndefined(); - }); - }); -}); diff --git a/packages/sdk/src/sequence.ts b/packages/sdk/src/sequence.ts deleted file mode 100644 index 5cf2a738..00000000 --- a/packages/sdk/src/sequence.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Secp256k1, Secp256k1Signature, Sha256 } from "@iov/crypto"; - -import { makeSignBytes } from "./encoding"; -import { decodeSignature } from "./signature"; -import { CosmosSdkTx } from "./types"; - -/** - * Serach for sequence s with `min` <= `s` < `upperBound` to find the sequence that was used to sign the transaction - * - * @param tx The signed transaction - * @param chainId The chain ID for which this transaction was signed - * @param accountNumber The account number for which this transaction was signed - * @param upperBound The upper bound for the testing, i.e. sequence must be lower than this value - * @param min The lowest sequence that is tested - * - * @returns the sequence if a match was found and undefined otherwise - */ -export async function findSequenceForSignedTx( - tx: CosmosSdkTx, - chainId: string, - accountNumber: number, - upperBound: number, - min = 0, -): Promise { - const firstSignature = tx.value.signatures.find(() => true); - if (!firstSignature) throw new Error("Signature missing in tx"); - - const { pubkey, signature } = decodeSignature(firstSignature); - const secp256keSignature = Secp256k1Signature.fromFixedLength(signature); - - for (let s = min; s < upperBound; s++) { - // console.log(`Trying sequence ${s}`); - const signBytes = makeSignBytes( - tx.value.msg, - tx.value.fee, - chainId, - tx.value.memo || "", - accountNumber, - s, - ); - const prehashed = new Sha256(signBytes).digest(); - const valid = await Secp256k1.verifySignature(secp256keSignature, prehashed, pubkey); - if (valid) return s; - } - return undefined; -} diff --git a/packages/sdk/src/signature.spec.ts b/packages/sdk/src/signature.spec.ts deleted file mode 100644 index b0e782c1..00000000 --- a/packages/sdk/src/signature.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Encoding } from "@iov/encoding"; - -import { decodeSignature, encodeSecp256k1Signature } from "./signature"; -import { StdSignature } from "./types"; - -const { fromBase64 } = Encoding; - -describe("signature", () => { - describe("encodeSecp256k1Signature", () => { - it("encodes a full signature", () => { - const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP"); - const signature = fromBase64( - "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==", - ); - expect(encodeSecp256k1Signature(pubkey, signature)).toEqual({ - // eslint-disable-next-line @typescript-eslint/camelcase - pub_key: { - type: "tendermint/PubKeySecp256k1", - value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP", - }, - signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==", - }); - }); - - it("throws when getting uncompressed public keys", () => { - const pubkey = fromBase64( - "BE8EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQE7WHpoHoNswYeoFkuYpYSKK4mzFzMV/dB0DVAy4lnNU=", - ); - const signature = fromBase64( - "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==", - ); - expect(() => encodeSecp256k1Signature(pubkey, signature)).toThrowError( - /public key must be compressed secp256k1/i, - ); - }); - - it("throws if signature contains recovery byte", () => { - const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP"); - const signature = Uint8Array.from([ - ...fromBase64( - "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==", - ), - 99, - ]); - expect(() => encodeSecp256k1Signature(pubkey, signature)).toThrowError( - /signature must be 64 bytes long/i, - ); - }); - }); - - describe("decodeSignature", () => { - it("works for secp256k1", () => { - const signature: StdSignature = { - // eslint-disable-next-line @typescript-eslint/camelcase - pub_key: { - type: "tendermint/PubKeySecp256k1", - value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP", - }, - signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==", - }; - expect(decodeSignature(signature)).toEqual({ - pubkey: fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP"), - signature: fromBase64( - "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==", - ), - }); - }); - }); -}); diff --git a/packages/sdk/src/signature.ts b/packages/sdk/src/signature.ts deleted file mode 100644 index d31eb119..00000000 --- a/packages/sdk/src/signature.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Encoding } from "@iov/encoding"; - -import { encodeSecp256k1Pubkey } from "./pubkey"; -import { pubkeyType, StdSignature } from "./types"; - -/** - * Takes a binary pubkey and signature to create a signature object - * - * @param pubkey a compressed secp256k1 public key - * @param signature a 64 byte fixed length representation of secp256k1 signature components r and s - */ -export function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature { - if (signature.length !== 64) { - throw new Error( - "Signature must be 64 bytes long. Cosmos SDK uses a 2x32 byte fixed length encoding for the secp256k1 signature integers r and s.", - ); - } - - return { - // eslint-disable-next-line @typescript-eslint/camelcase - pub_key: encodeSecp256k1Pubkey(pubkey), - signature: Encoding.toBase64(signature), - }; -} - -export function decodeSignature( - signature: StdSignature, -): { readonly pubkey: Uint8Array; readonly signature: Uint8Array } { - switch (signature.pub_key.type) { - // Note: please don't add cases here without writing additional unit tests - case pubkeyType.secp256k1: - return { - pubkey: Encoding.fromBase64(signature.pub_key.value), - signature: Encoding.fromBase64(signature.signature), - }; - default: - throw new Error("Unsupported pubkey type"); - } -} diff --git a/packages/sdk/src/signingcosmwasmclient.spec.ts b/packages/sdk/src/signingcosmwasmclient.spec.ts deleted file mode 100644 index 00fe65eb..00000000 --- a/packages/sdk/src/signingcosmwasmclient.spec.ts +++ /dev/null @@ -1,230 +0,0 @@ -import { Sha256 } from "@iov/crypto"; -import { Encoding } from "@iov/encoding"; -import { assert } from "@iov/utils"; - -import { Coin } from "./coins"; -import { PrivateCosmWasmClient } from "./cosmwasmclient"; -import { Secp256k1Pen } from "./pen"; -import { RestClient } from "./restclient"; -import { SigningCosmWasmClient, UploadMeta } from "./signingcosmwasmclient"; -import { getHackatom, makeRandomAddress, pendingWithoutWasmd } from "./testutils.spec"; - -const { toHex } = Encoding; - -const httpUrl = "http://localhost:1317"; - -const faucet = { - 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", - pubkey: { - type: "tendermint/PubKeySecp256k1", - value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", - }, - address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", -}; - -describe("SigningCosmWasmClient", () => { - describe("makeReadOnly", () => { - it("can be constructed", async () => { - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); - expect(client).toBeTruthy(); - }); - }); - - describe("getHeight", () => { - it("always uses authAccount implementation", async () => { - pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); - - const openedClient = (client as unknown) as PrivateCosmWasmClient; - const blockLatestSpy = spyOn(openedClient.restClient, "blocksLatest").and.callThrough(); - const authAccountsSpy = spyOn(openedClient.restClient, "authAccounts").and.callThrough(); - - const height = await client.getHeight(); - expect(height).toBeGreaterThan(0); - - expect(blockLatestSpy).toHaveBeenCalledTimes(0); - expect(authAccountsSpy).toHaveBeenCalledTimes(1); - }); - }); - - describe("upload", () => { - it("works", async () => { - pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); - const wasm = getHackatom(); - const { - codeId, - originalChecksum, - originalSize, - compressedChecksum, - compressedSize, - } = await client.upload(wasm); - expect(originalChecksum).toEqual(toHex(new Sha256(wasm).digest())); - expect(originalSize).toEqual(wasm.length); - expect(compressedChecksum).toMatch(/^[0-9a-f]{64}$/); - expect(compressedSize).toBeLessThan(wasm.length * 0.5); - expect(codeId).toBeGreaterThanOrEqual(1); - }); - - it("can set builder and source", async () => { - pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); - const wasm = getHackatom(); - - const meta: UploadMeta = { - source: "https://crates.io/api/v1/crates/cw-nameservice/0.1.0/download", - builder: "confio/cosmwasm-opt:0.6.2", - }; - const { codeId } = await client.upload(wasm, meta); - - const codeDetails = await client.getCodeDetails(codeId); - expect(codeDetails.source).toEqual(meta.source); - expect(codeDetails.builder).toEqual(meta.builder); - }); - }); - - describe("instantiate", () => { - it("works with transfer amount", async () => { - pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); - const { codeId } = await client.upload(getHackatom()); - - const transferAmount: readonly Coin[] = [ - { - amount: "1234", - denom: "ucosm", - }, - { - amount: "321", - denom: "ustake", - }, - ]; - const beneficiaryAddress = makeRandomAddress(); - const { contractAddress } = await client.instantiate( - codeId, - { - verifier: faucet.address, - beneficiary: beneficiaryAddress, - }, - "My cool label", - "Let's see if the memo is used", - transferAmount, - ); - - const rest = new RestClient(httpUrl); - const balance = (await rest.authAccounts(contractAddress)).result.value.coins; - expect(balance).toEqual(transferAmount); - }); - - it("can instantiate one code multiple times", async () => { - pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); - const { codeId } = await client.upload(getHackatom()); - - const contractAddress1 = await client.instantiate( - codeId, - { - verifier: faucet.address, - beneficiary: makeRandomAddress(), - }, - "contract 1", - ); - const contractAddress2 = await client.instantiate( - codeId, - { - verifier: faucet.address, - beneficiary: makeRandomAddress(), - }, - "contract 2", - ); - expect(contractAddress1).not.toEqual(contractAddress2); - }); - }); - - describe("execute", () => { - it("works", async () => { - pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); - const { codeId } = await client.upload(getHackatom()); - - // instantiate - const transferAmount: readonly Coin[] = [ - { - amount: "233444", - denom: "ucosm", - }, - { - amount: "5454", - denom: "ustake", - }, - ]; - const beneficiaryAddress = makeRandomAddress(); - const { contractAddress } = await client.instantiate( - codeId, - { - verifier: faucet.address, - beneficiary: beneficiaryAddress, - }, - "amazing random contract", - undefined, - transferAmount, - ); - - // execute - const result = await client.execute(contractAddress, { release: {} }, undefined); - const wasmEvent = result.logs.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 rest = new RestClient(httpUrl); - const beneficiaryBalance = (await rest.authAccounts(beneficiaryAddress)).result.value.coins; - expect(beneficiaryBalance).toEqual(transferAmount); - const contractBalance = (await rest.authAccounts(contractAddress)).result.value.coins; - expect(contractBalance).toEqual([]); - }); - }); - - describe("sendTokens", () => { - it("works", async () => { - pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); - - // instantiate - const transferAmount: readonly Coin[] = [ - { - amount: "7890", - denom: "ucosm", - }, - ]; - const beneficiaryAddress = makeRandomAddress(); - - // no tokens here - const before = await client.getAccount(beneficiaryAddress); - expect(before).toBeUndefined(); - - // send - const result = await client.sendTokens(beneficiaryAddress, transferAmount, "for dinner"); - const [firstLog] = result.logs; - expect(firstLog).toBeTruthy(); - - // got tokens - const after = await client.getAccount(beneficiaryAddress); - assert(after); - expect(after.balance).toEqual(transferAmount); - }); - }); -}); diff --git a/packages/sdk/src/signingcosmwasmclient.ts b/packages/sdk/src/signingcosmwasmclient.ts deleted file mode 100644 index e0daf56c..00000000 --- a/packages/sdk/src/signingcosmwasmclient.ts +++ /dev/null @@ -1,284 +0,0 @@ -import { Sha256 } from "@iov/crypto"; -import { Encoding } from "@iov/encoding"; -import pako from "pako"; - -import { isValidBuilder } from "./builder"; -import { Coin, coins } from "./coins"; -import { Account, CosmWasmClient, GetNonceResult, PostTxResult } from "./cosmwasmclient"; -import { makeSignBytes } from "./encoding"; -import { findAttribute, Log } from "./logs"; -import { BroadcastMode } from "./restclient"; -import { - MsgExecuteContract, - MsgInstantiateContract, - MsgSend, - MsgStoreCode, - StdFee, - StdSignature, -} from "./types"; - -export interface SigningCallback { - (signBytes: Uint8Array): Promise; -} - -export interface FeeTable { - readonly upload: StdFee; - readonly init: StdFee; - readonly exec: StdFee; - readonly send: StdFee; -} - -function prepareBuilder(buider: string | undefined): string { - if (buider === undefined) { - return ""; // normalization needed by backend - } else { - if (!isValidBuilder(buider)) throw new Error("The builder (Docker Hub image with tag) is not valid"); - return buider; - } -} - -const defaultFees: FeeTable = { - upload: { - amount: coins(25000, "ucosm"), - gas: "1000000", // one million - }, - init: { - amount: coins(12500, "ucosm"), - gas: "500000", // 500k - }, - exec: { - amount: coins(5000, "ucosm"), - gas: "200000", // 200k - }, - send: { - amount: coins(2000, "ucosm"), - gas: "80000", // 80k - }, -}; - -export interface UploadMeta { - /** The source URL */ - readonly source?: string; - /** The builder tag */ - readonly builder?: string; -} - -export interface UploadResult { - /** 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; - /** The ID of the code asigned by the chain */ - readonly codeId: number; - readonly logs: readonly Log[]; - /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ - readonly transactionHash: string; -} - -export interface InstantiateResult { - /** The address of the newly instantiated contract */ - readonly contractAddress: string; - readonly logs: readonly Log[]; - /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ - readonly transactionHash: string; -} - -export interface ExecuteResult { - readonly logs: readonly Log[]; - /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ - readonly transactionHash: string; -} - -export class SigningCosmWasmClient extends CosmWasmClient { - public readonly senderAddress: string; - - private readonly signCallback: SigningCallback; - private readonly fees: FeeTable; - - /** - * Creates a new client with signing capability to interact with a CosmWasm blockchain. This is the bigger brother of CosmWasmClient. - * - * This instance does a lot of caching. In order to benefit from that you should try to use one instance - * for the lifetime of your application. When switching backends, a new instance must be created. - * - * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) - * @param senderAddress The address that will sign and send transactions using this instance - * @param signCallback An asynchonous callback to create a signature for a given transaction. This can be implemented using secure key stores that require user interaction. - * @param customFees The fees that are paid for transactions - * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns - */ - public constructor( - apiUrl: string, - senderAddress: string, - signCallback: SigningCallback, - customFees?: Partial, - broadcastMode = BroadcastMode.Block, - ) { - super(apiUrl, broadcastMode); - this.anyValidAddress = senderAddress; - - this.senderAddress = senderAddress; - this.signCallback = signCallback; - this.fees = { ...defaultFees, ...(customFees || {}) }; - } - - public async getNonce(address?: string): Promise { - return super.getNonce(address || this.senderAddress); - } - - public async getAccount(address?: string): Promise { - return super.getAccount(address || this.senderAddress); - } - - /** Uploads code and returns a receipt, including the code ID */ - public async upload(wasmCode: Uint8Array, meta: UploadMeta = {}, memo = ""): Promise { - const source = meta.source || ""; - const builder = prepareBuilder(meta.builder); - - const compressed = pako.gzip(wasmCode, { level: 9 }); - const storeCodeMsg: MsgStoreCode = { - type: "wasm/store-code", - value: { - sender: this.senderAddress, - // eslint-disable-next-line @typescript-eslint/camelcase - wasm_byte_code: Encoding.toBase64(compressed), - source: source, - builder: builder, - }, - }; - const fee = this.fees.upload; - const { accountNumber, sequence } = await this.getNonce(); - const chainId = await this.getChainId(); - const signBytes = makeSignBytes([storeCodeMsg], fee, chainId, memo, accountNumber, sequence); - const signature = await this.signCallback(signBytes); - const signedTx = { - msg: [storeCodeMsg], - fee: fee, - memo: memo, - signatures: [signature], - }; - - const result = await this.postTx(signedTx); - const codeIdAttr = findAttribute(result.logs, "message", "code_id"); - return { - originalSize: wasmCode.length, - originalChecksum: Encoding.toHex(new Sha256(wasmCode).digest()), - compressedSize: compressed.length, - compressedChecksum: Encoding.toHex(new Sha256(compressed).digest()), - codeId: Number.parseInt(codeIdAttr.value, 10), - logs: result.logs, - transactionHash: result.transactionHash, - }; - } - - public async instantiate( - codeId: number, - initMsg: object, - label: string, - memo = "", - transferAmount?: readonly Coin[], - ): Promise { - const instantiateMsg: MsgInstantiateContract = { - type: "wasm/instantiate", - value: { - sender: this.senderAddress, - // eslint-disable-next-line @typescript-eslint/camelcase - code_id: codeId.toString(), - label: label, - // eslint-disable-next-line @typescript-eslint/camelcase - init_msg: initMsg, - // eslint-disable-next-line @typescript-eslint/camelcase - init_funds: transferAmount || [], - }, - }; - const fee = this.fees.init; - const { accountNumber, sequence } = await this.getNonce(); - const chainId = await this.getChainId(); - const signBytes = makeSignBytes([instantiateMsg], fee, chainId, memo, accountNumber, sequence); - - const signature = await this.signCallback(signBytes); - const signedTx = { - msg: [instantiateMsg], - fee: fee, - memo: memo, - signatures: [signature], - }; - - const result = await this.postTx(signedTx); - const contractAddressAttr = findAttribute(result.logs, "message", "contract_address"); - return { - contractAddress: contractAddressAttr.value, - logs: result.logs, - transactionHash: result.transactionHash, - }; - } - - public async execute( - contractAddress: string, - handleMsg: object, - memo = "", - transferAmount?: readonly Coin[], - ): Promise { - const executeMsg: MsgExecuteContract = { - type: "wasm/execute", - value: { - sender: this.senderAddress, - contract: contractAddress, - msg: handleMsg, - // eslint-disable-next-line @typescript-eslint/camelcase - sent_funds: transferAmount || [], - }, - }; - const fee = this.fees.exec; - const { accountNumber, sequence } = await this.getNonce(); - const chainId = await this.getChainId(); - const signBytes = makeSignBytes([executeMsg], fee, chainId, memo, accountNumber, sequence); - const signature = await this.signCallback(signBytes); - const signedTx = { - msg: [executeMsg], - fee: fee, - memo: memo, - signatures: [signature], - }; - - const result = await this.postTx(signedTx); - return { - logs: result.logs, - transactionHash: result.transactionHash, - }; - } - - public async sendTokens( - recipientAddress: string, - transferAmount: readonly Coin[], - memo = "", - ): Promise { - const sendMsg: MsgSend = { - type: "cosmos-sdk/MsgSend", - value: { - // eslint-disable-next-line @typescript-eslint/camelcase - from_address: this.senderAddress, - // eslint-disable-next-line @typescript-eslint/camelcase - to_address: recipientAddress, - amount: transferAmount, - }, - }; - const fee = this.fees.send; - const { accountNumber, sequence } = await this.getNonce(); - const chainId = await this.getChainId(); - const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); - const signature = await this.signCallback(signBytes); - const signedTx = { - msg: [sendMsg], - fee: fee, - memo: memo, - signatures: [signature], - }; - - return this.postTx(signedTx); - } -} diff --git a/packages/sdk/src/testdata/contract.json b/packages/sdk/src/testdata/contract.json deleted file mode 100644 index 1b75f593..00000000 --- a/packages/sdk/src/testdata/contract.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "// source": "https://github.com/CosmWasm/cosmwasm/blob/v0.8.0-alpha3/packages/vm/testdata/contract_0.8.wasm", - "data": "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RQ0FIAMtAIEBIQIMAgsgDUUEQCADQeABakGagsAAQQcQDiADQewBaigCACEGIANB6AFqKAIAIQQgAygC5AEhBSADKALgASECDAgLIANB4AFqIAgQdiADKALgASICQRZGDQsgAygC7AEhBiADKALoASEEIAMoAuQBIQUgEkUEQEEAIQcMIQsgDRDAAUEAIQcMIAtBACEBIANBADoA3AELIAJB/wFxIgRBIkcEQEETQRAgBEH9AEYbIQIMBAsgA0H4AGogBxB5IAMtAHhBAXFFDQIgAy0AeUEiRwRAQQ4hAgwECyAHEHMgA0HgAWogBxB4IAMoAuwBIQQgAygC6AEhBSADKALkASECIAMoAuABIgxBAUYEQCADKALwASEGDAQLAkAgBEEHRgRAQQAhCiACQZqCwABGDQEgAkGagsAAQQcQjwJFDQELQQEhCgsgBQRAIAIQwAELIARFIAxFIAJBFEdyckUEQCAFEMABCwJAAkACQCAKBEAgA0HgAWogBxB3IAMoAuABIgJBFkYNASADKALoASEEIAMoAuQBIQUgA0GMAmogAygC7AEiBjYCACADQYgCaiAENgIAIAMgBTYChAIgAyACNgKAAgwHCyANRQRAIANB4AFqIANB2AFqEBIgAygC4AFBAUcNAiADQfABaigCACEGIAMoAuwBIQQgAygC6AEhBSADKALkASECQQAhBwwiCyADQeABakGagsAAQQcQECADQewBaigCACEGIANB6AFqKAIAIQQgAygC5AEhBSADKALgASECDAcLIANBgAJqIAcQESADKAKAAiICQRZGDQEgA0GMAmooAgAhBiADQYgCaigCACEEIAMoAoQCIQUMBQsgAygC7AEhHCADKALoASESIAMoAuQBIQ0LIANB8ABqIAcQeUEAIQUgAy0AcSECIAMtAHBBAXENAAtBAiECDAILIANB4AFqQY2DwABBCBAQIANB7AFqKAIAIQYgA0HoAWooAgAMEgtBBCECCyANRQ0BCyASRQ0AIA0QwAFBACEHDBkLQQAhBwwYCyAcIQQgEgshBSABIQYMFgsgCw0CIANB4AFqIAgQdyADKALgASICQRZHDRAgA0HoAGogCBB5IAMtAGhBAXFFBEBBBCECDAoLIAMtAGlB+wBHBEBBDiECDAoLIAgQcyADQeAAaiAIEHEgAygCYCEBIAMgAy0AZEEBcSIOOgDcASADIAE2AtgBIANB2ABqIAEQeSADLQBYQQFxRQRAQQIhAgwJCyADLQBZIQJBACELIA4hBUEAIRACQANAAkACQAJAIAJB/wFxIgRBLEcEQCAEQf0ARg0BIA4NAkEJIQIMCgsgBUH/AXENASABEHMgA0HQAGogARB5IAMtAFBBAXFFDQggAy0AUSECDAILIAtFBEAgA0HgAWpBp4PAAEEGEA4gA0HsAWooAgAhBiADQegBaigCACEEIAMoAuQBIQUgAygC4AEhAkEADAoLIBBFBEAgA0HgAWpBrYPAAEEKEA4gA0HsAWooAgAhBiADQegBaigCACEEIAMoAuQBIQUgAygC4AEhAiAXRQ0NIAsQwAEMDQsgA0HgAWogCBB2IAMoAuABIgJBFkcEQCADKALsASEGIAMoAugBIQQgAygC5AEhBSAXBEAgCxDAAQsgGARAIBhBBXQhByAQQRBqIQEDQCABQQRqKAIABEAgASgCABDAAQsgAUEgaiEBIAdBYGoiBw0ACwsgDEUNDSAQEMABDA0LIBghESAMIRUgECEPIBchFAwEC0EAIQ4gA0EAOgDcAQsgAkH/AXEiBEEiRwRAQRAhAiAEQf0ARw0HQRMhAgwHCyADQcgAaiABEHkgAy0ASEEBcUUNBSADLQBJQSJHBEBBDiECDAcLIAEQcyADQeABaiABEHggAygC7AEhBCADKALoASEFIAMoAuQBIQIgAygC4AEiB0EBRgRAIAMoAvABIQYMBwsCQAJAIARBemoiCkEESw0AAkACQCAKQQFrDgQCAgIBAAtBACEKIAJBp4PAAEYNAiACQaeDwABBBhCPAkUNAgwBC0EBIQogAkGtg8AARg0BIAJBrYPAAEEKEI8CRQ0BC0ECIQoLIAUEQCACEMABCyAERSAHRSACQRRHcnJFBEAgBRDAAQsCQAJAAkACQAJAIApBAU0EQCAKQQFrDQEMAgsgA0HgAWogARB3IAMoAuABIgJBFkYNAiADKALoASEEIAMoAuQBIQUgA0GMAmogAygC7AEiBjYCACADQYgCaiAENgIAIAMgBTYChAIgAyACNgKAAgwLCyALRQRAIANB4AFqIANB2AFqEBIgAygC4AFBAUcNAyADQfABaigCACEGIAMoAuwBIQQgAygC6AEhBSADKALkASECQQAMDAsgA0HgAWpBp4PAAEEGEBAgA0HsAWooAgAhBiADQegBaigCACEEIAMoAuQBIQUgAygC4AEhAgwKCyAQDQQgA0HgAWogA0HYAWoQEyADKALgAUEBRwRAIAMoAuwBIRggAygC6AEhDCADKALkASEQDAMLIANB8AFqKAIAIQYgAygC7AEhBCADKALoASEFIAMoAuQBIQIgCyEJDAwLIANBgAJqIAEQESADKAKAAiICQRZGDQEgA0GMAmooAgAhBiADQYgCaigCACEEIAMoAoQCIQUMCAsgAygC7AEhHSADKALoASEXIAMoAuQBIQsLIANBQGsgARB5QQAhBSADLQBBIQIgAy0AQEEBcQ0AC0ECIQIMBQsgA0HgAWpBrYPAAEEKEBAgA0HsAWooAgAhBiADQegBaigCACEEIAMoAuQBIQUgAygC4AEhAiALIQkMBgsgAyAIEHlBACEHIAMtAAEhAiADLQAAQQFxDQALQQIhAgwRCyADQeABakGGg8AAQQcQECADQewBaigCACEGIANB6AFqKAIADAgLQQQhAgsgCwshCSAQRQ0BCyAYBEAgGEEFdCEHIBBBEGohAQNAIAFBBGooAgAEQCABKAIAEMABCyABQSBqIQEgB0FgaiIHDQALCyAMRQ0AIBAQwAELIAlFIBdFcg0AIAsQwAELQQAhC0EAIQcMCgsgFyEFIB0hBCAQIQYMCQsgA0HgAWpBgYPAAEEFEBAgA0HsAWooAgAhBiADQegBaigCAAshBCADKALkASEFIAMoAuABIQIMBwsgA0HsAWooAgAhBiADQegBaigCACEEIAMoAuQBIQUgAygC4AEhAgwBC0EEIQILIAxFIAlFcg0EIAkQwAEMBAsgEyEFIBkhBAwDCyADKALsASEGIAMoAugBIQQgAygC5AEhBQwCC0EEIQIMAQtBECECC0EACyEOIBJFIA1Fcg0AIA0QwAELAkAgByALRXINACAUBEAgCxDAAQsgEQRAIBFBBXQhByAPQRBqIQEDQCABQQRqKAIABEAgASgCABDAAQsgAUEgaiEBIAdBYGoiBw0ACwsgFUUNACAPEMABCyAWRSAOIBpFcnINACAaEMABCyADQYwCaiAGNgIAIANBiAJqIAQ2AgAgAyAFNgKEAiADIAI2AoACIANB4AFqQduFwABBGCADQYACahAUIABBHGogA0H4AWooAgA2AgAgAEEUaiADQfAB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ASACKAKsASEFIAJBhAFqIAIoArQBIgo2AgAgAkGAAWogATYCACACIAU2AnwgAiAENgJ4DA4LIANFBEAgAkGoAWogBhB3IAIoAqgBIgNBFkcEQCACKAK0ASEKIAIoArABIQkgAigCrAEhCAwQCyACQRBqIAYQeSACLQAQQQFxRQRAQQQhAwwMCyACLQARQSJHBEBBDiEDDAwLIAYQcyACQagBaiAGEHggAigCtAEhESACKAKwASEOIAIoAqwBIQMgAigCqAEiAUEBRgRAIAIoArgBIQogDiEIIBEhCQwQCyARRSABRSADQRRHcnINBSAOEMABDAULIAJBqAFqQYCCwABBBRAQDAMLIBZCAVIEQCACQagBaiAGEHcgAigCqAEiBEEWRwRAIAIoArQBIQogAigCsAEhASACKAKsASEFDA4LIAJBGGogBhB5IAItABhBAXFFBEBBBCEEDAMLIAItABlBIkcEQEEOIQQMAwsgBhBzIAJBqAFqIAYQeCACKAK0ASEBIAIoArABIQUgAigCrAEhBCACKAKoASIQQQFGBEAgAigCuAEhCgwOCyACIAE2AnQgAiAENgJwIAJB+ABqIAQgARCCAgJ/IAItAHhBAUcEQEEAIQcgAigCjAEhFSACKAKIASEKIAIoAoABIQggAigChAEMAQsgAiACLQB5OgCXASACQQI2ArwBIAJCAjcCrAEgAkGojcAANgKoASACQQM2AswBIAJBAjYCxAEgAiACQcABajYCuAEgAiACQZcBajYCyAEgAiACQfAAajYCwAEgAkGYAWogAkGoAWoQyQEgAkGoAWogAkGYAWoQygEgAigCnAEEQCACKAKYARDAAQsgAigCqAEhCCACKAKwASEKQQEhByACKAKsAQshCSAFBEAgBBDAAQsgAUUgEEUgBEEUR3JyRQRAIAUQwAELQgEhFiAHRQ0EQRQhBAwCCyACQagBakGhgMAAQQYQEAwCCyACQfgAaiAGEBEgAigCeCIEQRZGDQIgAkGEAWooAgAhCiACQYABaigCACEBIAIoAnwhBQwLCyAIIQUgCSEBDAoLIAJBtAFqKAIAIQogAkGwAWooAgAhASACKAKsASEFIAIoAqgBIQQMCQsgAkEIaiAGEHlBACEFIAItAAkhASACLQAIQQFxDQALC0ECIQcMBQsgAkGoAWogEhB1IAIoAqgBIgNB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AUEIaikCADcDACACIAEpAgA3AzgMBAsgAkHQAGogAkGIAWpBz4LAAEELEIUBIAIoAlBBAUYEQCACQUBrIAJB3ABqKQIANwMAIAIgAikCVDcDOAwECyACIAIoAlQ2AnggAiACQdgAai0AADoAfCACQdAAaiACQfgAakHagsAAQQkgAUEIahAFIAJB0ABqIQMCQCACKAJQQQNGBEAgAkHQAGogAkH4AGpBp4HAACABQRRqEAYgAigCUEEDRg0BCyACQUBrIANBCGopAgA3AwAgAiADKQIANwM4DAQLIAJBOGogAigCeCACLQB8EH0MAwsgAkHQAGogAkGIAWpBxYLAAEEKEIUBIAIoAlBBAUcNASACQUBrIAJB0ABqQQRyIgFBCGopAgA3AwAgAiABKQIANwM4DAILIAJBOGogAigCVCACQdgAai0AABB9DAELIAJBOGogAigCVCACQdgAai0AABB9CyACKAI4IgNBA0cEQCACQUBrKAIAIQEgAigCPCEEIAJB3ABqIAJBxABqKAIANgIAIAJB2ABqIAE2AgAgAiAENgJUIAIgAzYCUAwECwJAIAIoApABIgMgAigCjAFHBEAgAigCiAEhAQwBCyADQQFqIgEgA0kNBSADQQF0IgQgASAEIAFLGyIEQQBIDQUCfyADRQRAIARBARBADAELIAIoAogBIANBASAEEEELIgFFDQsgAiAENgKMASACIAE2AogBIAIoApABIQMLIAEgA2pB/QA6AABBAyEDIAJBAzYCUCACIAIoApABQQFqNgKQAQwDCwJAIAIoApABIgMgAigCjAFHBEAgAigCiAEhBAwBCyADQQFqIgQgA0kNBCADQQF0IgUgBCAFIARLGyIFQQBIDQQCfyADRQRAIAVBARBADAELIAIoAogBIANBASAFEEELIgRFDQkgAiAFNgKMASACIAQ2AogBCyADIARqQfsAOgAAIAIgAigCkAFBAWo2ApABIAJB0ABqIAJBiAFqQdyAwABBBBCBASACKAJQIgNBA0cEQCACQdgAaiIBKAIAIQQgAigCVCEFIAJB3ABqIgYgBigCADYCACABIAQ2AgAgAiAFNgJUIAIgAzYCUAwDCwJAIAIoApABIgMgAigCjAFHBEAgAigCiAEhBAwBCyADQQFqIgQgA0kNBCADQQF0IgUgBCAFIARLGyIFQQBIDQQCfyADRQRAIAVBARBADAELIAIoAogBIANBASAFEEELIgRFDQkgAiAFNgKMASACIAQ2AogBIAIoApABIQMLIAMgBGpBOjoAACACIAIoApABQQFqNgKQASABQRRqIQMgAUEIaiEEAkACQCABKAIEQQFHBEAgAkHQAGogAkGIAWpBtILAAEEFEIUBIAIoAlBBAUYEQCACQUBrIAJB3ABqKQIANwMAIAIgAikCVDcDOAwDCyACIAIoAlQ2AnggAiACQdgAai0AADoAfCACQdAAaiACQfgAakHPgMAAQQ0gBBAFIAIoAlBBA0YEQCACQdAAaiACQfgAakHAgMAAIAMQCSACKAJQQQNGDQILIAJBQGsgAkHQAGoiAUEIaikCADcDACACIAEpAgA3AzgMAgsgAkHQAGogAkGIAWpBsYLAAEEDEIUBIAIoAlBBAUYEQCACQUBrIAJB3ABqKQIANwMAIAIgAikCVDcDOAwCCyACIAIoAlQ2AnggAiACQdgAai0AADoAfCACQdAAaiACQfgAakHPgMAAQQ0gBBAFIAJB0ABqIQECQCACKAJQQQNGBEAgAkHQAGogAkH4AGpB5oHAACADEAkgAigCUEEDRg0BCyACQUBrIAFBCGopAgA3AwAgAiABKQIANwM4DAILIAJBOGogAigCeCACLQB8EH0MAQsgAkE4aiACKAJ4IAItAHwQfQsgAigCOCIDQQNHBEAgAkFAaygCACEBIAIoAjwhBCACQdwAaiACQcQAaigCADYCACACQdgAaiABNgIAIAIgBDYCVCACIAM2AlAMAwsCQCACKAKQASIDIAIoAowBRwRAIAIoAogBIQEMAQsgA0EBaiIBIANJDQQgA0EBdCIEIAEgBCABSxsiBEEASA0EAn8gA0UEQCAEQQEQQAwBCyACKAKIASADQQEgBBBBCyIBRQ0KIAIgBDYCjAEgAiABNgKIASACKAKQASEDCyABIANqQf0AOgAAQQMhAyACQQM2AlAgAiACKAKQAUEBajYCkAEMAgsgAkE4aiACKAJ4IAItAHwQfQsgAigCOCIDQQNHBEAgAkFAaygCACEBIAIoAjwhBCACQdwAaiACQcQAaigCADYCACACQdgAaiABNgIAIAIgBDYCVCACIAM2AlAMAQsCQCACKAKQASIDIAIoAowBRwRAIAIoAogBIQEMAQsgA0EBaiIBIANJDQIgA0EBdCIEIAEgBCABSxsiBEEASA0CAn8gA0UEQCAEQQEQQAwBCyACKAKIASADQQEgBBBBCyIBRQ0IIAIgBDYCjAEgAiABNgKIASACKAKQASEDCyABIANqQf0AOgAAQQMhAyACQQM2AlAgAiACKAKQAUEBajYCkAELIANBA0cEQCACQdwAaigCACEBIAJB2ABqKAIAIQcgAigCVCEEIAIoAowBBEAgAigCiAEQwAELIAJB3ABqIAE2AgAgAkHYAGoiASAHNgIAIAIgBDYCVCACIAM2AlAgAkGgAWpB+IbAAEE9IAJB0ABqEBYgAkHoAGogAkG4AWooAgA2AgAgAkHgAGogAkGwAWopAgA3AwAgASACQagBaikCADcDACACIAIpAqABNwNQIAJBzABqQQE2AgAgAkIBNwI8IAJB4ITAADYCOCACQQY2AnwgAiACQfgAajYCSCACIAJB0ABqNgJ4IAJBiAFqIAJBOGoQyQEQjgEhASACQcIAaiACQZABaigCADYBACACIAIpA4gBNwE6IAAgAToABSAAQQA6AAQgACACKQE4NwEGIABBDGogAkE+aikBADcBACAAQQE2AgAgAkHQAGoQHAwFCyACKAKMASENIAJB0ABqIAIoAogBIg4gAigCkAEQZiACKAJQIg9BAUcNASACQUBrIgEgAkHcAGopAgA3AwAgAiACKQJUNwM4IAJBtAFqQQE2AgAgAkIBNwKkASACQYCFwAA2AqABIAJBBzYCfCACIAJB+ABqNgKwASACIAJBOGo2AnggAkGIAWogAkGgAWoQyQEQjgEhByACQaoBaiACQZABaigCADYBACACIAIpA4gBNwGiASAAIAc6AAUgAEEAOgAEIAAgAikBoAE3AQYgAEEMaiACQaYBaikBADcBACAAQQE2AgACQCACKAI4IgBBAk0EQAJAAkAgAEEBaw4CAQMACyACQUBrKAIARQ0CIAIoAjwQwAEMAgsgAkFAaygCAEUNASACKAI8EMABDAELIAEoAgBFDQAgAigCPBDAAQtBASEDQQAMAgsQyAEACyACKAJUQQFGBEBBASEFIABBATYCACAAQRxqIAJB8ABqKAIANgIAIABBFGogAkHoAGopAwA3AgAgAEEMaiACQeAAaikDADcCACAAIAJB2ABqKQMANwIEQQEhA0EADAILIAJBgAFqIAJB4ABqKAIA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ASACKAIAQQNGDQIgACACKQMANwIAIABBCGogAkEIaikDADcCACACQRBqJAAPCyAEQQEQxwEACxDIAQALAkAgAigCAEEBRw0AIAJBCGooAgBFDQAgAigCBBDAAQsgARAoAAv/AQEBfwJAAkAgAC0AACIBQQdNBEACQAJAAkACQAJAAkAgAUEBaw4HAQIDBwQFBwALIABBCGooAgBFDQYMBwsgAEEIaigCAEUNBQwGCyAAQQhqKAIARQ0EDAULIABBCGooAgBFDQMMBAsgAEEIaigCAARAIABBBGooAgAQwAELIABBFGooAgBFDQIgAEEQaigCABDAAQ8LIABBCGooAgAEQCAAQQRqKAIAEMABCyAAQRRqKAIARQ0BIABBEGooAgAQwAEMAQsgAEEIaigCAARAIABBBGooAgAQwAELIABBFGooAgBFDQAgAEEQaigCABDAAQsPCyAAQQRqKAIAEMABCwwAQuSuwoWXm6WIEQszAQF/IwBBEGsiACQAIABBHzYCDCAAQZCPwAA2AgggAEEIakGIhcAAQQBBgI/AABC1AQALEQAgACgCACkDAEEBIAEQ9AELDAAgACgCACABEPUBCxEAIAAoAgAgACgCBCABEP8BCwMAAQvDAQEBfwJAIAAoAgAiAUECTQRAAkACQCABQQFrDgIDAQALIAAoAgQgAEEMaigCAARAIABBCGooAgAQwAELDQIgAEEYaigCAEUNAiAAQRRqKAIAEMABDwsgACgCBEEBRw0BIABBDGooAgAEQCAAQQhqKAIAEMABCyAAQRRqKAIAIgFFDQEgAEEYaigCAEUNASABEMABDAELIABBDGooAgAEQCAAQQhqKAIAEMABCyAAQRhqKAIARQ0AIABBFGooAgAQwAELC/sFAQV/IAAoAggiAQRAIAAoAgAiBCABQcgAbGohBQNAIAQiAUHIAGohBAJAIAEoAgAiAkECTQRAAkACQCACQQFrDgIDAQALIAFBCGooAgAEQCABKAIEEMABCyABQRRqKAIABEAgAUEQaigCABDAAQsgAUEkaigCACICBEAgAkEFdCECIAFBHGooAgBBEGohAwNAIANBBGooAgAEQCADKAIAEMABCyADQSBqIQMgAkFgaiICDQALCyABQSBqKAIARQ0CIAEoAhwQwAEMAgsgAUEIaigCACICQQJNBEACQAJAAkAgAkEBaw4CAQIACyABQRBqKAIABEAgAUEMaigCABDAAQsgAUEsaigCAEUNBCABQShqKAIAEMABDAQLIAFBEGooAgAEQCABQQxqKAIAEMABCyABQSxqKAIARQ0DIAFBKGooAgAQwAEMAwsgAUEQaigCAARAIAFBDGooAgAQwAELIAFBGGooAgAiAkUNAiABQRxqKAIARQ0CIAIQwAEMAgsgAUEQaigCAARAIAFBDGooAgAQwAELIAFBHGooAgAEQCABQRhqKAIAEMABCyABQTxqKAIARQ0BIAFBOGooAgAQwAEMAQsgAUEQaigCACECIAFBCGooAgBFBEAgAgRAIAEoAgwQwAELIAFBHGooAgAEQCABQRhqKAIAEMABCyABQSxqKAIAIgIEQCACQQV0IQIgAUEkaigCAEEQaiEDA0AgA0EEaigCAARAIAMoAgAQwAELIANBIGohAyACQWBqIgINAAsLIAFBKGooAgBFDQEgASgCJBDAAQwBCyACBEAgASgCDBDAAQsgAUEgaigCACICBEAgAkEFdCECIAFBGGooAgBBEGohAwNAIANBBGooAgAEQCADKAIAEMABCyADQSBqIQMgAkFgaiICDQALCyABQRxqKAIABEAgASgCGBDAAQsgAUEkaigCACICRQ0AIAFBKGooAgBFDQAgAhDAAQsgBCAFRw0ACwsgAEEEaigCAARAIAAoAgAQwAELC74EAQR/IwBBEGsiBCQAIAAoAgAhAAJAAkACQAJAAkACQCABQYABTwRAIARBADYCDCABQYAQSQ0BIAFBgIAESQRAIAQgAUE/cUGAAXI6AA4gBCABQQZ2QT9xQYABcjoADSAEIAFBDHZBD3FB4AFyOgAMQQMhAQwECyAEIAFBP3FBgAFyOgAPIAQgAUESdkHwAXI6AAwgBCABQQZ2QT9xQYABcjoADiAEIAFBDHZBP3FBgAFyOgANQQQhAQwDCwJAIAAoAggiAiAAQQRqKAIARwRAIAAoAgAhAwwBCyACQQFqIgMgAkkNBiACQQF0IgUgAyAFIANLGyIFQQBIDQYCfyACRQRAIAVBARBADAELIAAoAgAgAkEBIAUQQQsiA0UNAiAAIAM2AgAgAEEEaiAFNgIAIAAoAgghAgsgAiADaiABOgAAIAAgACgCCEEBajYCCAwDCyAEIAFBP3FBgAFyOgANIAQgAUEGdkEfcUHAAXI6AAxBAiEBDAELIAVBARDHAQALAkAgAEEEaigCACIDIABBCGooAgAiAmsgAU8EQCAAKAIAIQMMAQsgASACaiIFIAJJDQMgA0EBdCICIAUgAiAFSxsiAkEASA0DAn8gA0UEQCACQQEQQAwBCyAAKAIAIANBASACEEELIgNFDQIgACADNgIAIABBBGogAjYCACAAQQhqKAIAIQILIABBCGogASACajYCACACIANqIARBDGogARCOAhoLIARBEGokAEEADwsgAkEBEMcBAAsQyAEAC1oBAX8jAEEgayICJAAgAiAAKAIANgIEIAJBGGogAUEQaikCADcDACACQRBqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpBnIrAACACQQhqENUBIAJBIGokAAu/AQEDfwJAAkACQCAAKAIAIgBBBGooAgAiBCAAQQhqKAIAIgNrIAJPBEAgACgCACEEDAELIAIgA2oiBSADSQ0CIARBAXQiAyAFIAMgBUsbIgNBAEgNAgJ/IARFBEAgA0EBEEAMAQsgACgCACAEQQEgAxBBCyIERQ0BIAAgBDYCACAAQQRqIAM2AgAgAEEIaigCACEDCyAAQQhqIAIgA2o2AgAgAyAEaiABIAIQjgIaQQAPCyADQQEQxwEACxDIAQALoAEBA38CQAJAIAAoAggiASAAQQRqKAIARgR/IAFBAWoiAiABSQ0CIAFBAXQiAyACIAMgAksbIgJBAEgNAgJ/IAFFBEAgAkEBEEAMAQsgACgCACABQQEgAhBBCyIBRQ0BIAAgATYCACAAQQRqIAI2AgAgACgCCAUgAQsgACgCAGpBOjoAACAAIAAoAghBAWo2AggPCyACQQEQxwEACxDIAQALxwICAn8BfiMAQUBqIgIkACACIAE2AgQgAkEANgIQIAJCATcDCCACQQg2AhwgAiACQQRqNgIYIAIgAkEIajYCJCACQTxqQQE2AgAgAkIBNwIsIAJBnIXAADYCKCACIAJBGGo2AjgCQAJAIAJBJGpBnIrAACACQShqENUBRQRAIAIoAgwiAyACKAIQIgFHBEAgAyABSQ0CAkAgAUUEQCADBEAgAigCCBDAAQsgAkEBNgIIQQAhAQwBCyACKAIIIANBASABEEEiA0UNBCACIAM2AggLIAIgATYCDAsgAkEwaiACQRBqKAIAIgE2AgAgAiACKQMIIgQ3AyggAEEMaiABNgIAIAAgBDcCBCAAQRQ2AgAgAkFAayQADwtBpIXAAEE3IAJBKGpBtIrAABDfAQALQcyLwABBJEG8i8AAEM0BAAsgAUEBEMcBAAu5AQEBfyMAQUBqIgUkACAFIAI2AgwgBSABNgIIAkAgBARAIAVBNGpBCTYCACAFQSRqQQI2AgAgBUICNwIUIAVB/IzAADYCECAFQQI2AiwgBSAENgI8IAUgAzYCOCAFIAVBKGo2AiAgBSAFQThqNgIwDAELIAVBJGpBATYCACAFQgI3AhQgBUHgjMAANgIQIAVBAjYCLCAFIAVBKGo2AiALIAUgBUEIajYCKCAAIAVBEGoQKSAFQUBrJAAL9wIBBH8jAEEgayICJAAgAkEIaiABKAIAEHkCQAJAAkAgAi0ACEEBcUUEQEECIQMMAQsCQAJAIAItAAkiBCIDQf0ARwRAAkAgA0EsRgRAIAEtAARFDQEMAwsgAS0ABA0CQQkhAwwECyABKAIAEHMgAiABKAIAEHkgAi0AAEEBcUUEQEEEIQMMBAsgAi0AASEEDAILIABBADsBAAwECyABQQA6AAQLIARB/wFxIgNB/QBHBEAgA0EiRwRAQRAhAwwCCyACQRBqIAEoAgAQLCACKAIQIgNBFkYNAiACQRxqKAIAIQEgAkEYaigCACEEIAIoAhQhBQwBC0ETIQMLIABBAToAACAAQRBqIAE2AgAgAEEMaiAENgIAIABBCGogBTYCACAAQQRqIAM2AgAMAQsgAkEQaiABEC0gAigCEEEWRwRAIABBAToAACAAQQRqIAIpAxA3AgAgAEEMaiACQRhqKQMANwIADAELIABBgAI7AQALIAJBIGokAAvNAQEEfyMAQSBrIgIkACACIAEQeQJAIAItAABBAXEEQCACLQABQSJHBEAgAEEONgIADAILIAEQcyACQQhqIAEQeCACQRRqKAIAIQUgAkEQaigCACEBIAIoAgwhAyACKAIIIgRBAUYEQCACQRhqKAIAIQQgACABNgIEIAAgAzYCACAAQQxqIAQ2AgAgAEEIaiAFNgIADAILIABBFjYCACABBEAgAxDAAQsgBUUgBEUgA0EUR3JyDQEgARDAAQwBCyAAQQQ2AgALIAJBIGokAAtxAQN/IwBBEGsiAiQAIAIgASgCABB3AkAgAigCACIDQRZHBEAgAkEMaigCACEBIAJBCGooAgAhBCAAIAIoAgQ2AgQgACADNgIAIABBDGogATYCACAAQQhqIAQ2AgAMAQsgACABKAIAEBELIAJBEGokAAvKAgEDfyMAQSBrIgIkACACQQhqIAEoAgAQeUEBIQMCQAJAAkAgAi0ACEEBcQRAAkACQCACLQAJIgMiBEEsRwRAIARB3QBHDQFBACEDIABBADoAAQwGCyABKAIAEHMgAiABKAIAEHlBASEDIAItAABBAXFFDQMgAi0AASEDDAELIAEtAARFBEAgAEEEakEHNgIAQQEhAwwFCyABQQA6AAQLIANB3QBGDQIgAkEQaiABKAIAEBEgAigCECIBQRZHBEAgAkEYaigCACEDIAIoAhQhBCAAQRBqIAJBHGooAgA2AgAgAEEMaiADNgIAIABBCGogBDYCACAAQQRqIAE2AgBBASEDDAQLIABBAToAAUEAIQMMAwsgAEEEakEBNgIADAILIABBBGpBBDYCAAwBCyAAQQRqQRM2AgBBASEDCyAAIAM6AAAgAkEgaiQAC4wDAQV/IwBBIGsiAiQAIAIgARB5AkACQAJAIAItAABBAXEEQCACLQABQSJHBEAgAEEBOgAAIABBBGpBDjYCAAwECyABEHMgAkEIaiABEHggAkEUaigCACEDIAJBEGooAgAhBCACKAIMIQEgAigCCCIFQQFGBEAgAkEYaigCACEFIABBAToAACAAQRBqIAU2AgAgAEEMaiADNgIAIABBCGogBDYCACAAQQRqIAE2AgAMBAsgA0F4aiIGQQVLDQECQAJAIAZBAWsOBQMDAwMBAAsgAUGvj8AARwRAIAEpAABC9srJy+as2rLyAFINAwsgAEEAOwEADAMLIAFBtJDAAEcEQCABQbSQwABBDRCPAg0CCyAAQYACOwEADAILIABBAToAACAAQQRqQQQ2AgAMAgsgAkEIaiABIANBxJDAAEECECogAEEBOgAAIABBDGogAkEQaikDADcCACAAQQRqIAIpAwg3AgALIAQEQCABEMABCyADRSAFRSABQRRHcnINACAEEMABCyACQSBqJAALrwQBBX8jAEEgayICJAAgAiABEHkCQAJAAkAgAi0AAEEBcQRAIAItAAFBIkcEQCAAQQE6AAAgAEEEakEONgIADAQLIAEQcyACQQhqIAEQeCACQRRqKAIAIQMgAkEQaigCACEEIAIoAgwhASACKAIIIgVBAUYEQCACQRhqKAIAIQUgAEEBOgAAIABBEGogBTYCACAAQQxqIAM2AgAgAEEIaiAENgIAIABBBGogATYCAAwECyADQXtqIgZBEEsNAQJAAkACQAJAAkACQCAGQQFrDhAHAAEHBwMCBwcHBwcHBwcEBQsgAUHsjcAARwRAIAFB7I3AAEEHEI8CDQcLIABBADsBAAwHCyABQciPwABHBEAgASkAAELj4NX7xe3bt/AAUg0GCyAAQYACOwEADAYLIAFB0I/AAEcEQCABQdCPwABBDBCPAg0FCyAAQYAEOwEADAULIAFB3I/AAEcEQCABQdyPwABBCxCPAg0ECyAAQYAGOwEADAQLIAFB54/AAEcEQCABQeePwABBFRCPAg0DCyAAQYAIOwEADAMLIAFB/I/AAEcEQCABQfyPwABBBRCPAg0CCyAAQYAKOwEADAILIABBAToAACAAQQRqQQQ2AgAMAgsgAkEIaiABIANBhJDAAEEGECogAEEBOgAAIABBDGogAkEQaikDADcCACAAQQRqIAIpAwg3AgALIAQEQCABEMABCyADRSAFRSABQRRHcnINACAEEMABCyACQSBqJAAL4xABBH8jAEEwayIDJAACQAJAAkACQAJAAkACQAJAAkAgASgCCCIEIAFBBGooAgBHBEAgASgCACEFDAELIARBAWoiBSAESQ0HIARBAXQiBiAFIAYgBUsbIgZBAEgNBwJ/IARFBEAgBkEBEEAMAQsgASgCACAEQQEgBhBBCyIFRQ0BIAEgBTYCACABQQRqIAY2AgAgASgCCCEECyAEIAVqQfsAOgAAIAEgASgCCEEBajYCCCADQRhqIAFBjI3AAEEDEIEBIAMoAhgiBEEDRwRAIANBJGooAgAhASADQSBqKAIAIQIgACADKAIcNgIEIAAgBDYCACAAQQxqIAE2AgAgAEEIaiACNgIADAgLAkAgASgCCCIEIAFBBGooAgBHBEAgASgCACEFDAELIARBAWoiBSAESQ0HIARBAXQiBiAFIAYgBUsbIgZBAEgNBwJ/IARFBEAgBkEBEEAMAQsgASgCACAEQQEgBhBBCyIFRQ0CIAEgBTYCACABQQRqIAY2AgAgASgCCCEECyAEIAVqQTo6AAAgASABKAIIQQFqNgIIAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAi0AAEEBaw4IAQIDBAUGBwgACyADQRhqIAFBuoTAAEELEIUBIAMoAhhBAUcNCCADQQhqIANBJGopAgA3AwAgAyADKQIcNwMADBQLIANBGGogAUGshMAAQQ4QhQEgAygCGEEBRw0IIANBCGogA0EkaikCADcDACADIAMpAhw3AwAMEwsgA0EYaiABQaCEwABBDBCFASADKAIYQQFHDQggA0EIaiADQSRqKQIANwMAIAMgAykCHDcDAAwSCyADQRhqIAFBk4TAAEEJEIUBIAMoAhhBAUcNCCADQQhqIANBJGopAgA3AwAgAyADKQIcNwMADBELIANBGGogAUGHhMAAQQwQhQEgAygCGEEBRw0IIANBCGogA0EYakEEciICQQhqKQIANwMAIAMgAikCADcDAAwQCyADQRhqIAFB+IPAAEEJEIUBIAMoAhhBAUYEQCADQQhqIANBJGopAgA3AwAgAyADKQIcNwMADBALIAMgAygCHDYCECADIANBIGotAAA6ABQgA0EYaiADQRBqQYGEwABBBiACQQRqEAUgA0EYaiEEAkAgAygCGEEDRgRAIANBGGogA0EQakHAgMAAQQMgAkEQahAFIAMoAhhBA0YNAQsgA0EIaiAEQQhqKQIANwMAIAMgBCkCADcDAAwQCyADIAMoAhAgAy0AFBB9DA8LIANBGGogAUHlg8AAQQ0QhQEgAygCGEEBRgRAIANBCGogA0EkaikCADcDACADIAMpAhw3AwAMDwsgAyADKAIcNgIQIAMgA0Egai0AADoAFCADQRhqIANBEGpB8oPAAEEGIAJBBGoQBSADQRhqIQQCQCADKAIYQQNGBEAgA0EYaiADQRBqQcCAwABBAyACQRBqEAUgAygCGEEDRg0BCyADQQhqIARBCGopAgA3AwAgAyAEKQIANwMADA8LIAMgAygCECADLQAUEH0MDgsgA0EYaiABQdmDwABBDBCFASADKAIYQQFHDQYgA0EIaiADQRhqQQRyIgJBCGopAgA3AwAgAyACKQIANwMADA0LIANBGGogAUG/g8AAQQkQhQEgAygCGEEBRgRAIANBCGogA0EkaikCADcDACADIAMpAhw3AwAMDQsgAyADKAIcNgIQIAMgA0Egai0AADoAFCADQRhqIANBEGpByIPAAEEHIAJBBGoQBSADQRhqIQQCQCADKAIYQQNGBEAgA0EYaiADQRBqQc+DwABBCiACQRBqEAUgAygCGEEDRg0BCyADQQhqIARBCGopAgA3AwAgAyAEKQIANwMADA0LIAMgAygCECADLQAUEH0MDAsgAyADKAIcNgIQIAMgA0Egai0AADoAFCADQRhqIANBEGpBwIDAAEEDIAJBBGoQBSADKAIYQQNGDQUgA0EIaiADQSBqKQMANwMAIAMgAykDGDcDAAwLCyADIAMoAhw2AhAgAyADQSBqLQAAOgAUIANBGGogA0EQakHAgMAAQQMgAkEEahAFIAMoAhhBA0YNCSADQQhqIANBIGopAwA3AwAgAyADKQMYNwMADAoLIAMgAygCHDYCECADIANBIGotAAA6ABQgA0EYaiADQRBqQcCAwABBAyACQQRqEAUgAygCGEEDRg0HIANBCGogA0EgaikDADcDACADIAMpAxg3AwAMCQsgAyADKAIcNgIQIAMgA0Egai0AADoAFCADQRhqIANBEGpBnITAAEEEIAJBBGoQBSADKAIYQQNGDQUgA0EIaiADQSBqKQMANwMAIAMgAykDGDcDAAwICyADIAMoAhwgA0Egai0AABB9DAcLIAMgAygCHCADQSBqLQAAEH0MBgsgAyADKAIQIAMtABQQfQwFCyAGQQEQxwEACyAGQQEQxwEACyADIAMoAhAgAy0AFBB9DAILIAMgAygCECADLQAUEH0MAQsgAyADKAIQIAMtABQQfQsgAygCACICQQNHBEAgA0EMaigCACEBIANBCGooAgAhBCAAIAMoAgQ2AgQgACACNgIAIABBDGogATYCACAAQQhqIAQ2AgAMAgsCQAJAIAEoAggiBCABQQRqKAIARwRAIAEoAgAhBQwBCyAEQQFqIgIgBEkNAiAEQQF0IgUgAiAFIAJLGyICQQBIDQICfyAERQRAIAJBARBADAELIAEoAgAgBEEBIAIQQQsiBUUNASABIAU2AgAgAUEEaiACNgIAIAEoAgghBAsgBCAFakH9ADoAACAAQQM2AgAgASABKAIIQQFqNgIIDAILIAJBARDHAQALEMgBAAsgA0EwaiQACx0AIAEoAgBFBEAACyAAQdCNwAA2AgQgACABNgIAC1UBAn8gASgCACECIAFBADYCAAJAIAIEQCABKAIEIQNBCEEEEEAiAUUNASABIAM2AgQgASACNgIAIABB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AnwhBiADQZgBaiACQQxqIggQZCADQZgBakEEciEEIAMoApgBQQFGBEAgAEEBNgIAIAAgBCkCADcCBCAAQRxqIARBGGooAgA2AgAgAEEUaiAEQRBqKQIANwIAIABBDGogBEEIaikCADcCACAFRQ0BIAYQwAEMAQsgA0HkAGogBEEIaigCADYCACADQfAAaiABQShqKAIANgIAIAMgBzYCWCADIAU2AlQgAyAGNgJQIAMgBCkCADcCXCADIAEpAiA3A2ggA0G4AWoQfyADQdABaiADQbgBahCEAQJAIAMoAtABQQFGBEAgA0H4AWogA0HcAWopAgA3AwAgAyADKQLUATcD8AEMAQsgAyADKALUATYCyAEgAyADQdgBai0AADoAzAEgA0HQAWogA0HIAWpBr4/AAEEIIANB0ABqEDcgA0HQAWohBAJAIAMoAtABQQNHDQAgA0HQAWogA0HIAWpBt4/AAEELIANB3ABqEDcgAygC0AFBA0cNACADQdABaiADQcgBakHCj8AAQQYgA0HoAGoQNyADKALQAUEDRw0AIANB8AFqIAMoAsgBIAMtAMwBEHwMAQsgA0H4AWogBEEIaikCADcDACADIAQpAgA3A/ABCwJ/IAMoAvABIgRBA0cEQCADQfgBaigCACEFIANB/AFqKAIAIQYgAygC9AEhByADKAK8AQRAIAMoArgBEMABCyADQfwBaiAGNgIAIANB+AFqIAU2AgAgAyAHNgL0ASADIAQ2AvABIANB0AFqQZiJwABBGSADQfABahAWIANByABqIANB5AFqKQIANwMAIAMgA0HcAWopAgA3A0AgA0HYAWooAgAhBSADKALQASEEQQEhBiADKALUAQwBCyADKALAASEFIAMoArgBIQRBACEGIAMoArwBCyEHIANBLGoiCSAFNgIAIANBKGogBzYCACADQTBqIAMpA0A3AwAgA0E4aiADQcgAaikDADcDACADIAQ2AiQgAyAGNgIgAkAgBkUEQCADQeCNwABBBiAEIAUQYyADLQAAQQlHDQEgBwRAIAQQwAELIAMoAlQEQCADKAJQEMABCyADQeAAaigCAARAIAMoAlwQwAELIANB7ABqKAIABEAgAygCaBDAAQsgAEEYakIANwIAIABBEGpCBDcCACAAQQhqQgA3AgAgAEKAgICAgAE3AgAgAkEEaigCAARAIAIoAgAQwAELIAJBEGooAgAEQCAIKAIAEMABCyABQRRqKAIABEAgASgCEBDAAQsgAUE0aigCACIABEAgAEEFdCECIAFBLGooAgBBEGohAANAIABBBGooAgAEQCAAKAIAEMABCyAAQSBqIQAgAkFgaiICDQALCyABQTBqKAIABEAgASgCLBDAAQsgAUE8aigCAEUNBCABKAI4EMABDAQLIAAgAykCJDcCBCAAQQE2AgAgAEEcaiADQTxqKAIANgIAIABBFGogA0E0aikCADcCACAAQQxqIAkpAgA3AgAgAygCVARAIAMoAlAQwAELIANB4ABqKAIABEAgAygCXBDAAQsgA0HsAGooAgAEQCADKAJoEMABCyACQQRqKAIABEAgAigCABDAAQsgAkEQaigCAARAIAgoAgAQwAELIAFBFGooAgBFDQIgASgCEBDAAQwCCyAAIAMpAwA3AgQgAEEBNgIAIABBHGogA0EYaigCADYCACAAQRRqIANBEGopAwA3AgAgAEEMaiADQQhqKQMANwIAIAcEQCAEEMABCyADKAJUBEAgAygCUBDAAQsgA0HgAGooAgAEQCADKAJcEMABCyADQewAaigCAARAIAMoAmgQwAELIAJBBGooAgAEQCACKAIAEMABCyACQRBqKAIABEAgCCgCABDAAQsgAUEUaigCAEUNASABKAIQEMABDAELIAJBBGooAgAEQCACKAIAEMABCyACQRBqKAIABEAgAigCDBDAAQsgAUEUaigCAARAIAEoAhAQwAELIAFBJGooAgBFDQAgASgCIBDAAQsgAUE0aigCACIABEAgAEEFdCECIAFBLGooAgBBEGohAANAIABBBGooAgAEQCAAKAIAEMABCyAAQSBqIQAgAkFgaiICDQALCyABQTBqKAIABEAgASgCLBDAAQsgAUE8aigCAEUNACABKAI4EMABCyADQYACaiQAC+4YAgl/A34jAEHAAmsiAyQAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCACQf8BcUEBaw4FAgMEBQABCxAeAAsgA0EQaiABQcgAEI4CGiADQYABahBiIAMoAoABQQFHDQkgAEEBNgIAIAAgA0GAAWpBBHIiASkCADcCBCAAQRxqIAFBGGooAgA2AgAgAEEUaiABQRBqKQIANwIAIABBDGogAUEIaikCADcCAAwKCxA6AAsCQANAIAMgDDcD+AEgAyADQfgBajYCyAEgA0EANgKgAiADQgE3A5gCIANBCjYCXCADIANByAFqNgJYIAMgA0GYAmo2AtgBIANBATYClAEgA0IBNwKEASADQZyFwAA2AoABIAMgA0HYAGo2ApABIANB2AFqQZyKwAAgA0GAAWoQ1QENBQJAIAMoApwCIgIgAygCoAIiBEYEQCADKAKYAiEFDAELIAIgBEkNByAERQRAIAIEQCADKAKYAhDAAQsgA0IBNwOYAkEAIQJBASEFDAELIAMoApgCIAJBASAEEEEiBUUNCCADIAQ2ApwCIAMgBTYCmAIgBCECCyADQRBqQaSOwABBCCAFIAMoAqACEGMgAy0AEEEJRw0BIAIEQCAFEMABCyADKQP4ASINQgF8IgwgDVoNAAtBgIvAAEEcQayOwAAQzQEACyAAIAMpAxA3AgQgAEEBNgIAIABBHGogA0EoaigCADYCACAAQRRqIANBIGopAwA3AgAgAEEMaiADQRhqKQMANwIAIAJFDQIgBRDAAQwCCxA7AAtBwAxAAEF/RwRAIABCgICAgIABNwIAIABBGGpCADcCACAAQRBqQgQ3AgAgAEEIakIANwIADAELQSBBARBAIgJFDQQgAkEYakH4jsAAKQAANwAAIAJBEGpB8I7AACkAADcAACACQQhqQeiOwAApAAA3AAAgAkHgjsAAKQAANwAAEI4BIQQgAEEMakKggICAgAQ3AgAgAEEIaiACNgIAIAAgBDoABSAAQQA6AAQgAEEBNgIAIABBFGogAykCmAI3AgAgAEEcaiADQaACaigCADYCAAsgAUEUaigCAARAIAEoAhAQwAELIAFBJGooAgAEQCABKAIgEMABCyABQSxqKAIAIQAgAUE0aigCACICBEAgAkEFdCEEIABBEGohAgNAIAJBBGooAgAEQCACKAIAEMABCyACQSBqIQIgBEFgaiIEDQALCyABQTBqKAIABEAgABDAAQsgAUE8aigCAEUNBiABKAI4EMABDAYLQaSFwABBNyADQYABakG0isAAEN8BAAtBzIvAAEEkQbyLwAAQzQEACyAEQQEQxwEAC0EgQQEQxwEACwJ/AkAgAygChAEiAkUEQEEBIQFBBSEGQQVBARBAIgQNAUEFQQEQxwEACyACQQh2IQUgA0GIAWooAgAhBCADQYwBaigCACEGQQAhASACQYCAfHEMAQsgBEEEakGCjsAALQAAOgAAIARB/o3AACgAADYAABCOASEFIANB4ABqIANBoAJqKAIANgIAIAMgAykCmAI3A1hBAyECQQALIQcgA0GAAmoiCCADQeAAaigCADYCACADIAMpA1g3A/gBIAJB/wFxIAVBCHRBgP4DcXIgB3IhBSABBEAgACAFNgIEIABBATYCACAAQRBqQQU2AgAgAEEMaiAGNgIAIABBCGogBDYCACAAQRRqIAMpA/gBNwIAIABBHGogCCgCADYCAAwBCyADQYABaiAFIAYQFSADQYABakEEciEBIAMoAoABQQFGBEAgAEEBNgIAIAAgASkCADcCBCAAQRxqIAFBGGooAgA2AgAgAEEUaiABQRBqKQIANwIAIABBDGogAUEIaikCADcCACAERQ0BIAUQwAEMAQsgA0H4AGogAUEgaigCADYCACADQfAAaiABQRhqKQIANwMAIANB6ABqIAFBEGopAgA3AwAgA0HgAGogAUEIaikCACIMNwMAIAMgASkCACINNwNYAkACQAJAAkAgA0E4aigCACIBIAynRgRAIAMoAjAiAiANpyIGRg0BIAIgBiABEI8CRQ0BCxCOASEBIANBgAFqIgJBBzoAACACIAE6AAEgAEEBNgIAIABBHGogA0GYAWooAgA2AgAgAEEUaiADQZABaikDADcCACAAQQxqIANBiAFqKQMANwIAIAAgAykDgAE3AgQMAQsgA0GAAWogA0HkAGoQZSADQYABakEEciEBIAMoAoABQQFGBEAgAEEBNgIAIAAgASkCADcCBCAAQRxqIAFBGGooAgA2AgAgAEEUaiABQRBqKQIANwIAIABBDGogAUEIaikCADcCAAwDCyADQbABaiABQQhqIgIoAgA2AgAgAyABKQIANwOoASADQYABaiADQcgAahBlIAMoAoABQQFGBEAgAEEBNgIAIAAgASkCADcCBCAAQRxqIAFBGGooAgA2AgAgAEEUaiABQRBqKQIANwIAIABBDGogAikCADcCAAwCCyADQcABaiACKAIANgIAIAMgASkCADcDuAEgA0GAAWogA0G4AWoQygEgA0GgAmogAykCgAE3AwAgA0GoAmogA0GIAWooAgA2AgAgA0KAgICAEDcDmAIgA0GAAWogA0GYAmoQGiADQYABakEEciEBIAMoAoABQQFGBEAgA0GQAmoiAiABQRhqKAIANgIAIANBiAJqIgYgAUEQaikCADcDACADQYACaiIHIAFBCGopAgA3AwAgAyABKQIANwP4ASADQZgCahAjIANB8AFqIAIoAgAiATYCACADQegBaiAGKQMAIgw3AwAgA0HgAWogBykDACINNwMAIAMgAykD+AEiDjcD2AEgAEEcaiABNgIAIABBFGogDDcCACAAQQxqIA03AgAgACAONwIEIABBATYCACADKAK8AUUNAiADKAK4ARDAAQwCCyADQYACaiICIAFBCGooAgA2AgAgAyABKQIANwP4ASADQZgCahAjIANB4AFqIAIpAwAiDDcDACADQdABaiAMPgIAIAMgAykD+AE3A8gBAkBBMEEEEEAiAQRAIANBmAJqQeaNwABBBkHsjcAAQQcQXCADQQhqIANBqAFqEF4gA0GAAWpB843AAEELIAMoAgggAygCDBBcIAFBEGogA0GoAmoiBikDADcCACABQQhqIANBoAJqKQMANwIAIAEgAykDmAI3AgAgASADKQOAATcCGCABQSBqIANBiAFqKQMANwIAIAFBKGogA0GQAWoiBykDADcCAEHIAEEIEEAiAkUNASADQYgBaiIIIANBwAFqKAIANgIAIANBlAFqIANBsAFqKAIANgIAIANBsAJqIgkgAykDyAE3AwAgA0G4AmoiCiADQdABaigCADYCACADIAMpA6gBNwKMASADQaACaiILIAgpAwA3AwAgBiAHKQMANwMAIAMgAykDuAE3A5gCIAJBADYCACACIAMpA5gCNwIEIAJBDGogCykDADcCACACQRRqIAYpAwA3AgAgAkEcaiAJKQMANwIAIAJBJGogCigCADYCACAAQRxqQQA2AgAgAEEUakKCgICAIDcCACAAQRBqIAE2AgAgAEEIakKBgICAEDcCACAAIAI2AgQgAEEANgIADAILQTBBBBDHAQALQcgAQQgQxwEACyADKAJcBEAgAygCWBDAAQsgA0HoAGooAgAEQCADKAJkEMABCyADQfQAaigCAARAIAMoAnAQwAELIAQEQCAFEMABCyADQSRqKAIABEAgAygCIBDAAQsgA0E0aigCAARAIAMoAjAQwAELIANBxABqKAIAIgAEQCAAQQV0IQQgA0E8aigCAEEQaiECA0AgAkEEaigCAARAIAIoAgAQwAEL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An8gBEUEQCAFQQEQQAwBCyABKAIAIARBASAFEEELIgNFDQcgASADNgIAIAFBBGogBTYCACABQQhqKAIAIQQLIAMgBGpBIjoAACABQQhqIgMgAygCAEEBaiIENgIAAkAgAUEEaigCACIDIARrQQZPBEAgASgCACEDDAELIARBBmoiBSAESQ0KIANBAXQiBCAFIAQgBUsbIgRBAEgNCgJ/IANFBEAgBEEBEEAMAQsgASgCACADQQEgBBBBCyIDRQ0IIAEgAzYCACABQQRqIAQ2AgAgAUEIaigCACEECyABQQhqIgUgBEEGajYCACADIARqIgNBBGpBpYDAAC8AADsAACADQaGAwAAoAAA2AAACQCABQQRqKAIAIgMgBSgCACIEa0ECTwRAIAEoAgAhAwwBCyAEQQJqIgUgBEkNCiADQQF0IgQgBSAEIAVLGyIEQQBIDQoCfyADRQRAIARBARBADAELIAEoAgAgA0EBIAQQQQsiA0UNCSABIAM2AgAgAUEEaiAENgIAIAFBCGooAgAhBAsgAUEIaiAEQQJqNgIAIAMgBGpBovQAOwAAIAJBKGogAkHQAGogARALIAIoAigiA0EDRw0KIAIgAUEAEHwMCwsgAEEBNgIAIAAgAkEoakEEciIDKQIANwIEIABBHGogA0EYaigCADYCACAAQRRqIANBEGopAgA3AgAgAEEMaiADQQhqKQIANwIADA4LIAZBARDHAQALIAZBARDHAQALIANBARDHAQALIANBARDHAQALIAVBARDHAQALIAVBARDHAQALIARBARDHAQALIARBARDHAQALEMgBAAsgAkEwaigCACEBIAIoAiwhBCACQQxqIAJBNGooAgA2AgAgAkEIaiABNgIAIAIgBDYCBCACIAM2AgALIAACfyACKAIAIgFBA0cEQCACQQxqKAIAIQMgAkEIaigCACEEIAIoAgQhBSACKAKUAQRAIAIoApABEMABCyACQQxqIAM2AgAgAkEIaiAENgIAIAIgBTYCBCACIAE2AgAgAkEoakHyicAAQScgAhAWIAJB+ABqIAJBPGopAgAiDDcDACACIAJBNGopAgAiDTcDcCACKQMoIQ4gAEEMaiACQTBqKAIANgIAIAAgDjcCBCAAQRBqIA03AgAgAEEYaiAMNwIAQQEMAQsgAikDkAEhDCAAQQxqIAIoApgBNgIAIAAgDDcCBEEACzYCACACKAJQIQMgAigCWCIABEAgAEEFdCEBIANBEGohAANAIABBBGooAgAEQCAAKAIAEMABCyAAQSBqIQAgAUFgaiIBDQALCyACKAJURQ0EIAMQwAEMBAsgAkEwaigCACEEIAIoAiwhBSACQZwBaiACQTRqKAIANgIAIAJBmAFqIAQ2AgAgAiAFNgKUASACIAM2ApABCwJAIAIoApABIgNBA0cEQCACQZwBaigCACEEIAJBmAFqKAIAIQUgAigClAEhBiACKAJkRQ0BIAIoAmAQwAEMAQsgAkHYAGogAkH4AGopAwA3AwAgAiACKQNwNwNQIAIpA2AhDCAAQQxqIAIoAmg2AgAgACAMNwIEIABBADYCACACKAIEBEAgAigCABDAAQsgAkEQaigCAARAIAIoAgwQwAELIAJBHGooAgAEQCACKAIYEMABCyAHBEAgCBDAAQsgCkUNAiAJEMABDAILIAJBnAFqIAQ2AgAgAkGYAWogBTYCACACIAY2ApQBIAIgAzYCkAEgAkEoakHOicAAQSQgAkGQAWoQFiACQdgAaiACQTxqKQIAIgw3AwAgAiACQTRqKQIAIg03A1AgAikDKCEOIABBDGogAkEwaigCADYCACAAIA43AgQgAEEQaiANNwIAIABBGGogDDcCACAAQQE2AgAgAigCBARAIAIoAgAQwAELIAJBEGooAgAEQCACKAIMEMABCyACQRxqKAIABEAgAigCGBDAAQsgBwRAIAgQwAELIApFDQEgCRDAAQwBCyAHRQ0AIAgQwAELIAEoAgAiAEUNACABQQRqKAIARQ0AIAAQwAELIAJBsAFqJAALpR4CG38DfiMAQZACayICJAAgAkHwAGogABBpIAJB8ABqQQRyIQACQCACKAJwQQFGBEAgAkHkAGogAEEYaigCADYCACACQdwAaiAAQRBqKQIANwIAIAJB1ABqIABBCGopAgA3AgAgAkEBNgJIIAIgACkCADcCTAwBCyACQfwAaiIDKAIAIQQgAkH4AGoiBSgCACERIAIoAnQhCiACQfAAaiABEGkCQCACKAJwQQFGBEAgAkHkAGogAEEYaigCADYCACACQdwAaiAAQRBqKQIANwIAIAJB1ABqIABBCGopAgA3AgAgAkEBNgJIIAIgACkCADcCTAwBCyADKAIAIQAgBSgCACESIAIoAnQh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2AJBAUcEQAJAIAIoAogDIgAgAigChANHBEAgAigCgAMhAwwBCyAAQQFqIgEgAEkNCSAAQQF0IgMgASADIAFLGyIBQQBIDQkCfyAARQRAIAFBARBADAELIAIoAoADIABBASABEEELIgNFDQIgAiABNgKEAyACIAM2AoADCyAAIANqQfsAOgAAIAIgAigCiANBAWo2AogDIAJBsANqIAJBgANqQY+NwABBAhCBASACKAKwAyIBQQNHBEAgAkG8A2ooAgAhACACQbgDaigCACEDIAIoArQDIQQMBgsCQCACKAKIAyIBIAIoAoQDRwRAIAIoAoADIQMMAQsgAUEBaiIAIAFJDQkgAUEBdCIDIAAgAyAASxsiAEEASA0JAn8gAUUEQCAAQQEQQAwBCyACKAKAAyABQQEgABBBCyIDRQ0DIAIgADYChAMgAiADNgKAAyACKAKIAyEBCyABIANqQTo6AAAgAiACKAKIA0EBajYCiAMgAkGwA2ogAkGAA2oQhAEgAigCsANBAUYEQCACQZgDaiACQbwDaikCADcDACACIAIpArQDNwOQAwwFCyACIAIoArQDNgKABCACIAJBuANqLQAAOgCEBCACQbADaiACQYAEaiAEEDYgAkGwA2ohACACKAKwA0EDRw0DIAJBsANqIAJBgARqIAJB6AJqEDQgAigCsANBA0cNAyACQbADaiACQYAEaiACQfQCahA1IAIoArADQQNHDQMgAkGQA2ogAigCgAQgAi0AhAQQfAwECyACQZAEaiACQYADaiAEEDEgAigCkAQiAUEDRwRAIAJBnARqKAIAIQAgAkGYBGooAgAhAyACKAKUBCEEDAULIAIoAogDIQEMBQsgAUEBEMcBAAsgAEEBEMcBAAsgAkGYA2ogAEEIaikCADcDACACIAApAgA3A5ADCyACKAKQAyIBQQNHBEAgAkGYA2ooAgAhAyACQZwDaigCACEAIAIoApQDIQQMAQsCQCACKAKIAyIBIAIoAoQDRwRAIAIoAoADIQMMAQsgAUEBaiIAIAFJDQQgAUEBdCIDIAAgAyAASxsiAEEASA0EAn8gAUUEQCAAQQEQQAwBCyACKAKAAyABQQEgABBBCyIDRQ0DIAIgADYChAMgAiADNgKAAyACKAKIAyEBCyABIANqQf0AOgAAIAIgAigCiANBAWoiATYCiAMMAQsgAigChAMEQCACKAKAAxDAAQsgAkGcA2ogADYCACACQZgDaiADNgIAIAIgBDYClAMgAiABNgKQAyACQbADakGOhsAAQeoAIAJBkANqEBYgAkGYBGogAkHEA2oiACkCACIXNwMAIAIgAkG8A2oiASkCACIYNwOQBCACKQOwAyEZIAJBuANqIgMgAygCADYCACABIBg3AgAgACAXNwIAIAIgGTcDsANBxIrAAEErIAJBsANqQfCKwAAQ3wEACyACKQOAAyEXIAIgATYCuAMgAiAXNwOwAyACQbADahBoAkAgAigC2AJFBEAgBBAkIAJB8AJqKAIAIgEEQCACQegCaigCACEAIAFBGGwhAQNAIABBBGooAgAEQCAAKAIAEMABCyAAQRBqKAIABEAgAEEMaigCABDAAQsgAEEYaiEAIAFBaGoiAQ0ACwsgAkHsAmooAgAEQCACKALoAhDAAQsgAkH0AmooAgAiAEUNASACQfgCaigCAEUNASAAEMABDAELIAQQHAsgAkGgBGokAA8LIABBARDHAQALEMgBAAu1GwIPfwN+IwBB8AFrIgEkACABQaABaiAAEGlBBCEAAkAgASgCoAFBAUYEQCABQZwBaiABQaABakEEciIAQRhqKAIANgIAIAFBlAFqIABBEGopAgA3AgAgAUGMAWogAEEIaikCADcCACABQQE2AoABIAEgACkCADcChAEMAQsgAUGoAWoiDigCACELIAFB4AFqIAEoAqQBIgwgAUGsAWoiDygCABByIAFB+ABqIAFB4AFqEHkCQCABLQB4QQFxRQ0AIAEtAHkiAkH7AEcEQCACQSJHBEBBCiEADAILIAFBoAFqIAFB4AFqEC8gAUGsAWooAgAhAiABQagBaigCACEEIAEoAqQBIQNBDiEAAkAgAS0AoAEiCEEBTQRAIAhBAWsNAwwBCyACRSADQRRHcg0CIAQQwAEMAgsgAUGwAWooAgAhBSADIQAMAQsgAUHgAWoQcyABQaABaiABQeABaiIJEC8gAUGsAWooAgAhAiABQagBaigCACEEIAEoAqQBIQMgAS0AoQEhBwJAIAEtAKABIgVBAU0EQCAFQQFrDQEgAUGwAWooAgAhBSADIQAMAgsgAkUgA0EUR3INACAEEMABCyABQaABaiAJEHcgASgCoAEiA0EWRwRAIAFBrAFqKAIAIQUgAUGoAWooAgAhAiABKAKkASEEIAMhAAwBCyABQfAAaiAJEHkgAS0AcEEBcSECIAEtAHEhBAJAAkACQCAHQQFHBEAgAkUNBCAEQf8BcUH7AEcNAyAJEHMgAUHoAGogCRBxIAEtAGwhBCABQeAAaiABKAJoIgUQeSABLQBgQQFxRQ0CIAEtAGEhAiAEQQFxIQQDQAJAAkACQCACQf8BcSIDQSxHBEAgA0H9AEYNAiAEQf8BcQ0BQQkhAAwJCyAEQf8BcQRAQRAhAAwJCyAFEHMgAUHYAGogBRB5IAEtAFhBAXFFDQggAS0AWSECCyACQf8BcSIDQSJHBEBBECEAIANB/QBHDQhBEyEADAgLIAFByABqIAUQeSABLQBIQQFxRQ0HIAEtAElBIkcNBiAFEHMgAUGgAWogBRB4IAEoAqwBIQIgASgCqAEhBCABKAKkASEDIAEoAqABIgdBAUcEQCAEBEAgAxDAAQsgAkUgB0UgA0EUR3JyDQIgBBDAAQwCCyADQRZGDQEgASgCsAEhBSADIQAMBwsgAUGgAWogCRB2IAEoAqABIgNBFkcEQCABQawBaigCACEFIAFBqAFqKAIAIQIgASgCpAEhBCADIQAMBwsgAUHQAGogCRB5IAEtAFBBAXFFDQZBCyEAQQAhByABLQBRQf0ARw0GDAMLIAFBoAFqIAUQdyABKAKgASIDQRZHBEAgASgCqAEhAiABKAKkASEEIAEgASgCrAEiBTYCjAEgASACNgKIASABIAQ2AoQBIAEgAzYCgAEgAyEADAYLIAFBgAFqIAUQESABKAKAASIDQRZHBEAgASgCjAEhBSABKAKIASECIAEoAoQBIQQgAyEADAYLIAFBQGsgBRB5QQAhBCABLQBBIQIgAS0AQEEBcQ0ACwwCCyACRQ0DAkAgBEH/AXFB+wBHDQAgCRBzIAFBOGogCRBxIAEtADwhBCABQTBqIAEoAjgiBRB5IAEtADBBAXFFBEBBAiEADAULIAEtADEhAiAEQQFxIQRBACEHAkACfwJAA0ACQAJAAkAgAkH/AXEiA0EsRwRAIANB/QBGDQIgBEH/AXENAUEJDAYLQRAgBEH/AXENBRogBRBzIAFBKGogBRB5IAEtAChBAXFFDQQgAS0AKSECCyACQf8BcSICQSJHBEBBE0EQIAJB/QBGGwwFCyABQRhqIAUQeSABLQAYQQFxRQ0DQQ4gAS0AGUEiRw0EGiAFEHMgAUGgAWogBRB4IAEoAqwBIQIgASgCqAEhBCABKAKkASEKIAEoAqABIgNBAUYEQCABKAKwASEFIAoMBQsCQCACQQdGBEBBACENIApBmoLAAEYNASAKQZqCwABBBxCPAkUNAQtBASENCyAEBEAgChDAAQsgAkUgA0UgCkEUR3JyRQRAIAQQwAELAkAgDQRAIAFBoAFqIAUQdyABKAKgASIDQRZGDQEgASgCqAEhAiABKAKkASEEIAEgASgCrAEiBTYCjAEgASACNgKIASABIAQ2AoQBIAEgAzYCgAEgAwwGCyAHRQRAIAFBoAFqIAUQdyABKAKgASIDQRZHBEAgASgCrAEhBSABKAKoASECIAEoAqQBIQQgAyEADA0LIAFBEGogBRB5IAEtABBBAXFFDQwgAS0AEUEiRw0IIAUQcyABQaABaiAFEHggASgCrAEhCCABKAKoASEGIAEoAqQBIQcgASgCoAEiA0EBRgRAIAEoArABIQUgCCECIAYhBCAHIQAMDQsgCEUgA0UgB0EUR3JyDQMgBhDAAQwDCyABQaABakGagsAAQQcQECABQawBaigCACEFIAFBqAFqKAIAIQIgASgCpAEhBCABKAKgASEAIAZFDQsMBgsgAUGAAWogBRARIAEoAoABIgNBFkYNASABKAKMASEFIAEoAogBIQIgASgChAEhBCADDAQLIAdFBEAgAUGgAWpBmoLAAEEHEA4gAUGsAWooAgAhBSABQagBaigCACECIAEoAqQBIQQgASgCoAEhAAwKCyABQaABaiAJEHYgASgCoAEiA0EWRwRAIAFBrAFqKAIAIQUgAUGoAWooAgAhAiABKAKkASEEIAZFBEAgAyEADAsLIAcQwAEgAyEADAoLIAFBIGogCRB5IAEtACBBAXFFBEAgBkUNCiAHEMABDAoLIAEtACFB/QBGDQZBCyEAIAZFDQkgBxDAAQwJCyABQQhqIAUQeUEAIQQgAS0ACSECIAEtAAhBAXENAAtBAgwBC0EECyEAIAZFIAdFcg0FCyAHEMABDAQLQQ4hAAwDCyAJEHMgAUGgAWogAUHgAWoQdCABQawBaiEEIAFBqAFqIQMgASgCoAEiAEEWRwRAIAQoAgAhBSADKAIAIQIgASgCpAEhBCAHRSAGRXINAyAHEMABDAMLIAQgCDYCACADIAY2AgAgASAHNgKkASABIAFB4AFqNgKgASABQYABaiABQaABakEEchA8IAtFDQMgDBDAAQwDC0ECIQAMAQtBDiEACyABQewBaiAFNgIAIAFB6AFqIAI2AgAgASAENgLkASABIAA2AuABIAFBoAFqQfyIwABBHCABQeABahAUIAFByAFqIAFBtAFqKQIAIhA3AwAgAUGMAWogDigCADYCACABQZABaiAPKQIANwMAIAFBmAFqIBA3AwAgAUEBNgKAASABIAEpA6ABNwKEASALRQ0AIAwQwAELIAFB0AFqEH8gAUGAAWpBBHIhCAJAAkACQAJAAn8gASgCgAFBAUcEQAJAIAEoAtgBIgcgASgC1AFHBEAgASgC0AEhAgwBCyAHQQFqIgMgB0kNBSAHQQF0IgAgAyAAIANLGyIAQQBIDQUCfyAHRQRAIABBARBADAELIAEoAtABIAdBASAAEEELIgJFDQYgASAANgLUASABIAI2AtABCyACIAdqQfsAOgAAIAEgASgC2AFBAWo2AtgBIAFBoAFqIAFB0AFqQY+NwABBAhCBASABKAKgASIGQQNHBEAgAUGoAWooAgAhAiABKAKkASEEIAFBrAFqKAIADAILAkAgASgC2AEiBiABKALUAUcEQCABKALQASECDAELIAZBAWoiAyAGSQ0FIAZBAXQiACADIAAgA0sbIgBBAEgNBQJ/IAZFBEAgAEEBEEAMAQsgASgC0AEgBkEBIAAQQQsiAkUNBiABIAA2AtQBIAEgAjYC0AEgASgC2AEhBgsgAiAGakE6OgAAIAEgASgC2AFBAWo2AtgBIAFBwAFqIAgQWiABQaABaiABQdABaiABKALAASIAIAEoAsgBEIEBIAEoAsQBBEAgABDAAQsgASgCoAEiBkEDRwRAIAFBqAFqKAIAIQIgASgCpAEhBCABQawBaigCAAwCCwJAIAEoAtgBIgYgASgC1AFHBEAgASgC0AEhAgwBCyAGQQFqIgMgBkkNBSAGQQF0IgAgAyAAIANLGyIAQQBIDQUCfyAGRQRAIABBARBADAELIAEoAtABIAZBASAAEEELIgJFDQYgASAANgLUASABIAI2AtABIAEoAtgBIQYLIAIgBmpB/QA6AAAgASABKALYAUEBaiIGNgLYAQwDCyABQeABaiABQdABaiAIEDEgASgC4AEiBkEDRg0BIAFB6AFq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AmooAgAhBiADQdACaigCACECIAMoAswCIQcgAygCyAJBAUYEQCACIQQgBiECIAEhBiAHIQEMCwsgA0HkBGogATYCACADQeAEaiAGNgIAIANB6ARqIgUgAykD+AQ3AwAgA0HwBGogBCgCADYCACADIAI2AtwEIAMgBzYC2AQgA0GYAmogChB5IAMtAJgCQQFxDQEgA0HYBGoQHEEEIQEMCgsgA0HUAmooAgAhCCADQdACaigCACEFIAMoAswCIQcMAQsgAy0AmQJB/QBHBEAgA0HYBGoQHEELIQEMCQsgChBzIANByARqIAVBCGooAgA2AgAgAyAFKQIANwPABEEBDAQLIAQEQCABEMABCyACRSAMRSABQRRHcnJFBEAgBBDAAQsgCwRAIAghAiAFIQQgByEBDAgLIANBqAJqIAoQeSADLQCoAkEBcQ0CIAUNAQtBBCEBDAYLIAcQwAFBBCEBDAULIAMtAKkCQf0ARwRAQQshASAFRQ0FIAcQwAEMBQsgChBzIAghBiAFIQJBAAshCiADQaAEaiIFIAE2AgAgA0GcBGogBjYCACADQZgEaiACNgIAIANBpARqIAMpA8AENwIAIANBrARqIANByARqKAIANgIAIAMgByIENgKUBCADIAo2ApAEIANBkAJqIA0QeSADLQCQAkEBcUUNASADLQCRAkH9AEcEQAJAIApFBEAgAkUNASAHEMABQQshAQwGCyADQZAEakEEchAcC0ELIQEMBAsgDRBzIANBiARqIAVBCGopAgA3AwAgAyAFKQIANwOABEEACyEIIANB2ANqIg0gBjYCACADQdQDaiACNgIAIANB0ANqIAQ2AgAgA0HcA2ogAykDgAQ3AgAgA0HkA2ogA0GIBGopAwA3AgAgAyAKNgLMAyADIAg2AsgDIANByAJqIANBuANqEHQgA0HUAmohByADQdACaiELIAMoAsgCIgFBFkcEQCAHKAIAIQYgCygCACECIAMoAswCIQQgA0HIA2oQSQwDCyADQfgCaiANQQhqKQIAIhE3AwAgA0GAA2ogDUEQaigCACIFNgIAIAsgCjYCACAHIAQ2AgAgA0HYAmogAjYCACADQdwCaiANKQIAIhI3AgAgA0HkAmogETcCACADQewCaiIBIAU2AgAgAyASNwPwAiADIAg2AswCIANBADYCyAIgEARAIA8QwAELIAAgAykCzAI3AgAgAEEgaiABKAIANgIAIABBGGogA0HkAmopAgA3AgAgAEEQaiADQdwCaikCADcCACAAQQhqIANB1AJqKQIANwIADAMLIApFBEAgAkUNASAHEMABQQQhAQwCCyADQZAEakEEchAcC0EEIQELIANB1ANqIAY2AgAgA0HQA2ogAjYCACADIAQ2AswDIAMgATYCyANBpgFBARBAIgFFDQEgAUHPkcAAQaYBEI4CIQUgAyADQcgDajYCiAUgA0EANgKYBCADQgE3A5AEIANBHjYC/AQgAyADQYgFajYC+AQgAyADQZAEajYC2AQgA0HcAmpBATYCACADQgE3AswCIANBkJHAADYCyAIgAyADQfgEajYC2AIgA0HYBGpB+JLAACADQcgCahDVAQ0CIAMoApQEIgEgAygCmAQiAkcEQCABIAJJDQQCQCACRQRAIAEEQCADKAKQBBDAAQsgA0EBNgKQBEEAIQIMAQsgAygCkAQgAUEBIAIQQSIBRQ0GIAMgATYCkAQLIAMgAjYClAQLIANB4ARqIgIgA0GYBGooAgA2AgAgAyADKQOQBDcD2AQQjgEhBAJAIAMoAsgDQRRHDQAgAygC0ANFDQAgAygCzAMQwAELIANBqANqIgEgAigCADYCACADQZgDaiADQbADaigCADYCACADQZADaiABKQMAIhE3AwAgA0HUAmoiAkKmgYCA4BQ3AgAgA0HQAmogBTYCACADQdwCaiADKQPYBCISNwIAIANB5AJqIgEgET4CACADIBI3A4gDIAMgBEEIdEEFcjYCzAIgA0EBNgLIAiAQBEAgDxDAAQsgAEKAgICAEDcCACAAQQhqIAMpAswCNwIAIABBEGogAikCADcCACAAQRhqIANB3AJqKQIANwIAIABBIGogASgCADYCAAsgDhDAASADQaAFaiQADwtBpgFBARDHAQALQZiRwABBNyADQcgCakGQk8AAEN8BAAtB3JTAAEEkQcyUwAAQzQEACyACQQEQxwEAC5FvAQ9/IwBB8AVrIgIkACACQagFaiABEFUgAkG0BWooAgAhAyACQbAFaigCACEJIAIoAqwFIQQgAi0AqQUhBgJAAkAgAAJ/AkACQAJAAkACfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAi0AqAUiBUEBTQRAIAVBAWsNAgwBCyADRSAE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IAIgBBDPAQALQaCwwABBHEGAssAAEM0BAAsgBEEBagwBCyAKDQMgAwshBCABIAQ2AgggByAESw0ACwsgAEKBgICAMDcCAAwFC0GgsMAAQRxBgLLAABDNAQALQQwhBiALQQFxDQFBESEGIA1BAUYNAQsgCEEQaiAFIAQQ5AEgCCgCEEEBRw0BIAEEQCAFEMABCyAAQoGAgIDwATcCACAAQQhqIAgpAhA3AgAgAEEQaiAIQRhqKAIANgIADAILIABBATYCACAAIAY2AgQgAUUNASAFEMABDAELIAAgBTYCBCAAQQA2AgAgAEEMaiAENgIAIABBCGogATYCAAsgCEEgaiQADwsQbAALIAJBARDHAQALcwEGfwJAIAEoAgQiBCABKAIIIgJNDQAgASgCACEFA0AgAiAFai0AACIGQXdqIgdBF0sEQEEBIQMMAgtBASAHdEGTgIAEcUUEQEEBIQMMAgsgASACQQFqIgI2AgggBCACSw0ACwsgACAGOgABIAAgAzoAAAs4AQF/An9BACABKAIEIAEoAggiAk0NABogASgCACACai0AACECQQELIQEgACACOgABIAAgAToAAAuyAQEDfwJAAkACQCABKAIIIgIgAUEEaigCAEcEQCABKAIAIQQMAQsgAkEBaiIDIAJJDQIgAkEBdCIEIAMgBCADSxsiA0EASA0CAn8gAkUEQCADQQEQQAwBCyABKAIAIAJBASADEEELIgRFDQEgASAENgIAIAFBBGogAzYCACABKAIIIQILIAIgBGpB3QA6AAAgAEEDNgIAIAEgASgCCEEBajYCCA8LIANBARDHAQALEMgBAAuyAQECfwJAAkACQCABKAIIIgIgAUEEaigCAEcEQCABKAIAIQMMAQsgAkEBaiIDIAJJDQIgAkEBdCIEIAMgBCADSxsiBEEASA0CAn8gAkUEQCAEQQEQQAwBCyABKAIAIAJBASAEEEELIgNFDQEgASADNgIAIAFBBGogBDYCACABKAIIIQILIAIgA2pB/QA6AAAgAEEDNgIAIAEgASgCCEEBajYCCA8LIARBARDHAQALEMgBAAvOAgECfwJAAkACQAJAIAEoAggiAiABQQRqKAIARwRAIAEoAgAhAwwBCyACQQFqIgMgAkkNAyACQQF0IgQgAyAEIANLGyIEQQBIDQMCfyACRQRAIARBARBADAELIAEo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ACAAQQhqQQE6AAAgACABNgIEIABBADYCACABIAEoAghBAWo2AggPCyADQQEQxwEACxDIAQAL8wQBBH8jAEEQayIGJAACQAJAAkACQAJAAkAgASgCCCIEIAFBBGooAgBHBEAgASgCACEFDAELIARBAWoiBSAESQ0EIARBAXQiByAFIAcgBUsbIgdBAEgNBAJ/IARFBEAgB0EBEEAMAQsgASgCACAEQQEgBxBBCyIFRQ0BIAEgBTYCACABQQRqIAc2AgAgASgCCCEECyAEIAVqQfsAOgAAQQEhBCABIAEoAghBAWo2AgggBiABIAIgAxCBASAGKAIAIgJBA0cEQCAGQQxqKAIAIQEgBkEIaigCACEDIAYoAgQhBSAAIAI2AgQgAEEQaiABNgIAIABBDGogAzYCACAAQQhqIAU2AgAMBQsCQCABKAIIIgQgAUEEaigCAEcEQCABKAIAIQUMAQsgBEEBaiICIARJDQQgBEEBdCIDIAIgAyACSxsiAkEASA0EAn8gBEUEQCACQQEQQAwBCyABKAIAIARBASACEEELIgVFDQIgASAFNgIAIAFBBGogAjYCACABKAIIIQQLIAQgBWpBOjoAACABIAEoAghBAWoiBDYCCAJAIAFBBGooAgAgBEcEQCABKAIAIQUMAQsgBEEBaiICIARJDQQgBEEBdCIDIAIgAyACSxsiAkEASA0EAn8gBEUEQCACQQEQQAwBCyABKAIAIARBASACEEELIgVFDQMgASAFNgIAIAFBBGogAjYCACABKAIIIQQLIAQgBWpB+wA6AAAgACABNgIEIABBCGpBAToAACABIAEoAghBAWo2AghBACEEDAQLIAdBARDHAQALIAJBARDHAQALIAJBARDHAQALEMgBAAsgACAENgIAIAZBEGokAAucEAIJfwN+AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQEEAIAFBZmoiBiAGIAFLGyIHBEADQCAJQRpqIgggCUkiBg0CIAYNByAIIAFLDQggC0EgaiIIIAtJIgYNAyAGDQkgCCADSw0KIAIgC2oiBSAEIAAgCWoiDCkAACIOQjiGIg9COoinai0AADoAACAFQQFqIAQgDyAOQiiGQoCAgICAgMD/AIOEIg9CNIinQT9xai0AADoAACAFQQJqIAQgDyAOQhiGQoCAgICA4D+DIA5CCIZCgICA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AyAEQQEQxwEACyACQQEQxwEACxDIAQALQeTIwABBJEHUyMAAEM0BAAsgACAENgIEIAAgATYCAAv0AQEDfyMAQRBrIgIkACAAKAIAQRhqIgMoAgAhASADQQI2AgACQAJAAkACQCABQQJNBEAgAUEBaw0CDAELQZzLwABBHEGMy8AAEJoBAAsgACgCACIDQRxqIgEoAgAiAC0AAA0BIABBAToAAEEAIQACQEGQhsEAKAIAQQFGBEBBlIbBACgCACEADAELQZCGwQBCATcDAAtBlIbBACAANgIAIANBIGotAAANAiABKAIAQQA6AAALIAJBEGokAA8LQcTPwABBIEG0z8AAEJoBAAsgAiABNgIIIAIgAEEARzoADEH4x8AAQSsgAkEIakGkyMAAEN8BAAsJACAAQQA2AgALPAECfyAAKAIIIgFB3MLAACgCAEYEfyAAKAIAIgBB2MLAACgCACICRgRAQQEPCyAAIAIgARCPAkUFQQALC7kDAQV/IwBBEGsiAyQAIANBAnIhBEGogsEAKAIAIQEDQAJAAkACQAJAAkAgASICQQNLDQACQAJAIAJBAWsOAwMCAQALQaiCwQBBAkGogsEAKAIAIgEgASACRhs2AgAgASACRw0GIANBqILBADYCACAAIAJBAUZBuMHAACgCABEBACADQQM2AgQgAxCvAQsgA0EQaiQADwsgAkEDcUECRgRAA0BBwILBACgCAEEBRwRAQcCCwQBCATcCAEHIgsEAQQA2AgALIAIhARClASEFQaiCwQAgBEGogsEAKAIAIgIgASACRhs2AgAgA0EAOgAIIAMgBTYCACADIAFBfHE2AgQgASACRgRAIAMtAAhFDQQMBQsCQCADKAIAIgFFDQAgASABKAIAIgFBf2o2AgAgAUEBRw0AIAMQpAELIAJBA3FBAkYNAAwFCwALQcTMwABBOUG0zMAAEJoBAAtBkM3AAEEqQYDNwAAQmgEACwNAEKYBIAMtAAhFDQALCyADKAIAIgJFDQAgAiACKAIAIgJBf2o2AgAgAkEBRw0AIAMQpAFBqILBACgCACEBDAELQaiCwQAoAgAhAQwACwALowIBA38jAEFAaiIBJAAgACgCACICKAIAIQMgAiAAKAIENgIAIAEgA0EDcSIANgIMIABBAkYEQAJAIANBfHEiAARAA0AgACgCBCAAKAIAIQIgAEEA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AAtBAAwECyAFQQQQzgEACyABIAJBACAEENEBAAsgASACIAQgAhDRAQALQQELIANBMGokAAt4AQJ/IAAtAAQhASAALQAFBEAgAUH/AXEhAiAAAn9BASACDQAaIAAoAgAiAUEcaigCACgCDCECIAEoAhghACABLQAAQQRxRQRAIABB49vAAEECIAIRAgAMAQsgAEHi28AAQQEgAhECAAsiAToABAsgAUH/AXFBAEcL5QICA38FfiMAQdAAayIDJAAgAAJ/QQEgAC0ACA0AGiAAKAIEIQUgACgCACIELQAAQQRxRQRAQQEgBCgCGEHd28AAQefbwAAgBRtBAkEBIAUbIARBHGooAgAoAgwRAgANARogASAAKAIAIAIoAgwRAAAMAQsgBUUEQEEBIAQoAhhB5dvAAEECIARBHGooAgAoAgwRAgANARogACgCACEECyADQQE6ABcgAyADQRdqNgIQIAQpAgghBiAEKQIQIQcgA0E0akG828AANgIAIAMgBCkCGDcDCCAEKQIgIQggBCkCKCEJIAMgBC0AMDoASCAEKQIAIQogAyAJNwNAIAMgCDcDOCADIAc3AyggAyAGNwMgIAMgCjcDGCADIANBCGo2AjBBASABIANBGGogAigCDBEAAA0AGiADKAIwQdvbwABBAiADKAI0KAIMEQIACzoACCAAIAAoAgRBAWo2AgQgA0HQAGokAAuWAQECfyAALQAIIQEgACgCBCICBEAgAUH/AXEhASAAAn9BASABDQAaAkAgAkEBRw0AIAAtAAlFDQAgACgCACICLQAAQQRxDQBBASACKAIYQejbwABBASACQRxqKAIAKAIMEQIADQEaCyAAKAIAIgEoAhhBlNfAAEEBIAFBHGooAgAoAgwRAgALIgE6AAgLIAFB/wFxQQBHCwsAIAAgASACEOcBCzIBAX9BASEBIAAtAAQEfyABBSAAKAIAIgAoAhhB/NvAAEEBIABBHGooAgAoAgwRAgALC+gBAQF/IwBBEGsiAiQAIAJBADYCDCAAIAJBDGoCfwJAIAFBgAFPBEAgAUGAEEkNASABQYCABEkEQCACIAFBP3FBgAFyOgAOIAIgAUEGdkE/cUGAAXI6AA0gAiABQQx2QQ9xQeABcjoADEEDDAMLIAIgAUE/cUGAAXI6AA8gAiABQRJ2QfABcjoADCACIAFBBnZBP3FBgAFy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CWohAwwECyAPQv////+PYINCgICAgMAAhAshD0EDIQQLIAIoAhggAyACKAIcKAIQEQAARQ0ACwwECyAJIA1rIAZqIQkgBiENIAYgDEcNAQsLIANFIAEgA0ZyDQAgAyABTw0CIAAgA2osAABBv39MDQILQQEhCiACKAIYIAAgA2ogASADayACKAIcKAIMEQIADQAgAigCGEEiIAIoAhwoAhARAAAhCgsgCEEgaiQAIAoPCyAAIAEgAyABENEBAAsLACACIAAgARDQAQuKAQEDfyMAQYABayIDJAAgACgCACECQQAhAANAIAAgA2pB/wBqIAJBD3EiBEEwciAEQdcAaiAEQQpJGzoAACAAQX9qIQAgAkEEdiICDQALIABBgAFqIgJBgQFPBEAgAkGAARDPAQALIAFBAUH928AAQQIgACADakGAAWpBACAAaxD2ASADQYABaiQAC9cCAQd/QQEhCQJAAkAgAkUNACABIAJBAXRqIQogAEGA/gNxQQh2IQsgAEH/AXEhDQJAA0AgAUECaiEMIAcgAS0AASICaiEIIAsgAS0AACIBRwRAIAEgC0sNAyAIIQcgDCIBIApHDQEMAwsgCCAHTwRAIAggBEsNAiADIAdqIQECQANAIAJFDQEgAkF/aiECIAEtAAAgAUEBaiEBIA1HDQALQQAhCQwFCyAIIQcgDCIBIApHDQEMAwsLIAcgCBDPAQALIAggBBDOAQALIAZFDQAgBSAGaiEDIABB//8DcSEBA0ACQCAFQQFqIQACfyAAIAUtAAAiAkEYdEEYdSIEQQBODQAaIAAgA0YNASAFLQABIARB/wBxQQh0ciECIAVBAmoLIQUgASACayIBQQBIDQIgCUEBcyEJIAMgBUcNAQwCCwtBzNTAAEErQYzVwAAQzQEACyAJQQFxC8oCAgN/BH4jAEEwayIDJAACQCACRQRAIABBADoAAUEBIQQMAQsCQCABLQAAQStGBEBBASEEIAJBf2oiAkUNASABQQFqIQELIANBKGohBAJAA0AgAkUNASABLQAAQVBqIgVBCk8EQEEBIQQgAEEBOgABDAQLIANBEGogB0IAQgoQkgIgA0EgaiAGQgBCChCSAiADQgBCACAGEJICIAMpAxggAykDCIRCAFIgBCkDACIGIAMpAxAgAykDAHx8IgggBlRyQQFGBEAgAEECOgABQQEhBAwE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ZXNyY192YWxpZGF0b3Jkc3RfdmFsaWRhdG9yd2l0aGRyYXd2YWxpZGF0b3JyZWNpcGllbnR1bmRlbGVnYXRlZGVsZWdhdGVJbml0UmVzcG9uc2Vsb2dMb2dBdHRyaWJ1dGVrZXl2YWx1ZUhhbmRsZVJlc3BvbnNlQ29pbmRlbm9tQmFua1F1ZXJ5YWxsX2JhbGFuY2VzYWRkcmVzc2JhbGFuY2VXYXNtUXVlcnlyYXdzbWFydFN0YWtpbmdRdWVyeXZhbGlkYXRvcnNkZWxlZ2F0aW9uc2RlbGVnYXRvcmJvbmRlZF9kZW5vbUFsbEJhbGFuY2VSZXNwb25zZWJsb2NrbWVzc2FnZWNvbnRyYWN0aGVpZ2h0dGltZWNoYWluX2lkc2VuZGVyc2VudF9mdW5kc1N0ZEVycm9ydW5kZXJmbG93bWludWVuZHN1YnRyYWhlbmR1bmF1dGhvcml6ZWRzZXJpYWxpemVfZXJyc291cmNlcGFyc2VfZXJydGFyZ2V0bnVsbF9wb2ludGVybm90X2ZvdW5ka2luZGludmFsaWRfdXRmOGludmFsaWRfYmFzZTY0Z2VuZXJpY19lcnJTZXJpYWxpemluZyBRdWVyeVJlcXVlc3Q6IABFAhAAGgAAAFF1ZXJpZXIgc3lzdGVtIGVycm9yOiAAAGgCEAAWAAAACwAAAAgAAAAEAAAADAAAAA0AAACcAhAAAAAAAGEgRGlzcGxheSBpbXBsZW1lbnRhdGlvbiByZXR1cm5lZCBhbiBlcnJvciB1bmV4cGVjdGVkbHljb3Ntd2FzbV9zdGQ6OnR5cGVzOjpFbnZoYWNrYXRvbTo6Y29udHJhY3Q6OkluaXRNc2djb3JlOjpyZXN1bHQ6OlJlc3VsdDxjb3Ntd2FzbV9zdGQ6OmluaXRfaGFuZGxlOjpIYW5kbGVSZXNwb25zZSwgY29zbXdhc21fc3RkOjplcnJvcnM6OnN0ZF9lcnJvcjo6U3RkRXJyb3I+Y29zbXdhc21fc3RkOjpxdWVyeTo6UXVlcnlSZXF1ZXN0PGNvc213YXNtX3N0ZDo6dHlwZXM6Ok5ldmVyPmNvcmU6OnJlc3VsdDo6UmVzdWx0PGNvc213YXNtX3N0ZDo6ZW5jb2Rpbmc6OkJpbmFyeSwgY29zbXdhc21fc3RkOjplcnJvcnM6OnN0ZF9lcnJvcjo6U3RkRXJyb3I+Y29yZTo6cmVzdWx0OjpSZXN1bHQ8Y29zbXdhc21fc3RkOjppbml0X2hhbmRsZTo6SW5pdFJlc3BvbnNlLCBjb3Ntd2FzbV9zdGQ6OmVycm9yczo6c3RkX2Vycm9yOjpTdGRFcnJvcj5oYWNrYXRvbTo6Y29udHJhY3Q6OlF1ZXJ5TXNnaGFja2F0b206OmNvbnRyYWN0OjpTdGF0ZWhhY2thdG9tOjpjb250cmFjdDo6SGFuZGxlTXNnaGFja2F0b206OmNvbnRyYWN0OjpWZXJpZmllclJlc3BvbnNlY29zbXdhc21fc3RkOjpxdWVyeTo6QWxsQmFsYW5jZVJlc3BvbnNlAAAADgAAAAQAAAAEAAAADwAAABAAAAARAAAAEgAAAAAAAAABAAAAEwAAAGNhbGxlZCBgUmVzdWx0Ojp1bndyYXAoKWAgb24gYW4gYEVycmAgdmFsdWUAFAAAABwAAAAEAAAAFQAAAGF0dGVtcHQgdG8gYWRkIHdpdGggb3ZlcmZsb3c8Ojpjb3JlOjptYWNyb3M6OnBhbmljIG1hY3Jvcz4AAJwFEAAeAAAAAwAAAAoAAABUcmllZCB0byBzaHJpbmsgdG8gYSBsYXJnZXIgY2FwYWNpdHltaXNzaW5nIGZpZWxkIGBg8AUQAA8AAAD/BRAAAQAAAGR1cGxpY2F0ZSBmaWVsZCBgAAAAEAYQABEAAAD/BRAAAQAAAHVua25vd24gdmFyaWFudCBgYCwgdGhlcmUgYXJlIG5vIHZhcmlhbnRzAAAANAYQABEAAABFBhAAGAAAAGAsIGV4cGVjdGVkIDQGEAARAAAAcAYQAAwAAABFcnJPa2ludmFsaWQgVWludDEyOCAnJyAtIAAAkQYQABEAAACiBhAABAAAAGludmFsaWQgYmFzZTY0OiC4BhAAEAAAAAsAAAAIAAAABAAAABYAAABjb25maWdhY3Rpb25yZWxlYXNlZGVzdGluYXRpb25TdGF0ZXNyYy9jb250cmFjdC5ycwAAAwcQAA8AAAB/AAAACQAAAHRlc3Qua2V5AwcQAA8AAACNAAAACQAAAG11c3Qgbm90IGJlIGVtcHR5AAAAAwcQAA8AAACVAAAAEwAAAEVycm9yIGluIG1lbW9yeS5ncm93IGluc3RydWN0aW9uAwcQAA8AAACuAAAABQAAAFRoaXMgcGFnZSBpbnRlbnRpb25hbGx5IGZhdWx0ZWR2ZXJpZmllcmJlbmVmaWNpYXJ5ZnVuZGVyY3B1X2xvb3BzdG9yYWdlX2xvb3BtZW1vcnlfbG9vcGFsbG9jYXRlX2xhcmdlX21lbW9yeXBhbmljAAAA7AYQAAcAAADIBxAACAAAANAHEAAMAAAA3AcQAAsAAADnBxAAFQAAAPwHEAAFAAAAb3RoZXJfYmFsYW5jZQAAAK8HEAAIAAAANAgQAA0AAABWZXJpZmllclJlc3BvbnNlT2tFcnIAAABkCBAAAgAAAGYIEAADAAAAHwAAAAgAAAAEAAAAIAAAACEAAACQCBAAAAAAAGEgRGlzcGxheSBpbXBsZW1lbnRhdGlvbiByZXR1cm5lZCBhbiBlcnJvciB1bmV4cGVjdGVkbHljb3JlOjpyZXN1bHQ6OlJlc3VsdDxjb3JlOjpyZXN1bHQ6OlJlc3VsdDxjb3Ntd2FzbV9zdGQ6OmVuY29kaW5nOjpCaW5hcnksIGNvc213YXNtX3N0ZDo6ZXJyb3JzOjpzdGRfZXJyb3I6OlN0ZEVycm9yPiwgY29zbXdhc21fc3RkOjplcnJvcnM6OnN5c3RlbV9lcnJvcjo6U3lzdGVtRXJyb3I+AAAAIgAAAAQAAAAEAAAAIwAAACQAAAAlAAAAJgAAAAAAAAABAAAAEwAAACcAAAAUAAAABAAAACgAAAAvcnVzdGMvZjNlMWE5NTRkMmVhZDRlMmZjMTk3YzdkYTdkNzFlNmM2MWJhZDE5Ni9zcmMvbGliY29yZS9vcHMvYXJpdGgucnOwCRAASAAAALsCAAAzAEGQlMAAC+EFYXR0ZW1wdCB0byBhZGQgd2l0aCBvdmVyZmxvdzw6OmNvcmU6Om1hY3Jvczo6cGFuaWMgbWFjcm9zPgAALAoQAB4AAAADAAAACgAAAFRyaWVkIHRvIHNocmluayB0byBhIGxhcmdlciBjYXBhY2l0eW1pc3NpbmcgZmllbGQgYGCAChAADwAAAI8KEAABAAAAZHVwbGljYXRlIGZpZWxkIGAAAACgChAAEQAAAI8KEAABAAAAdW5rbm93biB2YXJpYW50IGBgLCB0aGVyZSBhcmUgbm8gdmFyaWFudHMAAADEChAAEQAAANUKEAAYAAAAYCwgZXhwZWN0ZWQgxAoQABEAAAAACxAADAAAAFNvbWUiAAAABAAAAAQAAAApAAAATm9uZSIAAAAEAAAABAAAACoAAAAvdXNyL2xvY2FsL2NhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvYmFzZTY0LTAuMTEuMC9zcmMvZGVjb2RlLnJzAAAARAsQAFUAAADHAQAAHwAAAEQLEABVAAAAzQEAAB8AAABECxAAVQAAANABAAANAAAARAsQAFUAAADWAQAAHwAAAEQLEABVAAAA2QEAAA0AAABECxAAVQAAAN8BAAAfAAAARAsQAFUAAADiAQAADQAAAEQLEABVAAAA6AEAAB8AAABECxAAVQAAAOsBAAANAAAARAsQAFUAAADxAQAAHwAAAEQLEABVAAAA9AEAAA0AAABECxAAVQAAAPoBAAAfAAAARAsQAFUAAAD9AQAADQAAAEQLEABVAAAAAwIAAB8AAABECxAAVQAAAAYCAAANAAAARAsQAFUAAABtAAAALwAAAAAAAABhdHRlbXB0IHRvIG11bHRpcGx5IHdpdGggb3ZlcmZsb3cAAABECxAAVQAAAPgAAAA3AAAARAsQAFUAAAD5AAAAPgAAAEQLEABVAAAAFgEAABEAQYCawAALlRZhdHRlbXB0IHRvIHN1YnRyYWN0IHdpdGggb3ZlcmZsb3cAAABECxAAVQAAAB8BAAApAAAARAsQAFUAAAAjAQAAKQAAAEQLEABVAAAAIwEAACgAAABECxAAVQAAACgBAAARAAAARAsQAFUAAAA5AQAAJwAAAEQLEABVAAAAPAEAAAkAAABECxAAVQAAAFsBAAApAAAARAsQAFUAAABtAQAADQAAAEQLEABVAAAAdwEAABEAAABECxAAVQAAAH8BAAAVAAAARAsQAFUAAACDAQAAMQAAAGludGVybmFsIGVycm9yOiBlbnRlcmVkIHVucmVhY2hhYmxlIGNvZGU6IAAA1A0QACoAAABJbXBvc3NpYmxlOiBtdXN0IG9ubHkgaGF2ZSAwIHRvIDggaW5wdXQgYnl0ZXMgaW4gbGFzdCBjaHVuaywgd2l0aCBubyBpbnZhbGlkIGxlbmd0aHMIDhAAVAAAAEQLEABVAAAAkgEAAA4AAABECxAAVQAAAJ0BAAANAAAARAsQAFUAAACmAQAACQAAAE92ZXJmbG93IHdoZW4gY2FsY3VsYXRpbmcgb3V0cHV0IGJ1ZmZlciBsZW5ndGhJbnZhbGlkIFVURjgvdXNyL2xvY2FsL2NhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvYmFzZTY0LTAuMTEuMC9zcmMvZW5jb2RlLnJzAM4OEABVAAAALwAAABEAAABpbnRlZ2VyIG92ZXJmbG93IHdoZW4gY2FsY3VsYXRpbmcgYnVmZmVyIHNpemV1c2l6ZSBvdmVyZmxvdyB3aGVuIGNhbGN1bGF0aW5nIGJ1ZmZlciBzaXplHwAAAAgAAAAEAAAAKwAAAGludmFsaWQgYmFzZTY0OiCcDxAAEAAAAFVuc3VwcG9ydCBxdWVyeSB0eXBlOiAAALQPEAAWAAAAVW5rbm93biBzeXN0ZW0gZXJyb3LUDxAAFAAAAE5vIHN1Y2ggY29udHJhY3Q6IAAA8A8QABIAAABDYW5ub3QgcGFyc2UgcmVxdWVzdDogAAAMEBAAFgAAAEFsbG9jYXRlZCBtZW1vcnkgdG9vIHNtYWxsIHRvIGhvbGQgdGhlIGRhdGFiYXNlIHZhbHVlIGZvciB0aGUgZ2l2ZW4ga2V5LiBZb3UgY2FuIHNwZWNpZnkgY3VzdG9tIHJlc3VsdCBidWZmZXIgbGVuZ3RocyBieSB1c2luZyBFeHRlcm5hbFN0b3JhZ2UuZ2V0X3dpdGhfcmVzdWx0X2xlbmd0aCBleHBsaWNpdGVseS5FcnJvciByZWFkaW5nIGZyb20gZGF0YWJhc2UuIEVycm9yIGNvZGU6IADeEBAAKQAAAEVycm9yIHdyaXRpbmcgdG8gZGF0YWJhc2UuIEVycm9yIGNvZGU6IAAQERAAJwAAAGNhbm9uaWNhbGl6ZV9hZGRyZXNzIHJldHVybmVkIGVycm9yaHVtYW5pemVfYWRkcmVzcyByZXR1cm5lZCBlcnJvcmdlbmVyaWNfZXJybXNnaW52YWxpZF9iYXNlNjRpbnZhbGlkX3V0Zjhub3RfZm91bmRraW5kbnVsbF9wb2ludGVycGFyc2VfZXJydGFyZ2V0c2VyaWFsaXplX2VycnNvdXJjZXVuYXV0aG9yaXplZHVuZGVyZmxvd21pbnVlbmRzdWJ0cmFoZW5kQ2Fubm90IHN1YnRyYWN0ICBmcm9tIAAAAAsSEAAQAAAAGxIQAAYAAABVbmF1dGhvcml6ZWQ0EhAADAAAAEVycm9yIHNlcmlhbGl6aW5nIHR5cGUgOiAAAABIEhAAFwAAAF8SEAACAAAARXJyb3IgcGFyc2luZyBpbnRvIHR5cGUgdBIQABgAAABfEhAAAgAAAFJlY2VpdmVkIG51bGwgcG9pbnRlciwgcmVmdXNlIHRvIHVzZZwSEAAkAAAAIG5vdCBmb3VuZAAAkAgQAAAAAADIEhAACgAAAENhbm5vdCBkZWNvZGUgVVRGOCBieXRlcyBpbnRvIHN0cmluZzogAADkEhAAJgAAAEludmFsaWQgQmFzZTY0IHN0cmluZzogABQTEAAXAAAAR2VuZXJpYyBlcnJvcjogADQTEAAPAAAATnVsbFBvaW50ZXIAghEQAAsAAACQERAADgAAAJ4REAAMAAAAqhEQAAkAAAC3ERAADAAAAMMREAAJAAAA0hEQAA0AAADlERAADAAAAPEREAAJAAAAVW5kZXJmbG93YmFja3RyYWNlAAAiAAAABAAAAAQAAAAsAAAAU2VyaWFsaXplRXJyUGFyc2VFcnJOb3RGb3VuZEludmFsaWRVdGY4SW52YWxpZEJhc2U2NEdlbmVyaWNFcnJpbnZhbGlkX3JlcXVlc3RlcnJvcm5vX3N1Y2hfY29udHJhY3RhZGRydW5rbm93bnVuc3VwcG9ydGVkX3JlcXVlc3QCFBAADwAAABYUEAAQAAAAKhQQAAcAAAAxFBAAEwAAAC8AAAAIAAAABAAAADAAAAAxAAAALwAAAAgAAAAEAAAAMgAAAIgUEAAAAAAASW52YWxpZCBKU09OSlNPTiBoYXMgYSBjb21tYSBhZnRlciB0aGUgbGFzdCB2YWx1ZSBpbiBhbiBhcnJheSBvciBtYXAuSlNPTiBoYXMgbm9uLXdoaXRlc3BhY2UgdHJhaWxpbmcgY2hhcmFjdGVycyBhZnRlciB0aGUgdmFsdWUuRm91bmQgYSBsb25lIHN1cnJvZ2F0ZSwgd2hpY2ggY2FuIGV4aXN0IGluIEpTT04gYnV0IGNhbm5vdCBiZSBlbmNvZGVkIHRvIFVURi04Lk9iamVjdCBrZXkgaXMgbm90IGEgc3RyaW5nLkludmFsaWQgdW5pY29kZSBjb2RlIHBvaW50LkludmFsaWQgdHlwZUludmFsaWQgbnVtYmVyLkludmFsaWQgZXNjYXBlIHNlcXVlbmNlLkV4cGVjdGVkIHRoaXMgY2hhcmFjdGVyIHRvIHN0YXJ0IGEgSlNPTiB2YWx1ZS5FeHBlY3RlZCB0byBwYXJzZSBlaXRoZXIgYSBgdHJ1ZWAsIGBmYWxzZWAsIG9yIGEgYG51bGxgLkV4cGVjdGVkIHRoaXMgY2hhcmFjdGVyIHRvIGJlIGVpdGhlciBhIGAnLCdgIG9yIGEgYCd9J2AuRXhwZWN0ZWQgYSBsb3cgc3Vycm9nYXRlIChEQzAw4oCTREZGRikuRXhwZWN0ZWQgdGhpcyBjaGFyYWN0ZXIgdG8gYmUgZWl0aGVyIGEgYCcsJ2Agb3JhIGAnXSdgLkV4cGVjdGVkIGEgaGlnaCBzdXJyb2dhdGUgKEQ4MDDigJNEQkZGKS5FeHBlY3RlZCB0aGlzIGNoYXJhY3RlciB0byBiZSBhIGAnOidgLkVPRiB3aGlsZSBwYXJzaW5nIGEgSlNPTiB2YWx1ZS5FT0Ygd2hpbGUgcGFyc2luZyBhIHN0cmluZy5FT0Ygd2hpbGUgcGFyc2luZyBhbiBvYmplY3QuRU9GIHdoaWxlIHBhcnNpbmcgYSBsaXN0LkNvbnRyb2wgY2hhcmFjdGVyIGZvdW5kIGluIHN0cmluZy4vdXNyL2xvY2FsL2NhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvc2VyZGUtanNvbi13YXNtLTAuMi4xL3NyYy9kZS91bmVzY2FwZS5ycwAApBcQAGIAAAAlAAAAFQBBoLDAAAvxA2F0dGVtcHQgdG8gYWRkIHdpdGggb3ZlcmZsb3ekFxAAYgAAADMAAAApAAAAAAAAAGF0dGVtcHQgdG8gc3VidHJhY3Qgd2l0aCBvdmVyZmxvdwAAAKQXEABiAAAAmQAAAA4AAABOb24taGV4IEFTQ0lJIGNoYXJhY3RlciBmb3VuZC91c3IvbG9jYWwvY2FyZ28vcmVnaXN0cnkvc3JjL2dpdGh1Yi5jb20tMWVjYzYyOTlkYjllYzgyMy9zZXJkZS1qc29uLXdhc20tMC4yLjEvc3JjL2RlL21vZC5ycwAAoRgQAF0AAAAeAAAACQAAAKEYEABdAAAAewAAACsAAAChGBAAXQAAAHsAAAAgAAAAoRgQAF0AAAB8AAAAIQAAAFVua25vd24gc2VyaWFsaXphdGlvbiBlcnJvcgBAGRAAGwAAAEJ1ZmZlciBpcyBmdWxsAABkGRAADgAAAC91c3IvbG9jYWwvY2FyZ28vcmVnaXN0cnkvc3JjL2dpdGh1Yi5jb20tMWVjYzYyOTlkYjllYzgyMy9zZXJkZS1qc29uLXdhc20tMC4yLjEvc3JjL3Nlci9tb2QucnMAAHwZEABeAAAA0QAAAAkAAAAAAAAAYXR0ZW1wdCB0byBzdWJ0cmFjdCB3aXRoIG92ZXJmbG93AEGgtMAAC4skYXR0ZW1wdCB0byBhZGQgd2l0aCBvdmVyZmxvd3VzaXplIG92ZXJmbG93IHdoZW4gY2FsY3VsYXRpbmcgYjY0IGxlbmd0aC91c3IvbG9jYWwvY2FyZ28vcmVnaXN0cnkvc3JjL2dpdGh1Yi5jb20tMWVjYzYyOTlkYjllYzgyMy9iYXNlNjQtMC4xMS4wL3NyYy9lbmNvZGUucnMAZhoQAFUAAAC7AAAAMwAAAGYaEABVAAAAvAAAADoAAABmGhAAVQAAAPwAAAAYAAAAZhoQAFUAAAABAQAALwAAAGYaEABVAAAAAgEAADYAAABmGhAAVQAAABABAAAuAAAAZhoQAFUAAAAQAQAACQAAAGYaEABVAAAAEgEAAA8AAABmGhAAVQAAABEBAAAJAAAAZhoQAFUAAAAUAQAACQAAAGYaEABVAAAAFgEAAAkAAABmGhAAVQAAABgBAAAuAAAAZhoQAFUAAAAYAQAACQAAAGYaEABVAAAAGQEAAAkAAABpbnRlcm5hbCBlcnJvcjogZW50ZXJlZCB1bnJlYWNoYWJsZSBjb2RlOiAAAJwbEAAqAAAASW1wb3NzaWJsZSByZW1haW5kZXLQGxAAFAAAAGYaEABVAAAALwEAABYAAABmGhAAVQAAAEABAAAJAAAASW52YWxpZCBsYXN0IHN5bWJvbCAsIG9mZnNldCAuAAAMHBAAFAAAACAcEAAJAAAAKRwQAAEAAABFbmNvZGVkIHRleHQgY2Fubm90IGhhdmUgYSA2LWJpdCByZW1haW5kZXIuAEQcEAArAAAASW52YWxpZCBieXRlIAAAAHgcEAANAAAAIBwQAAkAAAApHBAAAQAAAE92ZXJmbG93IHdoZW4gY2FsY3VsYXRpbmcgbnVtYmVyIG9mIGNodW5rcyBpbiBpbnB1dC4vMDEyMzQ1Njc4OUFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6MDEyMzQ1Njc4OS1fQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrL/////////////////////////////////////////////////////////////8AAQIDBAUGBwgJCgv/////////DA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCX///////8mJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8+//80NTY3ODk6Ozw9/////////wABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ/////z//GhscHR4fICEiIyQlJicoKSorLC0uLzAxMjP//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////z7///8/NDU2Nzg5Ojs8Pf////////8AAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGf///////xobHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIz/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBTHRAAEx0QANMcEACTHxAAkx4QAJMdEAA2AAAABAAAAAQAAAA3AAAAOAAAACgpL3J1c3RjL2YzZTFhOTU0ZDJlYWQ0ZTJmYzE5N2M3ZGE3ZDcxZTZjNjFiYWQxOTYvc3JjL2xpYmNvcmUvbWFjcm9zL21vZC5ycwDCIBAASQAAAA8AAAAoAAAAY2FsbGVkIGBPcHRpb246OnVud3JhcCgpYCBvbiBhIGBOb25lYCB2YWx1ZVJVU1RfQkFDS1RSQUNFMQAAVSEQAAEAAABSVVNUX0xJQl9CQUNLVFJBQ0VCYWNrdHJhY2UANgAAAAQAAAAEAAAAOQAAADwAAAAIAAAABAAAAD0AAAA+AAAAPAAAAAgAAAAEAAAAPwAAAGBvbmUgb2YgsSEQAAcAAAAsIAAAwCEQAAIAAACwIRAAAQAAALAhEAABAAAAYCBvciBgAACwIRAAAQAAANwhEAAGAAAAsCEQAAEAAAAvdXNyL2xvY2FsL2NhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvc2VyZGUtMS4wLjEwNC9zcmMvZGUvbW9kLnJzAAAA/CEQAFUAAADVCAAAEgAAAGV4cGxpY2l0IHBhbmljAABDAAAABAAAAAQAAABEAAAARQAAAEYAAABDAAAABAAAAAQAAABHAAAAYWxyZWFkeSBib3Jyb3dlZGFscmVhZHkgbXV0YWJseSBib3Jyb3dlZC9ydXN0Yy9mM2UxYTk1NGQyZWFkNGUyZmMxOTdjN2RhN2Q3MWU2YzYxYmFkMTk2L3NyYy9saWJjb3JlL21hY3Jvcy9tb2QucnNhc3NlcnRpb24gZmFpbGVkOiBgKGxlZnQgPT0gcmlnaHQpYAogIGxlZnQ6IGBgLAogcmlnaHQ6IGBgAA0jEAAtAAAAOiMQAAwAAABGIxAAAQAAAMQiEABJAAAADwAAACgAAABIAAAAAAAAAAEAAABJAAAAYDogAA0jEAAtAAAAOiMQAAwAAACAIxAAAwAAAGNhbGxlZCBgT3B0aW9uOjp1bndyYXAoKWAgb24gYSBgTm9uZWAgdmFsdWUASAAAAAAAAAABAAAASgAAAEsAAAAQAAAABAAAAEwAAABIAAAAAAAAAAEAAABNAAAAY2FsbGVkIGBSZXN1bHQ6OnVud3JhcCgpYCBvbiBhbiBgRXJyYCB2YWx1ZQBOAAAACAAAAAQAAABPAAAAPDo6Y29yZTo6bWFjcm9zOjpwYW5pYyBtYWNyb3M+AAA0JBAAHgAAAAMAAAAKAAAAVHJpZWQgdG8gc2hyaW5rIHRvIGEgbGFyZ2VyIGNhcGFjaXR5QwAAAAQAAAAEAAAAQQAAAHNyYy9saWJzdGQvdGhyZWFkL21vZC5yc5gkEAAYAAAAiQMAABMAAABpbmNvbnNpc3RlbnQgcGFyayBzdGF0ZQACAAAAcGFyayBzdGF0ZSBjaGFuZ2VkIHVuZXhwZWN0ZWRseQDcJBAAHwAAAJgkEAAYAAAAhgMAAA0AAACYJBAAGAAAAB8EAAARAAAAZmFpbGVkIHRvIGdlbmVyYXRlIHVuaXF1ZSB0aHJlYWQgSUQ6IGJpdHNwYWNlIGV4aGF1c3RlZHRocmVhZCBuYW1lIG1heSBub3QgY29udGFpbiBpbnRlcmlvciBudWxsIGJ5dGVzAACYJBAAGAAAAJQEAAASAAAAaW5jb25zaXN0ZW50IHN0YXRlIGluIHVucGFya3NyYy9saWJzdGQvc3luYy9jb25kdmFyLnJzAAC4JRAAGgAAAEgCAAASAAAAYXR0ZW1wdGVkIHRvIHVzZSBhIGNvbmRpdGlvbiB2YXJpYWJsZSB3aXRoIHR3byBtdXRleGVzc3JjL2xpYnN0ZC9zeW5jL29uY2UucnMAAAAaJhAAFwAAAKgBAAAVAAAAYXNzZXJ0aW9uIGZhaWxlZDogc3RhdGVfYW5kX3F1ZXVlICYgU1RBVEVfTUFTSyA9PSBSVU5OSU5HAAAAGiYQABcAAACMAQAAFQAAAE9uY2UgaW5zdGFuY2UgaGFzIHByZXZpb3VzbHkgYmVlbiBwb2lzb25lZAAAGiYQABcAAADpAQAACQAAAFBvaXNvbkVycm9yIHsgaW5uZXI6IC4uIH0AAABQAAAAEAAAAAQAAABRAAAAUgAAAFMAAAAMAAAABAAAAFQAAABVAAAACAAAAAQAAABWAAAAVwAAAFUAAAAIAAAABAAAAFgAAABOdWxFcnJvckMAAAAEAAAABAAAAFkAAABzcmMvbGlic3RkL3N5cy93YXNtL2NvbmR2YXIucnMAAEgnEAAeAAAAFwAAAAkAAABjYW4ndCBibG9jayB3aXRoIHdlYiBhc3NlbWJseXNyYy9saWJzdGQvc3lzL3dhc20vbXV0ZXgucnMAAACVJxAAHAAAABYAAAAJAAAAY2Fubm90IHJlY3Vyc2l2ZWx5IGFjcXVpcmUgbXV0ZXhaAAAABAAAAAQAAABbAAAAXAAAAF0AAABaAAAABAAAAAQAAABeAAAAL3J1c3RjL2YzZTFhOTU0ZDJlYWQ0ZTJmYzE5N2M3ZGE3ZDcxZTZjNjFiYWQxOTYvc3JjL2xpYmNvcmUvZm10L21vZC5ycwAADCgQAEYAAABaAQAAEwAAAF8AAAAAAAAAAQAAABMAAABzcmMvbGliYWxsb2MvcmF3X3ZlYy5yc2NhcGFjaXR5IG92ZXJmbG93dCgQABcAAAAJAwAABQAAAGEgZm9ybWF0dGluZyB0cmFpdCBpbXBsZW1lbnRhdGlvbiByZXR1cm5lZCBhbiBlcnJvckZyb21VdGY4RXJyb3JieXRlcwAAAFoAAAAEAAAABAAAAGAAAABlcnJvcgAAAFoAAAAEAAAABAAAAGEAAABgbnVtYmVyIHdvdWxkIGJlIHplcm8gZm9yIG5vbi16ZXJvIHR5cGVudW1iZXIgdG9vIHNtYWxsIHRvIGZpdCBpbiB0YXJnZXQgdHlwZW51bWJlciB0b28gbGFyZ2UgdG8gZml0IGluIHRhcmdldCB0eXBlaW52YWxpZCBkaWdpdCBmb3VuZCBpbiBzdHJpbmdjYW5ub3QgcGFyc2UgaW50ZWdlciBmcm9tIGVtcHR5IHN0cmluZy4u0ikQAAIAAABCb3Jyb3dFcnJvckJvcnJvd011dEVycm9yAAAAZwAAAAAAAAABAAAAaAAAAGluZGV4IG91dCBvZiBib3VuZHM6IHRoZSBsZW4gaXMgIGJ1dCB0aGUgaW5kZXggaXMgAAAIKhAAIAAAACgqEAASAAAAY2FsbGVkIGBPcHRpb246OnVud3JhcCgpYCBvbiBhIGBOb25lYCB2YWx1ZXNyYy9saWJjb3JlL29wdGlvbi5yc3cqEAAVAAAAfQEAABUAAAAcKRAAAAAAAHcqEAAVAAAApAQAAAUAAAA6IAAAHCkQAAAAAAC0KhAA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AGAAAAAAAAAAAKDDB/jnDwAAADwAABwAAAAAAAAA////////f/j//////x8gABAAAPj+/wAAf///+dsHAAAAAAAAAPAAAAAAfwAAAAAA8AcAQcz1wAALEv///////////////////////wBBwPbAAAsO//////////////////8AQfD2wAALAvgDAEGS98AACwf+/////7+2AEGi98AACw3/BwAAAAAA+P//AAABAEG698AACxDAn589AAAAAAIAAAD///8HAEHU98AAC9EKwP8BAAAAAAAA+A8gwDQQAEoAAAAQNxAAAAIAABA5EAA6AAAAAAECAwQFBgcICQgKCwwNDg8QERITFAIVFhcYGRobHB0eHyACAgICAgICAgICIQICAgICAgICAgICAgICIiMkJSYCJwIoAgICKSorAiwtLi8wAgIxAgICMgICAgICAgICMwICNAICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICNQI2AjcCAgICAgICAjgCOQICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICOjs8AgICAj0CAj4/QEFCQ0RFRgICAkcCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICSAICAgICAgICAgICSQICAgICOwIAAQICAgIDAgICAgQCBQYCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICbQAAAAQAAAAEAAAAcgAAAFNvbWVOb25lVXRmOEVycm9ydmFsaWRfdXBfdG9lcnJvcl9sZW4AAABtAAAABAAAAAQAAABzAAAARXJyb3IAQaCGwQALAQE=" -} diff --git a/packages/sdk/src/testdata/cosmoshub.json b/packages/sdk/src/testdata/cosmoshub.json deleted file mode 100644 index cb33539c..00000000 --- a/packages/sdk/src/testdata/cosmoshub.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "//source": "https://hubble.figment.network/cosmos/chains/cosmoshub-3/blocks/415777/transactions/2BD600EA6090FC75FD844CA73542CC90A828770F4C01C5B483C3C1C43CCB65F4?format=json", - "tx": { - "type": "cosmos-sdk/StdTx", - "value": { - "msg": [ - { - "type": "cosmos-sdk/MsgSend", - "value": { - "from_address": "cosmos1txqfn5jmcts0x0q7krdxj8tgf98tj0965vqlmq", - "to_address": "cosmos1nynns8ex9fq6sjjfj8k79ymkdz4sqth06xexae", - "amount": [ - { - "denom": "uatom", - "amount": "35997500" - } - ] - } - } - ], - "fee": { - "amount": [ - { - "denom": "uatom", - "amount": "2500" - } - ], - "gas": "100000" - }, - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A5qFcJBJvEK/fOmEAY0DHNWwSRZ9TEfNZyH8VoVvDtAq" - }, - "signature": "NK1Oy4EUGAsoC03c1wi9GG03JC/39LEdautC5Jk643oIbEPqeXHMwaqbdvO/Jws0X/NAXaN8SAy2KNY5Qml+5Q==" - } - ], - "memo": "" - } - }, - "tx_data": "ygEoKBapCkOoo2GaChRZgJnSW8Lg8zwesNppHWhJTrk8uhIUmSc4HyYqQahKSZHt4pN2aKsALu8aEQoFdWF0b20SCDM1OTk3NTAwEhMKDQoFdWF0b20SBDI1MDAQoI0GGmoKJuta6YchA5qFcJBJvEK/fOmEAY0DHNWwSRZ9TEfNZyH8VoVvDtAqEkA0rU7LgRQYCygLTdzXCL0YbTckL/f0sR1q60LkmTrjeghsQ+p5cczBqpt2878nCzRf80Bdo3xIDLYo1jlCaX7l", - "id": "2BD600EA6090FC75FD844CA73542CC90A828770F4C01C5B483C3C1C43CCB65F4" -} diff --git a/packages/sdk/src/testdata/txresponse1.json b/packages/sdk/src/testdata/txresponse1.json deleted file mode 100644 index 703ea696..00000000 --- a/packages/sdk/src/testdata/txresponse1.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "height": "15888", - "txhash": "672DEDE8EF4DE8B5818959F417CCA357079D4D7A19C4B65443C7FBF8176AABF9", - "raw_log": "[{\"msg_index\":0,\"log\":\"\",\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2\"},{\"key\":\"amount\",\"value\":\"75000ucosm\"}]}]}]", - "logs": [ - { - "msg_index": 0, - "log": "", - "events": [ - { - "type": "message", - "attributes": [ - { "key": "action", "value": "send" }, - { "key": "sender", "value": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6" }, - { "key": "module", "value": "bank" } - ] - }, - { - "type": "transfer", - "attributes": [ - { "key": "recipient", "value": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2" }, - { "key": "amount", "value": "75000ucosm" } - ] - } - ] - } - ], - "gas_wanted": "200000", - "gas_used": "65407", - "tx": { - "type": "cosmos-sdk/StdTx", - "value": { - "msg": [ - { - "type": "cosmos-sdk/MsgSend", - "value": { - "from_address": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - "to_address": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2", - "amount": [{ "denom": "ucosm", "amount": "75000" }] - } - } - ], - "fee": { "amount": [{ "denom": "ucosm", "amount": "5000" }], "gas": "200000" }, - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ" - }, - "signature": "US7oH8S/8TxVrtBQkOhHxAM+oDB2spNAEawgh6H8CCFLRMOJK+uvQZZ6ceUgUsvDbxwCz7re1RU272fymMYRZQ==" - } - ], - "memo": "My first payment" - } - }, - "timestamp": "2020-02-14T11:25:55Z" -} diff --git a/packages/sdk/src/testdata/txresponse2.json b/packages/sdk/src/testdata/txresponse2.json deleted file mode 100644 index 0d1a37f9..00000000 --- a/packages/sdk/src/testdata/txresponse2.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "height": "16456", - "txhash": "7BFE4B93AF190F60132C62D08FDF50BE462FBCE374EB13D3FD0C32461E771EC0", - "raw_log": "[{\"msg_index\":0,\"log\":\"\",\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2\"},{\"key\":\"amount\",\"value\":\"75000ucosm\"}]}]}]", - "logs": [ - { - "msg_index": 0, - "log": "", - "events": [ - { - "type": "message", - "attributes": [ - { "key": "action", "value": "send" }, - { "key": "sender", "value": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6" }, - { "key": "module", "value": "bank" } - ] - }, - { - "type": "transfer", - "attributes": [ - { "key": "recipient", "value": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2" }, - { "key": "amount", "value": "75000ucosm" } - ] - } - ] - } - ], - "gas_wanted": "200000", - "gas_used": "65407", - "tx": { - "type": "cosmos-sdk/StdTx", - "value": { - "msg": [ - { - "type": "cosmos-sdk/MsgSend", - "value": { - "from_address": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - "to_address": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2", - "amount": [{ "denom": "ucosm", "amount": "75000" }] - } - } - ], - "fee": { "amount": [{ "denom": "ucosm", "amount": "5000" }], "gas": "200000" }, - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ" - }, - "signature": "ltvd9Rb3RF4zjbUVrpDpkok34g+py7XR8ZcM0tZUYRxxVdcMEin010x+ZFd/mOuutPj9fDmSENnienc/yi4msw==" - } - ], - "memo": "My first payment" - } - }, - "timestamp": "2020-02-14T11:35:41Z" -} diff --git a/packages/sdk/src/testdata/txresponse3.json b/packages/sdk/src/testdata/txresponse3.json deleted file mode 100644 index 8ffd0727..00000000 --- a/packages/sdk/src/testdata/txresponse3.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "height": "20730", - "txhash": "625BC75E697F73DA037387C34002BB2F682E7ACDCC4E015D3E90420516C6D0C8", - "raw_log": "[{\"msg_index\":0,\"log\":\"\",\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2\"},{\"key\":\"amount\",\"value\":\"75000ucosm\"}]}]}]", - "logs": [ - { - "msg_index": 0, - "log": "", - "events": [ - { - "type": "message", - "attributes": [ - { "key": "action", "value": "send" }, - { "key": "sender", "value": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6" }, - { "key": "module", "value": "bank" } - ] - }, - { - "type": "transfer", - "attributes": [ - { "key": "recipient", "value": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2" }, - { "key": "amount", "value": "75000ucosm" } - ] - } - ] - } - ], - "gas_wanted": "200000", - "gas_used": "65407", - "tx": { - "type": "cosmos-sdk/StdTx", - "value": { - "msg": [ - { - "type": "cosmos-sdk/MsgSend", - "value": { - "from_address": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - "to_address": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2", - "amount": [{ "denom": "ucosm", "amount": "75000" }] - } - } - ], - "fee": { "amount": [{ "denom": "ucosm", "amount": "5000" }], "gas": "200000" }, - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ" - }, - "signature": "eOFGl1tIHDMv3JdCK9fRSikVbYUD8+B0ksb3dJFya8MPYgpEpdSA7zZc+5n/cW6LR/BJdib4nqmJQv1yD9lm3g==" - } - ], - "memo": "My first payment" - } - }, - "timestamp": "2020-02-14T12:48:56Z" -} diff --git a/packages/sdk/src/testutils.spec.ts b/packages/sdk/src/testutils.spec.ts deleted file mode 100644 index 888f5954..00000000 --- a/packages/sdk/src/testutils.spec.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Random } from "@iov/crypto"; -import { Bech32, Encoding } from "@iov/encoding"; - -import hackatom from "./testdata/contract.json"; - -export function getHackatom(): Uint8Array { - return Encoding.fromBase64(hackatom.data); -} - -export function makeRandomAddress(): string { - return Bech32.encode("cosmos", Random.getBytes(20)); -} - -export const nonNegativeIntegerMatcher = /^[0-9]+$/; -export const tendermintIdMatcher = /^[0-9A-F]{64}$/; -export const tendermintOptionalIdMatcher = /^([0-9A-F]{64}|)$/; -export const tendermintAddressMatcher = /^[0-9A-F]{40}$/; -export const tendermintShortHashMatcher = /^[0-9a-f]{40}$/; -export const semverMatcher = /^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$/; - -// 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}$/; - -/** Deployed as part of scripts/wasmd/init.sh */ -export const deployedErc20 = { - codeId: 1, - source: "https://crates.io/api/v1/crates/cw-erc20/0.4.0/download", - builder: "cosmwasm/rust-optimizer:0.8.0", - checksum: "41b3bafd7f9a3870bbfb0a0620508df564c52499cdcdc67bf9df72262f3958a6", - instances: [ - "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", // HASH - "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd", // ISA - "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c", // JADE - ], -}; - -export const wasmd = { - endpoint: "http://localhost:1317", - chainId: "testing", -}; - -export const faucet = { - 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", - pubkey: { - type: "tendermint/PubKeySecp256k1", - value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", - }, - address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", -}; - -/** Unused account */ -export const unused = { - pubkey: { - type: "tendermint/PubKeySecp256k1", - value: "ArkCaFUJ/IH+vKBmNRCdUVl3mCAhbopk9jjW4Ko4OfRQ", - }, - address: "cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u", - accountNumber: 9, - sequence: 0, -}; - -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]; -} diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts deleted file mode 100644 index 0ab70b02..00000000 --- a/packages/sdk/src/types.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { Encoding } from "@iov/encoding"; - -import { Coin } from "./coins"; - -const { fromBase64, fromHex } = Encoding; - -/** An Amino/Cosmos SDK StdTx */ -export interface StdTx { - readonly msg: ReadonlyArray; - readonly fee: StdFee; - readonly signatures: ReadonlyArray; - readonly memo: string | undefined; -} - -export function isStdTx(txValue: unknown): txValue is StdTx { - const { memo, msg, fee, signatures } = txValue as StdTx; - return ( - typeof memo === "string" && Array.isArray(msg) && typeof fee === "object" && Array.isArray(signatures) - ); -} - -export interface CosmosSdkTx { - readonly type: string; - readonly value: StdTx; -} - -interface MsgTemplate { - readonly type: string; - readonly value: any; -} - -/** A Cosmos SDK token transfer message */ -export interface MsgSend extends MsgTemplate { - readonly type: "cosmos-sdk/MsgSend"; - readonly value: { - /** Bech32 account address */ - readonly from_address: string; - /** Bech32 account address */ - readonly to_address: string; - readonly amount: ReadonlyArray; - }; -} - -/** - * Uploads Wam code to the chain - * - * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L17 - */ -export interface MsgStoreCode extends MsgTemplate { - readonly type: "wasm/store-code"; - readonly value: { - /** Bech32 account address */ - readonly sender: string; - /** Base64 encoded Wasm */ - readonly wasm_byte_code: string; - /** A valid URI reference to the contract's source code. Can be empty. */ - readonly source: string; - /** A docker tag. Can be empty. */ - readonly builder: string; - }; -} - -/** - * Creates an instance of contract that was uploaded before. - * - * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L73 - */ -export interface MsgInstantiateContract extends MsgTemplate { - readonly type: "wasm/instantiate"; - readonly 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; - /** Init message as JavaScript object */ - readonly init_msg: any; - readonly init_funds: ReadonlyArray; - }; -} - -/** - * Creates an instance of contract that was uploaded before. - * - * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L103 - */ -export interface MsgExecuteContract extends MsgTemplate { - readonly type: "wasm/execute"; - readonly value: { - /** Bech32 account address */ - readonly sender: string; - /** Bech32 account address */ - readonly contract: string; - /** Handle message as JavaScript object */ - readonly msg: any; - readonly sent_funds: ReadonlyArray; - }; -} - -export type Msg = MsgSend | MsgStoreCode | MsgInstantiateContract | MsgExecuteContract | MsgTemplate; - -export function isMsgSend(msg: Msg): msg is MsgSend { - return (msg as MsgSend).type === "cosmos-sdk/MsgSend"; -} - -export function isMsgStoreCode(msg: Msg): msg is MsgStoreCode { - return (msg as MsgStoreCode).type === "wasm/store-code"; -} - -export function isMsgInstantiateContract(msg: Msg): msg is MsgInstantiateContract { - return (msg as MsgInstantiateContract).type === "wasm/instantiate"; -} - -export function isMsgExecuteContract(msg: Msg): msg is MsgExecuteContract { - return (msg as MsgExecuteContract).type === "wasm/execute"; -} - -export interface StdFee { - readonly amount: ReadonlyArray; - readonly gas: string; -} - -export interface StdSignature { - readonly pub_key: PubKey; - readonly signature: string; -} - -export interface PubKey { - // type is one of the strings defined in pubkeyTypes - // I don't use a string literal union here as that makes trouble with json test data: - // https://github.com/confio/cosmwasm-js/pull/44#pullrequestreview-353280504 - readonly type: string; - // Value field is base64-encoded in all cases - // Note: if type is Secp256k1, this must contain a COMPRESSED pubkey - to encode from bcp/keycontrol land, you must compress it first - readonly value: string; -} - -export const pubkeyType = { - /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/ed25519/ed25519.go#L22 */ - secp256k1: "tendermint/PubKeySecp256k1" as const, - /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/secp256k1/secp256k1.go#L23 */ - ed25519: "tendermint/PubKeyEd25519" as const, - /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12 */ - sr25519: "tendermint/PubKeySr25519" as const, -}; - -export const pubkeyTypes: readonly string[] = [pubkeyType.secp256k1, pubkeyType.ed25519, pubkeyType.sr25519]; - -export interface WasmData { - // key is hex-encoded - readonly key: string; - // value is base64 encoded - readonly val: string; -} - -// Model is a parsed WasmData object -export interface Model { - readonly key: Uint8Array; - readonly val: Uint8Array; -} - -export function parseWasmData({ key, val }: WasmData): Model { - return { - key: fromHex(key), - val: fromBase64(val), - }; -} - -/** - * An object containing a parsed JSON document. The result of JSON.parse(). - * This doen't privide any type safety over `any` but expresses intent in the code. - */ -export type JsonObject = any; diff --git a/packages/sdk/tsconfig.json b/packages/sdk/tsconfig.json deleted file mode 100644 index 167e8c02..00000000 --- a/packages/sdk/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "baseUrl": ".", - "outDir": "build", - "declarationDir": "build/types", - "rootDir": "src" - }, - "include": [ - "src/**/*" - ] -} diff --git a/packages/sdk/typedoc.js b/packages/sdk/typedoc.js deleted file mode 100644 index e2387c7d..00000000 --- a/packages/sdk/typedoc.js +++ /dev/null @@ -1,14 +0,0 @@ -const packageJson = require("./package.json"); - -module.exports = { - src: ["./src"], - out: "docs", - exclude: "**/*.spec.ts", - target: "es6", - name: `${packageJson.name} Documentation`, - readme: "README.md", - mode: "file", - excludeExternals: true, - excludeNotExported: true, - excludePrivate: true, -}; diff --git a/packages/sdk/types/address.d.ts b/packages/sdk/types/address.d.ts deleted file mode 100644 index 9eb5af82..00000000 --- a/packages/sdk/types/address.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { PubKey } from "./types"; -export declare function rawSecp256k1PubkeyToAddress(pubkeyRaw: Uint8Array, prefix: string): string; -export declare function pubkeyToAddress(pubkey: PubKey, prefix: string): string; diff --git a/packages/sdk/types/builder.d.ts b/packages/sdk/types/builder.d.ts deleted file mode 100644 index 68e18c0a..00000000 --- a/packages/sdk/types/builder.d.ts +++ /dev/null @@ -1 +0,0 @@ -export declare function isValidBuilder(builder: string): boolean; diff --git a/packages/sdk/types/coins.d.ts b/packages/sdk/types/coins.d.ts deleted file mode 100644 index 9208f3e2..00000000 --- a/packages/sdk/types/coins.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface Coin { - readonly denom: string; - readonly amount: string; -} -/** Creates a coin */ -export declare function coin(amount: number, denom: string): Coin; -/** Creates a list of coins with one element */ -export declare function coins(amount: number, denom: string): Coin[]; diff --git a/packages/sdk/types/cosmwasmclient.d.ts b/packages/sdk/types/cosmwasmclient.d.ts deleted file mode 100644 index 12ac73ec..00000000 --- a/packages/sdk/types/cosmwasmclient.d.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { Coin } from "./coins"; -import { Log } from "./logs"; -import { BroadcastMode, RestClient } from "./restclient"; -import { CosmosSdkTx, JsonObject, PubKey, StdTx } from "./types"; -export interface GetNonceResult { - readonly accountNumber: number; - readonly sequence: number; -} -export interface Account { - /** Bech32 account address */ - readonly address: string; - readonly balance: ReadonlyArray; - readonly pubkey: PubKey | undefined; - readonly accountNumber: number; - readonly sequence: number; -} -export interface PostTxResult { - readonly logs: readonly Log[]; - readonly rawLog: string; - /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ - readonly transactionHash: string; -} -export interface SearchByIdQuery { - readonly id: string; -} -export interface SearchByHeightQuery { - readonly height: number; -} -export interface SearchBySentFromOrToQuery { - readonly sentFromOrTo: string; -} -/** - * This query type allows you to pass arbitrary key/value pairs to the backend. It is - * more powerful and slightly lower level than the other search options. - */ -export interface SearchByTagsQuery { - readonly tags: readonly { - readonly key: string; - readonly value: string; - }[]; -} -export declare type SearchTxQuery = - | SearchByIdQuery - | SearchByHeightQuery - | SearchBySentFromOrToQuery - | SearchByTagsQuery; -export interface SearchTxFilter { - readonly minHeight?: number; - readonly maxHeight?: number; -} -export interface Code { - readonly id: number; - /** Bech32 account address */ - readonly creator: string; - /** Hex-encoded sha256 hash of the code stored here */ - readonly checksum: string; - readonly source?: string; - readonly builder?: string; -} -export interface CodeDetails extends Code { - /** The original wasm bytes */ - readonly data: Uint8Array; -} -export interface Contract { - readonly address: string; - readonly codeId: number; - /** Bech32 account address */ - readonly creator: string; - readonly label: string; -} -export interface ContractDetails extends Contract { - /** Argument passed on initialization of the contract */ - readonly initMsg: object; -} -/** A transaction that is indexed as part of the transaction history */ -export interface IndexedTx { - readonly height: number; - /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ - readonly hash: string; - /** Transaction execution error code. 0 on success. */ - readonly code: number; - readonly rawLog: string; - readonly logs: readonly Log[]; - readonly tx: CosmosSdkTx; - /** The gas limit as set by the user */ - readonly gasWanted?: number; - /** The gas used by the execution */ - readonly gasUsed?: number; - /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ - readonly timestamp: string; -} -export interface BlockHeader { - readonly version: { - readonly block: string; - readonly app: string; - }; - readonly height: number; - readonly chainId: string; - /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ - readonly time: string; -} -export interface Block { - /** The ID is a hash of the block header (uppercase hex) */ - readonly id: string; - readonly header: BlockHeader; - /** Array of raw transactions */ - readonly txs: ReadonlyArray; -} -/** Use for testing only */ -export interface PrivateCosmWasmClient { - readonly restClient: RestClient; -} -export declare class CosmWasmClient { - protected readonly restClient: RestClient; - /** Any address the chain considers valid (valid bech32 with proper prefix) */ - protected anyValidAddress: string | undefined; - private readonly codesCache; - private chainId; - /** - * Creates a new client to interact with a CosmWasm blockchain. - * - * This instance does a lot of caching. In order to benefit from that you should try to use one instance - * for the lifetime of your application. When switching backends, a new instance must be created. - * - * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) - * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns - */ - constructor(apiUrl: string, broadcastMode?: BroadcastMode); - getChainId(): Promise; - getHeight(): Promise; - /** - * Returns a 32 byte upper-case hex transaction hash (typically used as the transaction ID) - */ - getIdentifier(tx: CosmosSdkTx): Promise; - /** - * Returns account number and sequence. - * - * Throws if the account does not exist on chain. - * - * @param address returns data for this address. When unset, the client's sender adddress is used. - */ - getNonce(address: string): Promise; - getAccount(address: string): Promise; - /** - * Gets block header and meta - * - * @param height The height of the block. If undefined, the latest height is used. - */ - getBlock(height?: number): Promise; - searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise; - postTx(tx: StdTx): Promise; - getCodes(): Promise; - getCodeDetails(codeId: number): Promise; - getContracts(codeId: number): Promise; - /** - * Throws an error if no contract was found at the address - */ - getContract(address: string): Promise; - /** - * 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. - */ - queryContractRaw(address: string, key: Uint8Array): Promise; - /** - * 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. - */ - queryContractSmart(address: string, queryMsg: object): Promise; - private txsQuery; -} diff --git a/packages/sdk/types/decoding.d.ts b/packages/sdk/types/decoding.d.ts deleted file mode 100644 index fd0c5746..00000000 --- a/packages/sdk/types/decoding.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { StdTx } from "./types"; -export declare function unmarshalTx(data: Uint8Array): StdTx; diff --git a/packages/sdk/types/encoding.d.ts b/packages/sdk/types/encoding.d.ts deleted file mode 100644 index 4d32b80b..00000000 --- a/packages/sdk/types/encoding.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Msg, StdFee, StdTx } from "./types"; -export declare function marshalTx(tx: StdTx): Uint8Array; -export declare function makeSignBytes( - msgs: readonly Msg[], - fee: StdFee, - chainId: string, - memo: string, - accountNumber: number, - sequence: number, -): Uint8Array; diff --git a/packages/sdk/types/index.d.ts b/packages/sdk/types/index.d.ts deleted file mode 100644 index 474b2dd3..00000000 --- a/packages/sdk/types/index.d.ts +++ /dev/null @@ -1,40 +0,0 @@ -import * as logs from "./logs"; -import * as types from "./types"; -export { logs, types }; -export { pubkeyToAddress } from "./address"; -export { Coin, coin, coins } from "./coins"; -export { unmarshalTx } from "./decoding"; -export { makeSignBytes, marshalTx } from "./encoding"; -export { BroadcastMode, RestClient, TxsResponse } from "./restclient"; -export { - Account, - Block, - BlockHeader, - Code, - CodeDetails, - Contract, - ContractDetails, - CosmWasmClient, - GetNonceResult, - IndexedTx, - PostTxResult, - SearchByHeightQuery, - SearchByIdQuery, - SearchBySentFromOrToQuery, - SearchByTagsQuery, - SearchTxQuery, - SearchTxFilter, -} from "./cosmwasmclient"; -export { makeCosmoshubPath, Pen, PrehashType, Secp256k1Pen } from "./pen"; -export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; -export { findSequenceForSignedTx } from "./sequence"; -export { encodeSecp256k1Signature, decodeSignature } from "./signature"; -export { - ExecuteResult, - FeeTable, - InstantiateResult, - SigningCallback, - SigningCosmWasmClient, - UploadMeta, - UploadResult, -} from "./signingcosmwasmclient"; diff --git a/packages/sdk/types/logs.d.ts b/packages/sdk/types/logs.d.ts deleted file mode 100644 index 2e1decb3..00000000 --- a/packages/sdk/types/logs.d.ts +++ /dev/null @@ -1,28 +0,0 @@ -export interface Attribute { - readonly key: string; - readonly value: string; -} -export interface Event { - readonly type: string; - readonly attributes: readonly Attribute[]; -} -export interface Log { - readonly msg_index: number; - readonly log: string; - readonly events: readonly Event[]; -} -export declare function parseAttribute(input: unknown): Attribute; -export declare function parseEvent(input: unknown): Event; -export declare function parseLog(input: unknown): Log; -export declare function parseLogs(input: unknown): readonly Log[]; -/** - * Searches in logs for the first event of the given event type and in that event - * for the first first attribute with the given attribute key. - * - * Throws if the attribute was not found. - */ -export declare function findAttribute( - logs: readonly Log[], - eventType: "message" | "transfer", - attrKey: string, -): Attribute; diff --git a/packages/sdk/types/pen.d.ts b/packages/sdk/types/pen.d.ts deleted file mode 100644 index 068db3cb..00000000 --- a/packages/sdk/types/pen.d.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Slip10RawIndex } from "@iov/crypto"; -import { StdSignature } from "./types"; -export declare type PrehashType = "sha256" | "sha512" | null; -/** - * A pen is the most basic tool you can think of for signing. It works - * everywhere and can be used intuitively by everyone. However, it does not - * come with a great amount of features. End of semi suitable metaphor. - * - * This wraps a single keypair and allows for signing. - * - * Non-goals of this types are: multi account support, persistency, data migrations, - * obfuscation of sensitive data. - */ -export interface Pen { - readonly pubkey: Uint8Array; - readonly sign: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise; -} -/** - * The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a` - * with 0-based account index `a`. - */ -export declare function makeCosmoshubPath(a: number): readonly Slip10RawIndex[]; -export declare class Secp256k1Pen implements Pen { - static fromMnemonic(mnemonic: string, hdPath?: readonly Slip10RawIndex[]): Promise; - readonly pubkey: Uint8Array; - private readonly privkey; - private constructor(); - /** - * Creates and returns a signature - */ - sign(signBytes: Uint8Array, prehashType?: PrehashType): Promise; - address(prefix: string): string; -} diff --git a/packages/sdk/types/pubkey.d.ts b/packages/sdk/types/pubkey.d.ts deleted file mode 100644 index b6ebdebc..00000000 --- a/packages/sdk/types/pubkey.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { PubKey } from "./types"; -export declare function encodeSecp256k1Pubkey(pubkey: Uint8Array): PubKey; -export declare function decodeBech32Pubkey(bechEncoded: string): PubKey; -export declare function encodeBech32Pubkey(pubkey: PubKey, prefix: string): string; diff --git a/packages/sdk/types/restclient.d.ts b/packages/sdk/types/restclient.d.ts deleted file mode 100644 index 5d5deea4..00000000 --- a/packages/sdk/types/restclient.d.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { Coin } from "./coins"; -import { CosmosSdkTx, JsonObject, Model, StdTx } from "./types"; -export interface CosmosSdkAccount { - /** Bech32 account address */ - readonly address: string; - readonly coins: ReadonlyArray; - /** Bech32 encoded pubkey */ - readonly public_key: string; - readonly account_number: number; - readonly sequence: number; -} -export interface NodeInfo { - readonly protocol_version: { - readonly p2p: string; - readonly block: string; - readonly app: string; - }; - readonly id: string; - readonly listen_addr: string; - readonly network: string; - readonly version: string; - readonly channels: string; - readonly moniker: string; - readonly other: { - readonly tx_index: string; - readonly rpc_address: string; - }; -} -export interface ApplicationVersion { - readonly name: string; - readonly server_name: string; - readonly client_name: string; - readonly version: string; - readonly commit: string; - readonly build_tags: string; - readonly go: string; -} -export interface NodeInfoResponse { - readonly node_info: NodeInfo; - readonly application_version: ApplicationVersion; -} -export interface BlockId { - readonly hash: string; -} -export interface BlockHeader { - readonly version: { - readonly block: string; - readonly app: string; - }; - readonly height: string; - readonly chain_id: string; - /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ - readonly time: string; - readonly last_commit_hash: string; - readonly last_block_id: BlockId; - /** Can be empty */ - readonly data_hash: string; - readonly validators_hash: string; - readonly next_validators_hash: string; - readonly consensus_hash: string; - readonly app_hash: string; - /** Can be empty */ - readonly last_results_hash: string; - /** Can be empty */ - readonly evidence_hash: string; - readonly proposer_address: string; -} -export interface Block { - readonly header: BlockHeader; - readonly data: { - /** Array of base64 encoded transactions */ - readonly txs: ReadonlyArray | null; - }; -} -export interface BlockResponse { - readonly block_id: BlockId; - readonly block: Block; -} -interface AuthAccountsResponse { - readonly height: string; - readonly result: { - readonly type: "cosmos-sdk/Account"; - readonly value: CosmosSdkAccount; - }; -} -declare type WasmResponse = WasmSuccess | WasmError; -interface WasmSuccess { - readonly height: string; - readonly result: T; -} -interface WasmError { - readonly error: string; -} -export interface TxsResponse { - readonly height: string; - readonly txhash: string; - /** 🤷‍♂️ */ - readonly codespace?: string; - /** Falsy when transaction execution succeeded. Contains error code on error. */ - readonly code?: number; - readonly raw_log: string; - readonly logs?: object; - readonly tx: CosmosSdkTx; - /** The gas limit as set by the user */ - readonly gas_wanted?: string; - /** The gas used by the execution */ - readonly gas_used?: string; - readonly timestamp: string; -} -interface SearchTxsResponse { - readonly total_count: string; - readonly count: string; - readonly page_number: string; - readonly page_total: string; - readonly limit: string; - readonly txs: readonly TxsResponse[]; -} -export interface PostTxsResponse { - readonly height: string; - readonly txhash: string; - readonly code?: number; - readonly raw_log?: string; - /** The same as `raw_log` but deserialized? */ - readonly logs?: object; - /** The gas limit as set by the user */ - readonly gas_wanted?: string; - /** The gas used by the execution */ - readonly gas_used?: string; -} -interface EncodeTxResponse { - readonly tx: string; -} -export interface CodeInfo { - readonly id: number; - /** Bech32 account address */ - readonly creator: string; - /** Hex-encoded sha256 hash of the code stored here */ - readonly data_hash: string; - readonly source?: string; - readonly builder?: string; -} -export interface CodeDetails extends CodeInfo { - /** Base64 encoded raw wasm data */ - readonly data: string; -} -export interface ContractInfo { - readonly address: string; - readonly code_id: number; - /** Bech32 account address */ - readonly creator: string; - readonly label: string; -} -export interface ContractDetails extends ContractInfo { - /** Argument passed on initialization of the contract */ - readonly init_msg: object; -} -declare type RestClientResponse = - | NodeInfoResponse - | BlockResponse - | AuthAccountsResponse - | TxsResponse - | SearchTxsResponse - | PostTxsResponse - | EncodeTxResponse - | WasmResponse - | WasmResponse - | WasmResponse - | WasmResponse - | WasmResponse; -/** - * The mode used to send transaction - * - * @see https://cosmos.network/rpc/#/Transactions/post_txs - */ -export declare enum BroadcastMode { - /** Return after tx commit */ - Block = "block", - /** Return afer CheckTx */ - Sync = "sync", - /** Return right away */ - Async = "async", -} -export declare class RestClient { - private readonly client; - private readonly broadcastMode; - /** - * Creates a new client to interact with a Cosmos SDK light client daemon. - * This class tries to be a direct mapping onto the API. Some basic decoding and normalizatin is done - * but things like caching are done at a higher level. - * - * When building apps, you should not need to use this class directly. If you do, this indicates a missing feature - * in higher level components. Feel free to raise an issue in this case. - * - * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) - * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns - */ - constructor(apiUrl: string, broadcastMode?: BroadcastMode); - get(path: string): Promise; - post(path: string, params: any): Promise; - authAccounts(address: string): Promise; - blocksLatest(): Promise; - blocks(height: number): Promise; - nodeInfo(): Promise; - txById(id: string): Promise; - txsQuery(query: string): Promise; - /** returns the amino-encoding of the transaction performed by the server */ - encodeTx(tx: CosmosSdkTx): Promise; - /** - * Broadcasts a signed transaction to into the transaction pool. - * Depending on the RestClient's broadcast mode, this might or might - * wait for checkTx or deliverTx to be executed before returning. - * - * @param tx a signed transaction as StdTx (i.e. not wrapped in type/value container) - */ - postTx(tx: StdTx): Promise; - listCodeInfo(): Promise; - getCode(id: number): Promise; - listContractsByCodeId(id: number): Promise; - /** - * Returns null when contract was not found at this address. - */ - getContractInfo(address: string): Promise; - getAllContractState(address: string): Promise; - queryContractRaw(address: string, key: Uint8Array): Promise; - /** - * Makes a smart query on the contract and parses the reponse as JSON. - * Throws error if no such contract exists, the query format is invalid or the response is invalid. - */ - queryContractSmart(address: string, query: object): Promise; -} -export {}; diff --git a/packages/sdk/types/sequence.d.ts b/packages/sdk/types/sequence.d.ts deleted file mode 100644 index 70d38469..00000000 --- a/packages/sdk/types/sequence.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { CosmosSdkTx } from "./types"; -/** - * Serach for sequence s with `min` <= `s` < `upperBound` to find the sequence that was used to sign the transaction - * - * @param tx The signed transaction - * @param chainId The chain ID for which this transaction was signed - * @param accountNumber The account number for which this transaction was signed - * @param upperBound The upper bound for the testing, i.e. sequence must be lower than this value - * @param min The lowest sequence that is tested - * - * @returns the sequence if a match was found and undefined otherwise - */ -export declare function findSequenceForSignedTx( - tx: CosmosSdkTx, - chainId: string, - accountNumber: number, - upperBound: number, - min?: number, -): Promise; diff --git a/packages/sdk/types/signature.d.ts b/packages/sdk/types/signature.d.ts deleted file mode 100644 index c23e7167..00000000 --- a/packages/sdk/types/signature.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { StdSignature } from "./types"; -/** - * Takes a binary pubkey and signature to create a signature object - * - * @param pubkey a compressed secp256k1 public key - * @param signature a 64 byte fixed length representation of secp256k1 signature components r and s - */ -export declare function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature; -export declare function decodeSignature( - signature: StdSignature, -): { - readonly pubkey: Uint8Array; - readonly signature: Uint8Array; -}; diff --git a/packages/sdk/types/signingcosmwasmclient.d.ts b/packages/sdk/types/signingcosmwasmclient.d.ts deleted file mode 100644 index f6c34312..00000000 --- a/packages/sdk/types/signingcosmwasmclient.d.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Coin } from "./coins"; -import { Account, CosmWasmClient, GetNonceResult, PostTxResult } from "./cosmwasmclient"; -import { Log } from "./logs"; -import { BroadcastMode } from "./restclient"; -import { StdFee, StdSignature } from "./types"; -export interface SigningCallback { - (signBytes: Uint8Array): Promise; -} -export interface FeeTable { - readonly upload: StdFee; - readonly init: StdFee; - readonly exec: StdFee; - readonly send: StdFee; -} -export interface UploadMeta { - /** The source URL */ - readonly source?: string; - /** The builder tag */ - readonly builder?: string; -} -export interface UploadResult { - /** 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; - /** The ID of the code asigned by the chain */ - readonly codeId: number; - readonly logs: readonly Log[]; - /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ - readonly transactionHash: string; -} -export interface InstantiateResult { - /** The address of the newly instantiated contract */ - readonly contractAddress: string; - readonly logs: readonly Log[]; - /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ - readonly transactionHash: string; -} -export interface ExecuteResult { - readonly logs: readonly Log[]; - /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ - readonly transactionHash: string; -} -export declare class SigningCosmWasmClient extends CosmWasmClient { - readonly senderAddress: string; - private readonly signCallback; - private readonly fees; - /** - * Creates a new client with signing capability to interact with a CosmWasm blockchain. This is the bigger brother of CosmWasmClient. - * - * This instance does a lot of caching. In order to benefit from that you should try to use one instance - * for the lifetime of your application. When switching backends, a new instance must be created. - * - * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) - * @param senderAddress The address that will sign and send transactions using this instance - * @param signCallback An asynchonous callback to create a signature for a given transaction. This can be implemented using secure key stores that require user interaction. - * @param customFees The fees that are paid for transactions - * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns - */ - constructor( - apiUrl: string, - senderAddress: string, - signCallback: SigningCallback, - customFees?: Partial, - broadcastMode?: BroadcastMode, - ); - getNonce(address?: string): Promise; - getAccount(address?: string): Promise; - /** Uploads code and returns a receipt, including the code ID */ - upload(wasmCode: Uint8Array, meta?: UploadMeta, memo?: string): Promise; - instantiate( - codeId: number, - initMsg: object, - label: string, - memo?: string, - transferAmount?: readonly Coin[], - ): Promise; - execute( - contractAddress: string, - handleMsg: object, - memo?: string, - transferAmount?: readonly Coin[], - ): Promise; - sendTokens(recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise; -} diff --git a/packages/sdk/types/types.d.ts b/packages/sdk/types/types.d.ts deleted file mode 100644 index aeaa640f..00000000 --- a/packages/sdk/types/types.d.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { Coin } from "./coins"; -/** An Amino/Cosmos SDK StdTx */ -export interface StdTx { - readonly msg: ReadonlyArray; - readonly fee: StdFee; - readonly signatures: ReadonlyArray; - readonly memo: string | undefined; -} -export declare function isStdTx(txValue: unknown): txValue is StdTx; -export interface CosmosSdkTx { - readonly type: string; - readonly value: StdTx; -} -interface MsgTemplate { - readonly type: string; - readonly value: any; -} -/** A Cosmos SDK token transfer message */ -export interface MsgSend extends MsgTemplate { - readonly type: "cosmos-sdk/MsgSend"; - readonly value: { - /** Bech32 account address */ - readonly from_address: string; - /** Bech32 account address */ - readonly to_address: string; - readonly amount: ReadonlyArray; - }; -} -/** - * Uploads Wam code to the chain - * - * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L17 - */ -export interface MsgStoreCode extends MsgTemplate { - readonly type: "wasm/store-code"; - readonly value: { - /** Bech32 account address */ - readonly sender: string; - /** Base64 encoded Wasm */ - readonly wasm_byte_code: string; - /** A valid URI reference to the contract's source code. Can be empty. */ - readonly source: string; - /** A docker tag. Can be empty. */ - readonly builder: string; - }; -} -/** - * Creates an instance of contract that was uploaded before. - * - * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L73 - */ -export interface MsgInstantiateContract extends MsgTemplate { - readonly type: "wasm/instantiate"; - readonly 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; - /** Init message as JavaScript object */ - readonly init_msg: any; - readonly init_funds: ReadonlyArray; - }; -} -/** - * Creates an instance of contract that was uploaded before. - * - * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L103 - */ -export interface MsgExecuteContract extends MsgTemplate { - readonly type: "wasm/execute"; - readonly value: { - /** Bech32 account address */ - readonly sender: string; - /** Bech32 account address */ - readonly contract: string; - /** Handle message as JavaScript object */ - readonly msg: any; - readonly sent_funds: ReadonlyArray; - }; -} -export declare type Msg = MsgSend | MsgStoreCode | MsgInstantiateContract | MsgExecuteContract | MsgTemplate; -export declare function isMsgSend(msg: Msg): msg is MsgSend; -export declare function isMsgStoreCode(msg: Msg): msg is MsgStoreCode; -export declare function isMsgInstantiateContract(msg: Msg): msg is MsgInstantiateContract; -export declare function isMsgExecuteContract(msg: Msg): msg is MsgExecuteContract; -export interface StdFee { - readonly amount: ReadonlyArray; - readonly gas: string; -} -export interface StdSignature { - readonly pub_key: PubKey; - readonly signature: string; -} -export interface PubKey { - readonly type: string; - readonly value: string; -} -export declare const pubkeyType: { - /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/ed25519/ed25519.go#L22 */ - secp256k1: "tendermint/PubKeySecp256k1"; - /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/secp256k1/secp256k1.go#L23 */ - ed25519: "tendermint/PubKeyEd25519"; - /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12 */ - sr25519: "tendermint/PubKeySr25519"; -}; -export declare const pubkeyTypes: readonly string[]; -export interface WasmData { - readonly key: string; - readonly val: string; -} -export interface Model { - readonly key: Uint8Array; - readonly val: Uint8Array; -} -export declare function parseWasmData({ key, val }: WasmData): Model; -/** - * An object containing a parsed JSON document. The result of JSON.parse(). - * This doen't privide any type safety over `any` but expresses intent in the code. - */ -export declare type JsonObject = any; -export {}; diff --git a/packages/sdk/webpack.web.config.js b/packages/sdk/webpack.web.config.js deleted file mode 100644 index 7373cace..00000000 --- a/packages/sdk/webpack.web.config.js +++ /dev/null @@ -1,19 +0,0 @@ -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(["WASMD_ENABLED"])], - }, -]; From 5da7bbc93c9f764a900b2a6c1840ed39ae32e7f2 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 2 Jun 2020 15:32:50 +0200 Subject: [PATCH 11/15] Deduplicate and sort some types --- packages/bcp/src/address.ts | 8 +-- packages/bcp/src/cosmwasmconnection.ts | 6 +-- packages/bcp/src/decode.spec.ts | 15 +++--- packages/bcp/src/decode.ts | 23 ++++---- packages/bcp/src/encode.ts | 23 +++++--- packages/bcp/types/decode.d.ts | 15 +++--- packages/bcp/types/encode.d.ts | 12 ++--- packages/cli/README.md | 4 +- packages/cli/examples/local_faucet.ts | 2 +- packages/cli/examples/mask.ts | 8 +-- packages/cli/src/cli.ts | 10 +++- .../src/cosmwasmclient.searchtx.spec.ts | 3 +- packages/cosmwasm/src/cosmwasmclient.spec.ts | 3 +- packages/cosmwasm/src/cosmwasmclient.ts | 4 +- packages/cosmwasm/src/restclient.spec.ts | 4 +- packages/cosmwasm/src/restclient.ts | 4 +- .../cosmwasm/src/signingcosmwasmclient.ts | 3 +- packages/cosmwasm/src/types.ts | 54 ------------------- packages/cosmwasm/types/cosmwasmclient.d.ts | 4 +- packages/cosmwasm/types/restclient.d.ts | 4 +- .../cosmwasm/types/signingcosmwasmclient.d.ts | 3 +- packages/cosmwasm/types/types.d.ts | 35 ------------ packages/sdk38/src/index.ts | 14 ++++- packages/sdk38/types/index.d.ts | 14 ++++- 24 files changed, 111 insertions(+), 164 deletions(-) diff --git a/packages/bcp/src/address.ts b/packages/bcp/src/address.ts index c0cb7b9a..4396357a 100644 --- a/packages/bcp/src/address.ts +++ b/packages/bcp/src/address.ts @@ -1,4 +1,4 @@ -import { pubkeyToAddress as sdkPubkeyToAddress, types } from "@cosmwasm/sdk38"; +import { PubKey, pubkeyToAddress as sdkPubkeyToAddress, pubkeyType } from "@cosmwasm/sdk38"; import { Address, Algorithm, PubkeyBundle } from "@iov/bcp"; import { Secp256k1 } from "@iov/crypto"; import { Encoding } from "@iov/encoding"; @@ -7,15 +7,15 @@ const { toBase64 } = Encoding; // See https://github.com/tendermint/tendermint/blob/f2ada0a604b4c0763bda2f64fac53d506d3beca7/docs/spec/blockchain/encoding.md#public-key-cryptography export function pubkeyToAddress(pubkey: PubkeyBundle, prefix: string): Address { - let sdkKey: types.PubKey; + let sdkKey: PubKey; if (pubkey.algo === Algorithm.Secp256k1) { sdkKey = { - type: types.pubkeyType.secp256k1, + type: pubkeyType.secp256k1, value: toBase64(pubkey.data.length > 33 ? Secp256k1.compressPubkey(pubkey.data) : pubkey.data), }; } else if (pubkey.algo === Algorithm.Ed25519) { sdkKey = { - type: types.pubkeyType.ed25519, + type: pubkeyType.ed25519, value: toBase64(pubkey.data), }; } else { diff --git a/packages/bcp/src/cosmwasmconnection.ts b/packages/bcp/src/cosmwasmconnection.ts index 56e97ef6..b28c2c0f 100644 --- a/packages/bcp/src/cosmwasmconnection.ts +++ b/packages/bcp/src/cosmwasmconnection.ts @@ -4,7 +4,7 @@ import { isMsgInstantiateContract, isMsgStoreCode, } from "@cosmwasm/cosmwasm"; -import { findSequenceForSignedTx, IndexedTx, SearchTxFilter, types } from "@cosmwasm/sdk38"; +import { findSequenceForSignedTx, IndexedTx, isMsgSend, isStdTx, SearchTxFilter } from "@cosmwasm/sdk38"; import { Account, AccountQuery, @@ -278,7 +278,7 @@ export class CosmWasmConnection implements BlockchainConnection { public async postTx(tx: PostableBytes): Promise { const txAsJson = JSON.parse(Encoding.fromUtf8(tx)); - if (!types.isStdTx(txAsJson)) throw new Error("Postable bytes must contain a JSON encoded StdTx"); + if (!isStdTx(txAsJson)) throw new Error("Postable bytes must contain a JSON encoded StdTx"); const { transactionHash, rawLog } = await this.cosmWasmClient.postTx(txAsJson); const transactionId = transactionHash as TransactionId; const firstEvent: BlockInfo = { state: TransactionState.Pending }; @@ -479,7 +479,7 @@ export class CosmWasmConnection implements BlockchainConnection { if (!firstMsg) throw new Error("Got transaction without a first message. What is going on here?"); let senderAddress: string; - if (types.isMsgSend(firstMsg)) { + if (isMsgSend(firstMsg)) { senderAddress = firstMsg.value.from_address; } else if ( isMsgStoreCode(firstMsg) || diff --git a/packages/bcp/src/decode.spec.ts b/packages/bcp/src/decode.spec.ts index d40e178d..26e57a1f 100644 --- a/packages/bcp/src/decode.spec.ts +++ b/packages/bcp/src/decode.spec.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/camelcase */ import { MsgExecuteContract } from "@cosmwasm/cosmwasm"; -import { Coin, IndexedTx, types } from "@cosmwasm/sdk38"; +import { Coin, IndexedTx, PubKey, StdSignature, StdTx } from "@cosmwasm/sdk38"; +import { Msg } from "@cosmwasm/sdk38/types/types"; import { Address, Algorithm, isSendTransaction, SendTransaction, TokenTicker } from "@iov/bcp"; import { Encoding } from "@iov/encoding"; import { assert } from "@iov/utils"; @@ -85,7 +86,7 @@ describe("decode", () => { describe("decodePubkey", () => { it("works for secp256k1", () => { - const pubkey: types.PubKey = { + const pubkey: PubKey = { type: "tendermint/PubKeySecp256k1", value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP", }; @@ -93,7 +94,7 @@ describe("decode", () => { }); it("works for ed25519", () => { - const pubkey: types.PubKey = { + const pubkey: PubKey = { type: "tendermint/PubKeyEd25519", value: "s69CnMgLTpuRyEfecjws3mWssBrOICUx8C2O1DkKSto=", }; @@ -105,7 +106,7 @@ describe("decode", () => { it("throws for unsupported types", () => { // https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12 - const pubkey: types.PubKey = { + const pubkey: PubKey = { type: "tendermint/PubKeySr25519", value: "N4FJNPE5r/Twz55kO1QEIxyaGF5/HTXH6WgLQJWsy1o=", }; @@ -123,7 +124,7 @@ describe("decode", () => { describe("decodeFullSignature", () => { it("works", () => { - const fullSignature: types.StdSignature = { + const fullSignature: StdSignature = { pub_key: { type: "tendermint/PubKeySecp256k1", value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP", @@ -146,7 +147,7 @@ describe("decode", () => { describe("parseMsg", () => { it("works for bank send transaction", () => { - const msg: types.Msg = { + const msg: Msg = { type: "cosmos-sdk/MsgSend", value: { from_address: "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r", @@ -233,7 +234,7 @@ describe("decode", () => { sent_funds: [], }, }; - const tx: types.StdTx = { + const tx: StdTx = { msg: [msg], memo: defaultMemo, fee: { diff --git a/packages/bcp/src/decode.ts b/packages/bcp/src/decode.ts index 0adc918b..c8a91626 100644 --- a/packages/bcp/src/decode.ts +++ b/packages/bcp/src/decode.ts @@ -1,5 +1,6 @@ import { isMsgExecuteContract } from "@cosmwasm/cosmwasm"; -import { Coin, IndexedTx, types } from "@cosmwasm/sdk38"; +import { Coin, IndexedTx, PubKey, StdSignature } from "@cosmwasm/sdk38"; +import { isMsgSend, isStdTx, Msg, pubkeyType, StdFee, StdTx } from "@cosmwasm/sdk38/types/types"; import { Address, Algorithm, @@ -26,14 +27,14 @@ import { BankToken, Erc20Token } from "./types"; const { fromBase64 } = Encoding; -export function decodePubkey(pubkey: types.PubKey): PubkeyBundle { +export function decodePubkey(pubkey: PubKey): PubkeyBundle { switch (pubkey.type) { - case types.pubkeyType.secp256k1: + case pubkeyType.secp256k1: return { algo: Algorithm.Secp256k1, data: fromBase64(pubkey.value) as PubkeyBytes, }; - case types.pubkeyType.ed25519: + case pubkeyType.ed25519: return { algo: Algorithm.Ed25519, data: fromBase64(pubkey.value) as PubkeyBytes, @@ -47,7 +48,7 @@ export function decodeSignature(signature: string): SignatureBytes { return fromBase64(signature) as SignatureBytes; } -export function decodeFullSignature(signature: types.StdSignature, nonce: number): FullSignature { +export function decodeFullSignature(signature: StdSignature, nonce: number): FullSignature { return { nonce: nonce as Nonce, pubkey: decodePubkey(signature.pub_key), @@ -74,13 +75,13 @@ export function decodeAmount(tokens: readonly BankToken[], coin: Coin): Amount { } export function parseMsg( - msg: types.Msg, + msg: Msg, memo: string | undefined, chainId: ChainId, tokens: readonly BankToken[], erc20Tokens: readonly Erc20Token[], ): UnsignedTransaction { - if (types.isMsgSend(msg)) { + if (isMsgSend(msg)) { if (msg.value.amount.length !== 1) { throw new Error("Only MsgSend with one amount is supported"); } @@ -131,7 +132,7 @@ export function parseMsg( } } -export function parseFee(fee: types.StdFee, tokens: readonly BankToken[]): Fee { +export function parseFee(fee: StdFee, tokens: readonly BankToken[]): Fee { if (fee.amount.length !== 1) { throw new Error("Only fee with one amount is supported"); } @@ -142,12 +143,12 @@ export function parseFee(fee: types.StdFee, tokens: readonly BankToken[]): Fee { } export function parseUnsignedTx( - txValue: types.StdTx, + txValue: StdTx, chainId: ChainId, tokens: readonly BankToken[], erc20Tokens: readonly Erc20Token[], ): UnsignedTransaction { - if (!types.isStdTx(txValue)) { + if (!isStdTx(txValue)) { throw new Error("Only StdTx is supported"); } if (txValue.msg.length !== 1) { @@ -165,7 +166,7 @@ export function parseUnsignedTx( } export function parseSignedTx( - txValue: types.StdTx, + txValue: StdTx, chainId: ChainId, nonce: Nonce, tokens: readonly BankToken[], diff --git a/packages/bcp/src/encode.ts b/packages/bcp/src/encode.ts index 61006eeb..f8820a2a 100644 --- a/packages/bcp/src/encode.ts +++ b/packages/bcp/src/encode.ts @@ -1,5 +1,14 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { Coin, encodeSecp256k1Pubkey, encodeSecp256k1Signature, types } from "@cosmwasm/sdk38"; +import { + Coin, + CosmosSdkTx, + encodeSecp256k1Pubkey, + encodeSecp256k1Signature, + PubKey, + pubkeyType, + StdFee, + StdSignature, +} from "@cosmwasm/sdk38"; import { Algorithm, Amount, @@ -18,13 +27,13 @@ import { BankToken, Erc20Token } from "./types"; const { toBase64 } = Encoding; // TODO: This function seems to be unused and is not well tested (e.g. uncompressed secp256k1 or ed25519) -export function encodePubkey(pubkey: PubkeyBundle): types.PubKey { +export function encodePubkey(pubkey: PubkeyBundle): PubKey { switch (pubkey.algo) { case Algorithm.Secp256k1: return encodeSecp256k1Pubkey(pubkey.data); case Algorithm.Ed25519: return { - type: types.pubkeyType.ed25519, + type: pubkeyType.ed25519, value: toBase64(pubkey.data), }; default: @@ -54,7 +63,7 @@ export function toBankCoin(amount: Amount, tokens: readonly BankToken[]): Coin { }; } -export function encodeFee(fee: Fee, tokens: readonly BankToken[]): types.StdFee { +export function encodeFee(fee: Fee, tokens: readonly BankToken[]): StdFee { if (fee.tokens === undefined) { throw new Error("Cannot encode fee without tokens"); } @@ -67,7 +76,7 @@ export function encodeFee(fee: Fee, tokens: readonly BankToken[]): types.StdFee }; } -export function encodeFullSignature(fullSignature: FullSignature): types.StdSignature { +export function encodeFullSignature(fullSignature: FullSignature): StdSignature { switch (fullSignature.pubkey.algo) { case Algorithm.Secp256k1: { const compressedPubkey = Secp256k1.compressPubkey(fullSignature.pubkey.data); @@ -83,7 +92,7 @@ export function buildUnsignedTx( tx: UnsignedTransaction, bankTokens: readonly BankToken[], erc20Tokens: readonly Erc20Token[] = [], -): types.CosmosSdkTx { +): CosmosSdkTx { if (!isSendTransaction(tx)) { throw new Error("Received transaction of unsupported kind"); } @@ -146,7 +155,7 @@ export function buildSignedTx( tx: SignedTransaction, bankTokens: readonly BankToken[], erc20Tokens: readonly Erc20Token[] = [], -): types.CosmosSdkTx { +): CosmosSdkTx { const built = buildUnsignedTx(tx.transaction, bankTokens, erc20Tokens); return { ...built, diff --git a/packages/bcp/types/decode.d.ts b/packages/bcp/types/decode.d.ts index cc52190a..ae0da97d 100644 --- a/packages/bcp/types/decode.d.ts +++ b/packages/bcp/types/decode.d.ts @@ -1,4 +1,5 @@ -import { Coin, IndexedTx, types } from "@cosmwasm/sdk38"; +import { Coin, IndexedTx, PubKey, StdSignature } from "@cosmwasm/sdk38"; +import { Msg, StdFee, StdTx } from "@cosmwasm/sdk38/types/types"; import { Amount, ChainId, @@ -14,27 +15,27 @@ import { } from "@iov/bcp"; import { Decimal } from "@iov/encoding"; import { BankToken, Erc20Token } from "./types"; -export declare function decodePubkey(pubkey: types.PubKey): PubkeyBundle; +export declare function decodePubkey(pubkey: PubKey): PubkeyBundle; export declare function decodeSignature(signature: string): SignatureBytes; -export declare function decodeFullSignature(signature: types.StdSignature, nonce: number): FullSignature; +export declare function decodeFullSignature(signature: StdSignature, nonce: number): FullSignature; export declare function coinToDecimal(tokens: readonly BankToken[], coin: Coin): readonly [Decimal, string]; export declare function decodeAmount(tokens: readonly BankToken[], coin: Coin): Amount; export declare function parseMsg( - msg: types.Msg, + msg: Msg, memo: string | undefined, chainId: ChainId, tokens: readonly BankToken[], erc20Tokens: readonly Erc20Token[], ): UnsignedTransaction; -export declare function parseFee(fee: types.StdFee, tokens: readonly BankToken[]): Fee; +export declare function parseFee(fee: StdFee, tokens: readonly BankToken[]): Fee; export declare function parseUnsignedTx( - txValue: types.StdTx, + txValue: StdTx, chainId: ChainId, tokens: readonly BankToken[], erc20Tokens: readonly Erc20Token[], ): UnsignedTransaction; export declare function parseSignedTx( - txValue: types.StdTx, + txValue: StdTx, chainId: ChainId, nonce: Nonce, tokens: readonly BankToken[], diff --git a/packages/bcp/types/encode.d.ts b/packages/bcp/types/encode.d.ts index 316e09d2..af4033d4 100644 --- a/packages/bcp/types/encode.d.ts +++ b/packages/bcp/types/encode.d.ts @@ -1,18 +1,18 @@ -import { Coin, types } from "@cosmwasm/sdk38"; +import { Coin, CosmosSdkTx, PubKey, StdFee, StdSignature } from "@cosmwasm/sdk38"; import { Amount, Fee, FullSignature, PubkeyBundle, SignedTransaction, UnsignedTransaction } from "@iov/bcp"; import { BankToken, Erc20Token } from "./types"; -export declare function encodePubkey(pubkey: PubkeyBundle): types.PubKey; +export declare function encodePubkey(pubkey: PubkeyBundle): PubKey; export declare function toErc20Amount(amount: Amount, erc20Token: Erc20Token): string; export declare function toBankCoin(amount: Amount, tokens: readonly BankToken[]): Coin; -export declare function encodeFee(fee: Fee, tokens: readonly BankToken[]): types.StdFee; -export declare function encodeFullSignature(fullSignature: FullSignature): types.StdSignature; +export declare function encodeFee(fee: Fee, tokens: readonly BankToken[]): StdFee; +export declare function encodeFullSignature(fullSignature: FullSignature): StdSignature; export declare function buildUnsignedTx( tx: UnsignedTransaction, bankTokens: readonly BankToken[], erc20Tokens?: readonly Erc20Token[], -): types.CosmosSdkTx; +): CosmosSdkTx; export declare function buildSignedTx( tx: SignedTransaction, bankTokens: readonly BankToken[], erc20Tokens?: readonly Erc20Token[], -): types.CosmosSdkTx; +): CosmosSdkTx; diff --git a/packages/cli/README.md b/packages/cli/README.md index e9d512f8..e9bb0653 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -50,7 +50,7 @@ const { account_number, sequence } = (await client.authAccounts(faucetAddress)). // Craft a send transaction const emptyAddress = Bech32.encode("cosmos", Random.getBytes(20)); const memo = "My first contract on chain"; -const sendTokensMsg: types.MsgSend = { +const sendTokensMsg: MsgSend = { type: "cosmos-sdk/MsgSend", value: { from_address: faucetAddress, @@ -66,7 +66,7 @@ const sendTokensMsg: types.MsgSend = { const signBytes = makeSignBytes([sendTokensMsg], defaultFee, defaultNetworkId, memo, account_number, sequence); const signature = await pen.sign(signBytes); -const signedTx: types.StdTx = { +const signedTx: StdTx = { msg: [sendTokensMsg], fee: defaultFee, memo: memo, diff --git a/packages/cli/examples/local_faucet.ts b/packages/cli/examples/local_faucet.ts index 21199c9c..77a447b6 100644 --- a/packages/cli/examples/local_faucet.ts +++ b/packages/cli/examples/local_faucet.ts @@ -1,6 +1,6 @@ const defaultHttpUrl = "http://localhost:1317"; const defaultNetworkId = "testing"; -const defaultFee: types.StdFee = { +const defaultFee: StdFee = { amount: [ { amount: "5000", diff --git a/packages/cli/examples/mask.ts b/packages/cli/examples/mask.ts index b1a4f4cb..7a36b0ba 100644 --- a/packages/cli/examples/mask.ts +++ b/packages/cli/examples/mask.ts @@ -5,7 +5,7 @@ export type HandleMsg = msgs: ( | { send: { - amount: types.Coin[]; + amount: Coin[]; from_address: string; to_address: string; }; @@ -15,7 +15,7 @@ export type HandleMsg = contract_addr: string; // this had to be changed - is Base64 encoded string msg: string; - send: types.Coin[] | null; + send: Coin[] | null; }; } | { @@ -53,7 +53,7 @@ export interface State { const base64Msg = (msg: object): string => toBase64(toUtf8(JSON.stringify(msg))); -const sendMsg = (from_address: string, to_address: string, amount: types.Coin[]) => { +const sendMsg = (from_address: string, to_address: string, amount: Coin[]) => { return { send: { from_address, @@ -63,7 +63,7 @@ const sendMsg = (from_address: string, to_address: string, amount: types.Coin[]) }; } -const contractMsg = (contract_addr: string, msg: object, amount?: types.Coin[]) => { +const contractMsg = (contract_addr: string, msg: object, amount?: Coin[]) => { return { contract: { contract_addr, diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 83932e1f..f6a6c0c2 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -62,18 +62,24 @@ export function main(originalArgs: readonly string[]): void { [ "@cosmwasm/sdk38", [ + "coin", + "coins", "encodeSecp256k1Pubkey", "encodeSecp256k1Signature", "logs", "makeCosmoshubPath", "makeSignBytes", "marshalTx", + "IndexedTx", + "Coin", + "MsgSend", "Pen", + "PubKey", "pubkeyToAddress", "RestClient", "Secp256k1Pen", - "types", - "IndexedTx", + "StdFee", + "StdTx", ], ], [ diff --git a/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts b/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts index 431ed96f..4a152dd5 100644 --- a/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts +++ b/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { Coin, makeSignBytes, Secp256k1Pen } from "@cosmwasm/sdk38"; +import { Coin, CosmosSdkTx, makeSignBytes, Secp256k1Pen } from "@cosmwasm/sdk38"; import { assert, sleep } from "@iov/utils"; import { CosmWasmClient } from "./cosmwasmclient"; @@ -15,7 +15,6 @@ import { wasmd, wasmdEnabled, } from "./testutils.spec"; -import { CosmosSdkTx } from "./types"; describe("CosmWasmClient.searchTx", () => { let sendSuccessful: diff --git a/packages/cosmwasm/src/cosmwasmclient.spec.ts b/packages/cosmwasm/src/cosmwasmclient.spec.ts index 6fffd7b7..c9998ec8 100644 --- a/packages/cosmwasm/src/cosmwasmclient.spec.ts +++ b/packages/cosmwasm/src/cosmwasmclient.spec.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { makeSignBytes, Secp256k1Pen } from "@cosmwasm/sdk38"; +import { makeSignBytes, Secp256k1Pen, StdFee } from "@cosmwasm/sdk38"; import { Sha256 } from "@iov/crypto"; import { Bech32, Encoding } from "@iov/encoding"; import { assert, sleep } from "@iov/utils"; @@ -21,7 +21,6 @@ import { wasmd, wasmdEnabled, } from "./testutils.spec"; -import { StdFee } from "./types"; const { fromHex, fromUtf8, toAscii, toBase64 } = Encoding; diff --git a/packages/cosmwasm/src/cosmwasmclient.ts b/packages/cosmwasm/src/cosmwasmclient.ts index 16f438a1..991fa633 100644 --- a/packages/cosmwasm/src/cosmwasmclient.ts +++ b/packages/cosmwasm/src/cosmwasmclient.ts @@ -1,10 +1,10 @@ -import { Coin, decodeBech32Pubkey, IndexedTx } from "@cosmwasm/sdk38"; +import { Coin, CosmosSdkTx, decodeBech32Pubkey, IndexedTx, PubKey, StdTx } from "@cosmwasm/sdk38"; import { Sha256 } from "@iov/crypto"; import { Encoding } from "@iov/encoding"; import { Log, parseLogs } from "./logs"; import { BroadcastMode, RestClient } from "./restclient"; -import { CosmosSdkTx, JsonObject, PubKey, StdTx } from "./types"; +import { JsonObject } from "./types"; export interface GetNonceResult { readonly accountNumber: number; diff --git a/packages/cosmwasm/src/restclient.spec.ts b/packages/cosmwasm/src/restclient.spec.ts index c006d5bb..1fe1d856 100644 --- a/packages/cosmwasm/src/restclient.spec.ts +++ b/packages/cosmwasm/src/restclient.spec.ts @@ -7,6 +7,9 @@ import { Pen, rawSecp256k1PubkeyToAddress, Secp256k1Pen, + StdFee, + StdSignature, + StdTx, } from "@cosmwasm/sdk38"; import { Sha256 } from "@iov/crypto"; import { Encoding } from "@iov/encoding"; @@ -44,7 +47,6 @@ import { wasmd, wasmdEnabled, } from "./testutils.spec"; -import { StdFee, StdSignature, StdTx } from "./types"; const { fromAscii, fromBase64, fromHex, toAscii, toBase64, toHex } = Encoding; diff --git a/packages/cosmwasm/src/restclient.ts b/packages/cosmwasm/src/restclient.ts index 0e5846c8..06c0c131 100644 --- a/packages/cosmwasm/src/restclient.ts +++ b/packages/cosmwasm/src/restclient.ts @@ -1,8 +1,8 @@ -import { Coin } from "@cosmwasm/sdk38"; +import { Coin, CosmosSdkTx, StdTx } from "@cosmwasm/sdk38"; import { Encoding, isNonNullObject } from "@iov/encoding"; import axios, { AxiosError, AxiosInstance } from "axios"; -import { CosmosSdkTx, JsonObject, Model, parseWasmData, StdTx, WasmData } from "./types"; +import { JsonObject, Model, parseWasmData, WasmData } from "./types"; const { fromBase64, fromUtf8, toHex, toUtf8 } = Encoding; diff --git a/packages/cosmwasm/src/signingcosmwasmclient.ts b/packages/cosmwasm/src/signingcosmwasmclient.ts index 99eed14c..9c44fd09 100644 --- a/packages/cosmwasm/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm/src/signingcosmwasmclient.ts @@ -1,4 +1,4 @@ -import { Coin, coins, makeSignBytes } from "@cosmwasm/sdk38"; +import { Coin, coins, makeSignBytes, StdFee, StdSignature } from "@cosmwasm/sdk38"; import { Sha256 } from "@iov/crypto"; import { Encoding } from "@iov/encoding"; import pako from "pako"; @@ -8,7 +8,6 @@ import { Account, CosmWasmClient, GetNonceResult, PostTxResult } from "./cosmwas import { findAttribute, Log } from "./logs"; import { MsgExecuteContract, MsgInstantiateContract, MsgSend, MsgStoreCode } from "./msgs"; import { BroadcastMode } from "./restclient"; -import { StdFee, StdSignature } from "./types"; export interface SigningCallback { (signBytes: Uint8Array): Promise; diff --git a/packages/cosmwasm/src/types.ts b/packages/cosmwasm/src/types.ts index 59c9a112..3bcfb2c9 100644 --- a/packages/cosmwasm/src/types.ts +++ b/packages/cosmwasm/src/types.ts @@ -1,61 +1,7 @@ -import { Coin } from "@cosmwasm/sdk38"; import { Encoding } from "@iov/encoding"; -import { Msg } from "./msgs"; - const { fromBase64, fromHex } = Encoding; -/** An Amino/Cosmos SDK StdTx */ -export interface StdTx { - readonly msg: ReadonlyArray; - readonly fee: StdFee; - readonly signatures: ReadonlyArray; - readonly memo: string | undefined; -} - -export function isStdTx(txValue: unknown): txValue is StdTx { - const { memo, msg, fee, signatures } = txValue as StdTx; - return ( - typeof memo === "string" && Array.isArray(msg) && typeof fee === "object" && Array.isArray(signatures) - ); -} - -export interface CosmosSdkTx { - readonly type: string; - readonly value: StdTx; -} - -export interface StdFee { - readonly amount: ReadonlyArray; - readonly gas: string; -} - -export interface StdSignature { - readonly pub_key: PubKey; - readonly signature: string; -} - -export interface PubKey { - // type is one of the strings defined in pubkeyTypes - // I don't use a string literal union here as that makes trouble with json test data: - // https://github.com/confio/cosmwasm-js/pull/44#pullrequestreview-353280504 - readonly type: string; - // Value field is base64-encoded in all cases - // Note: if type is Secp256k1, this must contain a COMPRESSED pubkey - to encode from bcp/keycontrol land, you must compress it first - readonly value: string; -} - -export const pubkeyType = { - /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/ed25519/ed25519.go#L22 */ - secp256k1: "tendermint/PubKeySecp256k1" as const, - /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/secp256k1/secp256k1.go#L23 */ - ed25519: "tendermint/PubKeyEd25519" as const, - /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12 */ - sr25519: "tendermint/PubKeySr25519" as const, -}; - -export const pubkeyTypes: readonly string[] = [pubkeyType.secp256k1, pubkeyType.ed25519, pubkeyType.sr25519]; - export interface WasmData { // key is hex-encoded readonly key: string; diff --git a/packages/cosmwasm/types/cosmwasmclient.d.ts b/packages/cosmwasm/types/cosmwasmclient.d.ts index d12633cc..4978b694 100644 --- a/packages/cosmwasm/types/cosmwasmclient.d.ts +++ b/packages/cosmwasm/types/cosmwasmclient.d.ts @@ -1,7 +1,7 @@ -import { Coin, IndexedTx } from "@cosmwasm/sdk38"; +import { Coin, CosmosSdkTx, IndexedTx, PubKey, StdTx } from "@cosmwasm/sdk38"; import { Log } from "./logs"; import { BroadcastMode, RestClient } from "./restclient"; -import { CosmosSdkTx, JsonObject, PubKey, StdTx } from "./types"; +import { JsonObject } from "./types"; export interface GetNonceResult { readonly accountNumber: number; readonly sequence: number; diff --git a/packages/cosmwasm/types/restclient.d.ts b/packages/cosmwasm/types/restclient.d.ts index 2c914be4..3bf65fe2 100644 --- a/packages/cosmwasm/types/restclient.d.ts +++ b/packages/cosmwasm/types/restclient.d.ts @@ -1,5 +1,5 @@ -import { Coin } from "@cosmwasm/sdk38"; -import { CosmosSdkTx, JsonObject, Model, StdTx } from "./types"; +import { Coin, CosmosSdkTx, StdTx } from "@cosmwasm/sdk38"; +import { JsonObject, Model } from "./types"; export interface CosmosSdkAccount { /** Bech32 account address */ readonly address: string; diff --git a/packages/cosmwasm/types/signingcosmwasmclient.d.ts b/packages/cosmwasm/types/signingcosmwasmclient.d.ts index d3f9e00e..75bd9b76 100644 --- a/packages/cosmwasm/types/signingcosmwasmclient.d.ts +++ b/packages/cosmwasm/types/signingcosmwasmclient.d.ts @@ -1,8 +1,7 @@ -import { Coin } from "@cosmwasm/sdk38"; +import { Coin, StdFee, StdSignature } from "@cosmwasm/sdk38"; import { Account, CosmWasmClient, GetNonceResult, PostTxResult } from "./cosmwasmclient"; import { Log } from "./logs"; import { BroadcastMode } from "./restclient"; -import { StdFee, StdSignature } from "./types"; export interface SigningCallback { (signBytes: Uint8Array): Promise; } diff --git a/packages/cosmwasm/types/types.d.ts b/packages/cosmwasm/types/types.d.ts index b7d54776..45d870c7 100644 --- a/packages/cosmwasm/types/types.d.ts +++ b/packages/cosmwasm/types/types.d.ts @@ -1,38 +1,3 @@ -import { Coin } from "@cosmwasm/sdk38"; -import { Msg } from "./msgs"; -/** An Amino/Cosmos SDK StdTx */ -export interface StdTx { - readonly msg: ReadonlyArray; - readonly fee: StdFee; - readonly signatures: ReadonlyArray; - readonly memo: string | undefined; -} -export declare function isStdTx(txValue: unknown): txValue is StdTx; -export interface CosmosSdkTx { - readonly type: string; - readonly value: StdTx; -} -export interface StdFee { - readonly amount: ReadonlyArray; - readonly gas: string; -} -export interface StdSignature { - readonly pub_key: PubKey; - readonly signature: string; -} -export interface PubKey { - readonly type: string; - readonly value: string; -} -export declare const pubkeyType: { - /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/ed25519/ed25519.go#L22 */ - secp256k1: "tendermint/PubKeySecp256k1"; - /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/secp256k1/secp256k1.go#L23 */ - ed25519: "tendermint/PubKeyEd25519"; - /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12 */ - sr25519: "tendermint/PubKeySr25519"; -}; -export declare const pubkeyTypes: readonly string[]; export interface WasmData { readonly key: string; readonly val: string; diff --git a/packages/sdk38/src/index.ts b/packages/sdk38/src/index.ts index d67248bf..b1f290f7 100644 --- a/packages/sdk38/src/index.ts +++ b/packages/sdk38/src/index.ts @@ -1,6 +1,5 @@ import * as logs from "./logs"; -import * as types from "./types"; -export { logs, types }; +export { logs }; export { pubkeyToAddress, rawSecp256k1PubkeyToAddress } from "./address"; export { Coin, coin, coins } from "./coins"; @@ -32,3 +31,14 @@ export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from ". export { findSequenceForSignedTx } from "./sequence"; export { encodeSecp256k1Signature, decodeSignature } from "./signature"; export { FeeTable, SigningCallback, SigningCosmosClient } from "./signingcosmosclient"; +export { + isMsgSend, + isStdTx, + pubkeyType, + CosmosSdkTx, + PubKey, + MsgSend, + StdFee, + StdSignature, + StdTx, +} from "./types"; diff --git a/packages/sdk38/types/index.d.ts b/packages/sdk38/types/index.d.ts index 78a73f2e..424559eb 100644 --- a/packages/sdk38/types/index.d.ts +++ b/packages/sdk38/types/index.d.ts @@ -1,6 +1,5 @@ import * as logs from "./logs"; -import * as types from "./types"; -export { logs, types }; +export { logs }; export { pubkeyToAddress, rawSecp256k1PubkeyToAddress } from "./address"; export { Coin, coin, coins } from "./coins"; export { @@ -30,3 +29,14 @@ export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from ". export { findSequenceForSignedTx } from "./sequence"; export { encodeSecp256k1Signature, decodeSignature } from "./signature"; export { FeeTable, SigningCallback, SigningCosmosClient } from "./signingcosmosclient"; +export { + isMsgSend, + isStdTx, + pubkeyType, + CosmosSdkTx, + PubKey, + MsgSend, + StdFee, + StdSignature, + StdTx, +} from "./types"; From 9d054c6dd3fb72af5bbab19b67d2ad3cd57e3692 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 2 Jun 2020 15:40:49 +0200 Subject: [PATCH 12/15] Improve Msg types --- packages/bcp/src/decode.spec.ts | 3 +- packages/bcp/src/decode.ts | 14 +++++++-- packages/bcp/types/decode.d.ts | 3 +- packages/cli/src/cli.ts | 1 + .../src/cosmwasmclient.searchtx.spec.ts | 4 +-- packages/cosmwasm/src/cosmwasmclient.spec.ts | 3 +- packages/cosmwasm/src/msgs.ts | 31 +++---------------- packages/cosmwasm/src/restclient.spec.ts | 4 +-- .../cosmwasm/src/signingcosmwasmclient.ts | 4 +-- packages/cosmwasm/types/msgs.d.ts | 26 +++------------- packages/sdk38/src/index.ts | 1 + packages/sdk38/src/types.ts | 6 ++-- packages/sdk38/types/index.d.ts | 1 + packages/sdk38/types/types.d.ts | 6 ++-- 14 files changed, 36 insertions(+), 71 deletions(-) diff --git a/packages/bcp/src/decode.spec.ts b/packages/bcp/src/decode.spec.ts index 26e57a1f..c97e20ed 100644 --- a/packages/bcp/src/decode.spec.ts +++ b/packages/bcp/src/decode.spec.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/camelcase */ import { MsgExecuteContract } from "@cosmwasm/cosmwasm"; -import { Coin, IndexedTx, PubKey, StdSignature, StdTx } from "@cosmwasm/sdk38"; -import { Msg } from "@cosmwasm/sdk38/types/types"; +import { Coin, IndexedTx, Msg, PubKey, StdSignature, StdTx } from "@cosmwasm/sdk38"; import { Address, Algorithm, isSendTransaction, SendTransaction, TokenTicker } from "@iov/bcp"; import { Encoding } from "@iov/encoding"; import { assert } from "@iov/utils"; diff --git a/packages/bcp/src/decode.ts b/packages/bcp/src/decode.ts index c8a91626..cab2543b 100644 --- a/packages/bcp/src/decode.ts +++ b/packages/bcp/src/decode.ts @@ -1,6 +1,16 @@ import { isMsgExecuteContract } from "@cosmwasm/cosmwasm"; -import { Coin, IndexedTx, PubKey, StdSignature } from "@cosmwasm/sdk38"; -import { isMsgSend, isStdTx, Msg, pubkeyType, StdFee, StdTx } from "@cosmwasm/sdk38/types/types"; +import { + Coin, + IndexedTx, + isMsgSend, + isStdTx, + Msg, + PubKey, + pubkeyType, + StdFee, + StdSignature, + StdTx, +} from "@cosmwasm/sdk38"; import { Address, Algorithm, diff --git a/packages/bcp/types/decode.d.ts b/packages/bcp/types/decode.d.ts index ae0da97d..495a92f9 100644 --- a/packages/bcp/types/decode.d.ts +++ b/packages/bcp/types/decode.d.ts @@ -1,5 +1,4 @@ -import { Coin, IndexedTx, PubKey, StdSignature } from "@cosmwasm/sdk38"; -import { Msg, StdFee, StdTx } from "@cosmwasm/sdk38/types/types"; +import { Coin, IndexedTx, Msg, PubKey, StdFee, StdSignature, StdTx } from "@cosmwasm/sdk38"; import { Amount, ChainId, diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index f6a6c0c2..c1bc1bf7 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -72,6 +72,7 @@ export function main(originalArgs: readonly string[]): void { "marshalTx", "IndexedTx", "Coin", + "Msg", "MsgSend", "Pen", "PubKey", diff --git a/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts b/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts index 4a152dd5..e7f981ea 100644 --- a/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts +++ b/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts @@ -1,9 +1,9 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { Coin, CosmosSdkTx, makeSignBytes, Secp256k1Pen } from "@cosmwasm/sdk38"; +import { Coin, CosmosSdkTx, isMsgSend, makeSignBytes, MsgSend, Secp256k1Pen } from "@cosmwasm/sdk38"; import { assert, sleep } from "@iov/utils"; import { CosmWasmClient } from "./cosmwasmclient"; -import { isMsgExecuteContract, isMsgInstantiateContract, isMsgSend, MsgSend } from "./msgs"; +import { isMsgExecuteContract, isMsgInstantiateContract } from "./msgs"; import { RestClient } from "./restclient"; import { SigningCosmWasmClient } from "./signingcosmwasmclient"; import { diff --git a/packages/cosmwasm/src/cosmwasmclient.spec.ts b/packages/cosmwasm/src/cosmwasmclient.spec.ts index c9998ec8..011e1495 100644 --- a/packages/cosmwasm/src/cosmwasmclient.spec.ts +++ b/packages/cosmwasm/src/cosmwasmclient.spec.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { makeSignBytes, Secp256k1Pen, StdFee } from "@cosmwasm/sdk38"; +import { makeSignBytes, MsgSend, Secp256k1Pen, StdFee } from "@cosmwasm/sdk38"; import { Sha256 } from "@iov/crypto"; import { Bech32, Encoding } from "@iov/encoding"; import { assert, sleep } from "@iov/utils"; @@ -7,7 +7,6 @@ import { ReadonlyDate } from "readonly-date"; import { Code, CosmWasmClient, PrivateCosmWasmClient } from "./cosmwasmclient"; import { findAttribute } from "./logs"; -import { MsgSend } from "./msgs"; import { SigningCosmWasmClient } from "./signingcosmwasmclient"; import cosmoshub from "./testdata/cosmoshub.json"; import { diff --git a/packages/cosmwasm/src/msgs.ts b/packages/cosmwasm/src/msgs.ts index bf47a18c..e29eeaea 100644 --- a/packages/cosmwasm/src/msgs.ts +++ b/packages/cosmwasm/src/msgs.ts @@ -1,28 +1,11 @@ -import { Coin } from "@cosmwasm/sdk38"; - -interface MsgTemplate { - readonly type: string; - readonly value: any; -} - -/** A Cosmos SDK token transfer message */ -export interface MsgSend extends MsgTemplate { - readonly type: "cosmos-sdk/MsgSend"; - readonly value: { - /** Bech32 account address */ - readonly from_address: string; - /** Bech32 account address */ - readonly to_address: string; - readonly amount: ReadonlyArray; - }; -} +import { Coin, Msg } from "@cosmwasm/sdk38"; /** * Uploads Wam code to the chain * * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L17 */ -export interface MsgStoreCode extends MsgTemplate { +export interface MsgStoreCode extends Msg { readonly type: "wasm/store-code"; readonly value: { /** Bech32 account address */ @@ -41,7 +24,7 @@ export interface MsgStoreCode extends MsgTemplate { * * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L73 */ -export interface MsgInstantiateContract extends MsgTemplate { +export interface MsgInstantiateContract extends Msg { readonly type: "wasm/instantiate"; readonly value: { /** Bech32 account address */ @@ -61,7 +44,7 @@ export interface MsgInstantiateContract extends MsgTemplate { * * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L103 */ -export interface MsgExecuteContract extends MsgTemplate { +export interface MsgExecuteContract extends Msg { readonly type: "wasm/execute"; readonly value: { /** Bech32 account address */ @@ -74,12 +57,6 @@ export interface MsgExecuteContract extends MsgTemplate { }; } -export type Msg = MsgSend | MsgStoreCode | MsgInstantiateContract | MsgExecuteContract | MsgTemplate; - -export function isMsgSend(msg: Msg): msg is MsgSend { - return (msg as MsgSend).type === "cosmos-sdk/MsgSend"; -} - export function isMsgStoreCode(msg: Msg): msg is MsgStoreCode { return (msg as MsgStoreCode).type === "wasm/store-code"; } diff --git a/packages/cosmwasm/src/restclient.spec.ts b/packages/cosmwasm/src/restclient.spec.ts index 1fe1d856..02884c16 100644 --- a/packages/cosmwasm/src/restclient.spec.ts +++ b/packages/cosmwasm/src/restclient.spec.ts @@ -4,6 +4,8 @@ import { encodeBech32Pubkey, makeCosmoshubPath, makeSignBytes, + Msg, + MsgSend, Pen, rawSecp256k1PubkeyToAddress, Secp256k1Pen, @@ -20,10 +22,8 @@ import { findAttribute, parseLogs } from "./logs"; import { isMsgInstantiateContract, isMsgStoreCode, - Msg, MsgExecuteContract, MsgInstantiateContract, - MsgSend, MsgStoreCode, } from "./msgs"; import { PostTxsResponse, RestClient, TxsResponse } from "./restclient"; diff --git a/packages/cosmwasm/src/signingcosmwasmclient.ts b/packages/cosmwasm/src/signingcosmwasmclient.ts index 9c44fd09..11ed27dd 100644 --- a/packages/cosmwasm/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm/src/signingcosmwasmclient.ts @@ -1,4 +1,4 @@ -import { Coin, coins, makeSignBytes, StdFee, StdSignature } from "@cosmwasm/sdk38"; +import { Coin, coins, makeSignBytes, MsgSend, StdFee, StdSignature } from "@cosmwasm/sdk38"; import { Sha256 } from "@iov/crypto"; import { Encoding } from "@iov/encoding"; import pako from "pako"; @@ -6,7 +6,7 @@ import pako from "pako"; import { isValidBuilder } from "./builder"; import { Account, CosmWasmClient, GetNonceResult, PostTxResult } from "./cosmwasmclient"; import { findAttribute, Log } from "./logs"; -import { MsgExecuteContract, MsgInstantiateContract, MsgSend, MsgStoreCode } from "./msgs"; +import { MsgExecuteContract, MsgInstantiateContract, MsgStoreCode } from "./msgs"; import { BroadcastMode } from "./restclient"; export interface SigningCallback { diff --git a/packages/cosmwasm/types/msgs.d.ts b/packages/cosmwasm/types/msgs.d.ts index 2fb0ae6f..ed636a26 100644 --- a/packages/cosmwasm/types/msgs.d.ts +++ b/packages/cosmwasm/types/msgs.d.ts @@ -1,25 +1,10 @@ -import { Coin } from "@cosmwasm/sdk38"; -interface MsgTemplate { - readonly type: string; - readonly value: any; -} -/** A Cosmos SDK token transfer message */ -export interface MsgSend extends MsgTemplate { - readonly type: "cosmos-sdk/MsgSend"; - readonly value: { - /** Bech32 account address */ - readonly from_address: string; - /** Bech32 account address */ - readonly to_address: string; - readonly amount: ReadonlyArray; - }; -} +import { Coin, Msg } from "@cosmwasm/sdk38"; /** * Uploads Wam code to the chain * * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L17 */ -export interface MsgStoreCode extends MsgTemplate { +export interface MsgStoreCode extends Msg { readonly type: "wasm/store-code"; readonly value: { /** Bech32 account address */ @@ -37,7 +22,7 @@ export interface MsgStoreCode extends MsgTemplate { * * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L73 */ -export interface MsgInstantiateContract extends MsgTemplate { +export interface MsgInstantiateContract extends Msg { readonly type: "wasm/instantiate"; readonly value: { /** Bech32 account address */ @@ -56,7 +41,7 @@ export interface MsgInstantiateContract extends MsgTemplate { * * @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L103 */ -export interface MsgExecuteContract extends MsgTemplate { +export interface MsgExecuteContract extends Msg { readonly type: "wasm/execute"; readonly value: { /** Bech32 account address */ @@ -68,9 +53,6 @@ export interface MsgExecuteContract extends MsgTemplate { readonly sent_funds: ReadonlyArray; }; } -export declare type Msg = MsgSend | MsgStoreCode | MsgInstantiateContract | MsgExecuteContract | MsgTemplate; -export declare function isMsgSend(msg: Msg): msg is MsgSend; export declare function isMsgStoreCode(msg: Msg): msg is MsgStoreCode; export declare function isMsgInstantiateContract(msg: Msg): msg is MsgInstantiateContract; export declare function isMsgExecuteContract(msg: Msg): msg is MsgExecuteContract; -export {}; diff --git a/packages/sdk38/src/index.ts b/packages/sdk38/src/index.ts index b1f290f7..57e3091b 100644 --- a/packages/sdk38/src/index.ts +++ b/packages/sdk38/src/index.ts @@ -37,6 +37,7 @@ export { pubkeyType, CosmosSdkTx, PubKey, + Msg, MsgSend, StdFee, StdSignature, diff --git a/packages/sdk38/src/types.ts b/packages/sdk38/src/types.ts index 7d10fe3c..0db122f9 100644 --- a/packages/sdk38/src/types.ts +++ b/packages/sdk38/src/types.ts @@ -24,13 +24,13 @@ export interface CosmosSdkTx { readonly value: StdTx; } -interface MsgTemplate { +export interface Msg { readonly type: string; readonly value: any; } /** A Cosmos SDK token transfer message */ -export interface MsgSend extends MsgTemplate { +export interface MsgSend extends Msg { readonly type: "cosmos-sdk/MsgSend"; readonly value: { /** Bech32 account address */ @@ -41,8 +41,6 @@ export interface MsgSend extends MsgTemplate { }; } -export type Msg = MsgSend | MsgTemplate; - export function isMsgSend(msg: Msg): msg is MsgSend { return (msg as MsgSend).type === "cosmos-sdk/MsgSend"; } diff --git a/packages/sdk38/types/index.d.ts b/packages/sdk38/types/index.d.ts index 424559eb..829a4403 100644 --- a/packages/sdk38/types/index.d.ts +++ b/packages/sdk38/types/index.d.ts @@ -35,6 +35,7 @@ export { pubkeyType, CosmosSdkTx, PubKey, + Msg, MsgSend, StdFee, StdSignature, diff --git a/packages/sdk38/types/types.d.ts b/packages/sdk38/types/types.d.ts index a38796a2..8083027c 100644 --- a/packages/sdk38/types/types.d.ts +++ b/packages/sdk38/types/types.d.ts @@ -11,12 +11,12 @@ export interface CosmosSdkTx { readonly type: string; readonly value: StdTx; } -interface MsgTemplate { +export interface Msg { readonly type: string; readonly value: any; } /** A Cosmos SDK token transfer message */ -export interface MsgSend extends MsgTemplate { +export interface MsgSend extends Msg { readonly type: "cosmos-sdk/MsgSend"; readonly value: { /** Bech32 account address */ @@ -26,7 +26,6 @@ export interface MsgSend extends MsgTemplate { readonly amount: ReadonlyArray; }; } -export declare type Msg = MsgSend | MsgTemplate; export declare function isMsgSend(msg: Msg): msg is MsgSend; export interface StdFee { readonly amount: ReadonlyArray; @@ -63,4 +62,3 @@ export declare function parseWasmData({ key, val }: WasmData): Model; * This doen't privide any type safety over `any` but expresses intent in the code. */ export declare type JsonObject = any; -export {}; From 54cff0367e6495ef6a4a3dc6c94711e7797b7816 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 3 Jun 2020 08:41:51 +0200 Subject: [PATCH 13/15] Remove contract functionality from sdk38 --- packages/sdk38/src/cosmosclient.spec.ts | 65 +----------- packages/sdk38/src/cosmosclient.ts | 127 +----------------------- packages/sdk38/src/index.ts | 4 - packages/sdk38/src/restclient.ts | 85 +--------------- packages/sdk38/src/types.ts | 30 ------ packages/sdk38/types/cosmosclient.d.ts | 49 +-------- packages/sdk38/types/index.d.ts | 4 - packages/sdk38/types/restclient.d.ts | 16 +-- packages/sdk38/types/types.d.ts | 14 --- 9 files changed, 6 insertions(+), 388 deletions(-) diff --git a/packages/sdk38/src/cosmosclient.spec.ts b/packages/sdk38/src/cosmosclient.spec.ts index 28d57a6a..b41ae6f2 100644 --- a/packages/sdk38/src/cosmosclient.spec.ts +++ b/packages/sdk38/src/cosmosclient.spec.ts @@ -76,7 +76,7 @@ describe("CosmosClient", () => { const height1 = await client.getHeight(); expect(height1).toBeGreaterThan(0); - await client.getCodes(); // warm up the client + await client.getAccount(guest.address); // warm up the client const height2 = await client.getHeight(); expect(height2).toBeGreaterThan(0); @@ -85,7 +85,7 @@ describe("CosmosClient", () => { expect(height3).toEqual(height2 + 1); expect(blockLatestSpy).toHaveBeenCalledTimes(1); - expect(authAccountsSpy).toHaveBeenCalledTimes(2); + expect(authAccountsSpy).toHaveBeenCalledTimes(3); }); }); @@ -232,65 +232,4 @@ describe("CosmosClient", () => { expect(transactionHash).toMatch(/^[0-9A-F]{64}$/); }); }); - - describe("getContracts", () => { - it("works", async () => { - pendingWithoutWasmd(); - const client = new CosmosClient(wasmd.endpoint); - const result = await client.getContracts(1); - expect(result.length).toBeGreaterThanOrEqual(3); - const [hash, isa, jade] = result; - expect(hash).toEqual({ - address: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", - codeId: 1, - creator: faucet.address, - label: "HASH", - }); - expect(isa).toEqual({ - address: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd", - codeId: 1, - creator: faucet.address, - label: "ISA", - }); - expect(jade).toEqual({ - address: "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c", - codeId: 1, - creator: faucet.address, - label: "JADE", - }); - }); - }); - - describe("getContract", () => { - it("works for HASH instance", async () => { - pendingWithoutWasmd(); - const client = new CosmosClient(wasmd.endpoint); - const hash = await client.getContract("cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5"); - expect(hash).toEqual({ - address: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", - codeId: 1, - creator: faucet.address, - label: "HASH", - initMsg: { - decimals: 5, - name: "Hash token", - symbol: "HASH", - initial_balances: jasmine.arrayContaining([ - { - address: faucet.address, - amount: "11", - }, - { - address: unused.address, - amount: "12812345", - }, - { - address: guest.address, - amount: "22004000000", - }, - ]), - }, - }); - }); - }); }); diff --git a/packages/sdk38/src/cosmosclient.ts b/packages/sdk38/src/cosmosclient.ts index 7960c82d..5a020430 100644 --- a/packages/sdk38/src/cosmosclient.ts +++ b/packages/sdk38/src/cosmosclient.ts @@ -5,7 +5,7 @@ import { Coin } from "./coins"; import { Log, parseLogs } from "./logs"; import { decodeBech32Pubkey } from "./pubkey"; import { BroadcastMode, RestClient } from "./restclient"; -import { CosmosSdkTx, JsonObject, PubKey, StdTx } from "./types"; +import { CosmosSdkTx, PubKey, StdTx } from "./types"; export interface GetNonceResult { readonly accountNumber: number; @@ -75,34 +75,6 @@ export interface SearchTxFilter { readonly maxHeight?: number; } -export interface Code { - readonly id: number; - /** Bech32 account address */ - readonly creator: string; - /** Hex-encoded sha256 hash of the code stored here */ - readonly checksum: string; - readonly source?: string; - readonly builder?: string; -} - -export interface CodeDetails extends Code { - /** The original wasm bytes */ - readonly data: Uint8Array; -} - -export interface Contract { - readonly address: string; - readonly codeId: number; - /** Bech32 account address */ - readonly creator: string; - readonly label: string; -} - -export interface ContractDetails extends Contract { - /** Argument passed on initialization of the contract */ - readonly initMsg: object; -} - /** A transaction that is indexed as part of the transaction history */ export interface IndexedTx { readonly height: number; @@ -150,7 +122,6 @@ export class CosmosClient { /** Any address the chain considers valid (valid bech32 with proper prefix) */ protected anyValidAddress: string | undefined; - private readonly codesCache = new Map(); private chainId: string | undefined; /** @@ -318,102 +289,6 @@ export class CosmosClient { }; } - public async getCodes(): Promise { - const result = await this.restClient.listCodeInfo(); - return result.map( - (entry): Code => { - this.anyValidAddress = entry.creator; - return { - id: entry.id, - creator: entry.creator, - checksum: Encoding.toHex(Encoding.fromHex(entry.data_hash)), - source: entry.source || undefined, - builder: entry.builder || undefined, - }; - }, - ); - } - - public async getCodeDetails(codeId: number): Promise { - const cached = this.codesCache.get(codeId); - if (cached) return cached; - - const getCodeResult = await this.restClient.getCode(codeId); - const codeDetails: CodeDetails = { - id: getCodeResult.id, - creator: getCodeResult.creator, - checksum: Encoding.toHex(Encoding.fromHex(getCodeResult.data_hash)), - source: getCodeResult.source || undefined, - builder: getCodeResult.builder || undefined, - data: Encoding.fromBase64(getCodeResult.data), - }; - this.codesCache.set(codeId, codeDetails); - return codeDetails; - } - - public async getContracts(codeId: number): Promise { - const result = await this.restClient.listContractsByCodeId(codeId); - return result.map( - (entry): Contract => ({ - address: entry.address, - codeId: entry.code_id, - creator: entry.creator, - label: entry.label, - }), - ); - } - - /** - * Throws an error if no contract was found at the address - */ - public async getContract(address: string): Promise { - const result = await this.restClient.getContractInfo(address); - if (!result) throw new Error(`No contract found at address "${address}"`); - return { - address: result.address, - codeId: result.code_id, - creator: result.creator, - label: result.label, - initMsg: result.init_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 - const _info = await this.getContract(address); - - return this.restClient.queryContractRaw(address, key); - } - - /** - * 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: object): Promise { - try { - return await this.restClient.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 { // TODO: we need proper pagination support const limit = 100; diff --git a/packages/sdk38/src/index.ts b/packages/sdk38/src/index.ts index 57e3091b..7e86fdc9 100644 --- a/packages/sdk38/src/index.ts +++ b/packages/sdk38/src/index.ts @@ -8,10 +8,6 @@ export { Account, Block, BlockHeader, - Code, - CodeDetails, - Contract, - ContractDetails, CosmosClient, GetNonceResult, IndexedTx, diff --git a/packages/sdk38/src/restclient.ts b/packages/sdk38/src/restclient.ts index cb6316e9..a0d07624 100644 --- a/packages/sdk38/src/restclient.ts +++ b/packages/sdk38/src/restclient.ts @@ -2,9 +2,7 @@ import { Encoding, isNonNullObject } from "@iov/encoding"; import axios, { AxiosError, AxiosInstance } from "axios"; import { Coin } from "./coins"; -import { CosmosSdkTx, JsonObject, Model, parseWasmData, StdTx, WasmData } from "./types"; - -const { fromBase64, fromUtf8, toHex, toUtf8 } = Encoding; +import { CosmosSdkTx, StdTx } from "./types"; export interface CosmosSdkAccount { /** Bech32 account address */ @@ -210,13 +208,6 @@ type RestClientResponse = | WasmResponse | WasmResponse; -/** Unfortunately, Cosmos SDK encodes empty arrays as null */ -type CosmosSdkArray = ReadonlyArray | null; - -function normalizeArray(backend: CosmosSdkArray): ReadonlyArray { - return backend || []; -} - /** * The mode used to send transaction * @@ -231,17 +222,6 @@ export enum BroadcastMode { Async = "async", } -function isWasmError(resp: WasmResponse): resp is WasmError { - return (resp as WasmError).error !== undefined; -} - -function unwrapWasmResponse(response: WasmResponse): T { - if (isWasmError(response)) { - throw new Error(response.error); - } - return response.result; -} - // We want to get message data from 500 errors // https://stackoverflow.com/questions/56577124/how-to-handle-500-error-message-with-axios // this should be chained to catch one error and throw a more informative one @@ -391,67 +371,4 @@ export class RestClient { } return responseData as PostTxsResponse; } - - // The /wasm endpoints - - // wasm rest queries are listed here: https://github.com/cosmwasm/wasmd/blob/master/x/wasm/client/rest/query.go#L19-L27 - public async listCodeInfo(): Promise { - const path = `/wasm/code`; - const responseData = (await this.get(path)) as WasmResponse>; - return normalizeArray(unwrapWasmResponse(responseData)); - } - - // this will download the original wasm bytecode by code id - // throws error if no code with this id - public async getCode(id: number): Promise { - const path = `/wasm/code/${id}`; - const responseData = (await this.get(path)) as WasmResponse; - return unwrapWasmResponse(responseData); - } - - public async listContractsByCodeId(id: number): Promise { - const path = `/wasm/code/${id}/contracts`; - const responseData = (await this.get(path)) as WasmResponse>; - return normalizeArray(unwrapWasmResponse(responseData)); - } - - /** - * Returns null when contract was not found at this address. - */ - public async getContractInfo(address: string): Promise { - const path = `/wasm/contract/${address}`; - const response = (await this.get(path)) as WasmResponse; - return unwrapWasmResponse(response); - } - - // Returns all contract state. - // This is an empty array if no such contract, or contract has no data. - public async getAllContractState(address: string): Promise { - const path = `/wasm/contract/${address}/state`; - const responseData = (await this.get(path)) as WasmResponse>; - return normalizeArray(unwrapWasmResponse(responseData)).map(parseWasmData); - } - - // Returns the data at the key if present (unknown decoded json), - // or null if no data at this (contract address, key) pair - public async queryContractRaw(address: string, key: Uint8Array): Promise { - const hexKey = toHex(key); - const path = `/wasm/contract/${address}/raw/${hexKey}?encoding=hex`; - const responseData = (await this.get(path)) as WasmResponse; - const data = unwrapWasmResponse(responseData); - return data.length === 0 ? null : fromBase64(data[0].val); - } - - /** - * Makes a smart query on the contract and parses the reponse as JSON. - * Throws error if no such contract exists, the query format is invalid or the response is invalid. - */ - public async queryContractSmart(address: string, query: object): Promise { - const encoded = toHex(toUtf8(JSON.stringify(query))); - const path = `/wasm/contract/${address}/smart/${encoded}?encoding=hex`; - const responseData = (await this.get(path)) as WasmResponse; - const result = unwrapWasmResponse(responseData); - // By convention, smart queries must return a valid JSON document (see https://github.com/CosmWasm/cosmwasm/issues/144) - return JSON.parse(fromUtf8(fromBase64(result.smart))); - } } diff --git a/packages/sdk38/src/types.ts b/packages/sdk38/src/types.ts index 0db122f9..476cb555 100644 --- a/packages/sdk38/src/types.ts +++ b/packages/sdk38/src/types.ts @@ -1,9 +1,5 @@ -import { Encoding } from "@iov/encoding"; - import { Coin } from "./coins"; -const { fromBase64, fromHex } = Encoding; - /** An Amino/Cosmos SDK StdTx */ export interface StdTx { readonly msg: ReadonlyArray; @@ -75,29 +71,3 @@ export const pubkeyType = { }; export const pubkeyTypes: readonly string[] = [pubkeyType.secp256k1, pubkeyType.ed25519, pubkeyType.sr25519]; - -export interface WasmData { - // key is hex-encoded - readonly key: string; - // value is base64 encoded - readonly val: string; -} - -// Model is a parsed WasmData object -export interface Model { - readonly key: Uint8Array; - readonly val: Uint8Array; -} - -export function parseWasmData({ key, val }: WasmData): Model { - return { - key: fromHex(key), - val: fromBase64(val), - }; -} - -/** - * An object containing a parsed JSON document. The result of JSON.parse(). - * This doen't privide any type safety over `any` but expresses intent in the code. - */ -export type JsonObject = any; diff --git a/packages/sdk38/types/cosmosclient.d.ts b/packages/sdk38/types/cosmosclient.d.ts index 0dcc72bb..0af6120a 100644 --- a/packages/sdk38/types/cosmosclient.d.ts +++ b/packages/sdk38/types/cosmosclient.d.ts @@ -1,7 +1,7 @@ import { Coin } from "./coins"; import { Log } from "./logs"; import { BroadcastMode, RestClient } from "./restclient"; -import { CosmosSdkTx, JsonObject, PubKey, StdTx } from "./types"; +import { CosmosSdkTx, PubKey, StdTx } from "./types"; export interface GetNonceResult { readonly accountNumber: number; readonly sequence: number; @@ -48,30 +48,6 @@ export interface SearchTxFilter { readonly minHeight?: number; readonly maxHeight?: number; } -export interface Code { - readonly id: number; - /** Bech32 account address */ - readonly creator: string; - /** Hex-encoded sha256 hash of the code stored here */ - readonly checksum: string; - readonly source?: string; - readonly builder?: string; -} -export interface CodeDetails extends Code { - /** The original wasm bytes */ - readonly data: Uint8Array; -} -export interface Contract { - readonly address: string; - readonly codeId: number; - /** Bech32 account address */ - readonly creator: string; - readonly label: string; -} -export interface ContractDetails extends Contract { - /** Argument passed on initialization of the contract */ - readonly initMsg: object; -} /** A transaction that is indexed as part of the transaction history */ export interface IndexedTx { readonly height: number; @@ -114,7 +90,6 @@ export declare class CosmosClient { protected readonly restClient: RestClient; /** Any address the chain considers valid (valid bech32 with proper prefix) */ protected anyValidAddress: string | undefined; - private readonly codesCache; private chainId; /** * Creates a new client to interact with a CosmWasm blockchain. @@ -149,27 +124,5 @@ export declare class CosmosClient { getBlock(height?: number): Promise; searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise; postTx(tx: StdTx): Promise; - getCodes(): Promise; - getCodeDetails(codeId: number): Promise; - getContracts(codeId: number): Promise; - /** - * Throws an error if no contract was found at the address - */ - getContract(address: string): Promise; - /** - * 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. - */ - queryContractRaw(address: string, key: Uint8Array): Promise; - /** - * 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. - */ - queryContractSmart(address: string, queryMsg: object): Promise; private txsQuery; } diff --git a/packages/sdk38/types/index.d.ts b/packages/sdk38/types/index.d.ts index 829a4403..7e10a5ff 100644 --- a/packages/sdk38/types/index.d.ts +++ b/packages/sdk38/types/index.d.ts @@ -6,10 +6,6 @@ export { Account, Block, BlockHeader, - Code, - CodeDetails, - Contract, - ContractDetails, CosmosClient, GetNonceResult, IndexedTx, diff --git a/packages/sdk38/types/restclient.d.ts b/packages/sdk38/types/restclient.d.ts index 5d5deea4..b93175d3 100644 --- a/packages/sdk38/types/restclient.d.ts +++ b/packages/sdk38/types/restclient.d.ts @@ -1,5 +1,5 @@ import { Coin } from "./coins"; -import { CosmosSdkTx, JsonObject, Model, StdTx } from "./types"; +import { CosmosSdkTx, StdTx } from "./types"; export interface CosmosSdkAccount { /** Bech32 account address */ readonly address: string; @@ -213,19 +213,5 @@ export declare class RestClient { * @param tx a signed transaction as StdTx (i.e. not wrapped in type/value container) */ postTx(tx: StdTx): Promise; - listCodeInfo(): Promise; - getCode(id: number): Promise; - listContractsByCodeId(id: number): Promise; - /** - * Returns null when contract was not found at this address. - */ - getContractInfo(address: string): Promise; - getAllContractState(address: string): Promise; - queryContractRaw(address: string, key: Uint8Array): Promise; - /** - * Makes a smart query on the contract and parses the reponse as JSON. - * Throws error if no such contract exists, the query format is invalid or the response is invalid. - */ - queryContractSmart(address: string, query: object): Promise; } export {}; diff --git a/packages/sdk38/types/types.d.ts b/packages/sdk38/types/types.d.ts index 8083027c..bb8390ed 100644 --- a/packages/sdk38/types/types.d.ts +++ b/packages/sdk38/types/types.d.ts @@ -48,17 +48,3 @@ export declare const pubkeyType: { sr25519: "tendermint/PubKeySr25519"; }; export declare const pubkeyTypes: readonly string[]; -export interface WasmData { - readonly key: string; - readonly val: string; -} -export interface Model { - readonly key: Uint8Array; - readonly val: Uint8Array; -} -export declare function parseWasmData({ key, val }: WasmData): Model; -/** - * An object containing a parsed JSON document. The result of JSON.parse(). - * This doen't privide any type safety over `any` but expresses intent in the code. - */ -export declare type JsonObject = any; From 3ceae61a0d45dc7bcd171a4e2a513c99b3bab4de Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 3 Jun 2020 13:21:53 +0200 Subject: [PATCH 14/15] Deduplicate large parts of the RestClient --- packages/cosmwasm/src/cosmwasmclient.ts | 12 +- packages/cosmwasm/src/index.ts | 2 +- packages/cosmwasm/src/restclient.spec.ts | 3 +- packages/cosmwasm/src/restclient.ts | 295 +----------------- .../cosmwasm/src/signingcosmwasmclient.ts | 3 +- packages/cosmwasm/types/cosmwasmclient.d.ts | 4 +- packages/cosmwasm/types/index.d.ts | 2 +- packages/cosmwasm/types/restclient.d.ts | 165 +--------- .../cosmwasm/types/signingcosmwasmclient.d.ts | 3 +- packages/sdk38/src/index.ts | 11 +- packages/sdk38/src/restclient.ts | 83 +---- packages/sdk38/types/index.d.ts | 11 +- packages/sdk38/types/restclient.d.ts | 66 +--- 13 files changed, 70 insertions(+), 590 deletions(-) diff --git a/packages/cosmwasm/src/cosmwasmclient.ts b/packages/cosmwasm/src/cosmwasmclient.ts index 991fa633..b0c88375 100644 --- a/packages/cosmwasm/src/cosmwasmclient.ts +++ b/packages/cosmwasm/src/cosmwasmclient.ts @@ -1,9 +1,17 @@ -import { Coin, CosmosSdkTx, decodeBech32Pubkey, IndexedTx, PubKey, StdTx } from "@cosmwasm/sdk38"; +import { + BroadcastMode, + Coin, + CosmosSdkTx, + decodeBech32Pubkey, + IndexedTx, + PubKey, + StdTx, +} from "@cosmwasm/sdk38"; import { Sha256 } from "@iov/crypto"; import { Encoding } from "@iov/encoding"; import { Log, parseLogs } from "./logs"; -import { BroadcastMode, RestClient } from "./restclient"; +import { RestClient } from "./restclient"; import { JsonObject } from "./types"; export interface GetNonceResult { diff --git a/packages/cosmwasm/src/index.ts b/packages/cosmwasm/src/index.ts index 1c03de9b..53b22ee4 100644 --- a/packages/cosmwasm/src/index.ts +++ b/packages/cosmwasm/src/index.ts @@ -1,7 +1,7 @@ import * as logs from "./logs"; export { logs }; -export { BroadcastMode, RestClient, TxsResponse } from "./restclient"; +export { RestClient, TxsResponse } from "./restclient"; export { Account, Block, diff --git a/packages/cosmwasm/src/restclient.spec.ts b/packages/cosmwasm/src/restclient.spec.ts index 02884c16..1ee42402 100644 --- a/packages/cosmwasm/src/restclient.spec.ts +++ b/packages/cosmwasm/src/restclient.spec.ts @@ -7,6 +7,7 @@ import { Msg, MsgSend, Pen, + PostTxsResponse, rawSecp256k1PubkeyToAddress, Secp256k1Pen, StdFee, @@ -26,7 +27,7 @@ import { MsgInstantiateContract, MsgStoreCode, } from "./msgs"; -import { PostTxsResponse, RestClient, TxsResponse } from "./restclient"; +import { RestClient, TxsResponse } from "./restclient"; import { SigningCosmWasmClient } from "./signingcosmwasmclient"; import cosmoshub from "./testdata/cosmoshub.json"; import { diff --git a/packages/cosmwasm/src/restclient.ts b/packages/cosmwasm/src/restclient.ts index 06c0c131..815d12ab 100644 --- a/packages/cosmwasm/src/restclient.ts +++ b/packages/cosmwasm/src/restclient.ts @@ -1,108 +1,10 @@ -import { Coin, CosmosSdkTx, StdTx } from "@cosmwasm/sdk38"; -import { Encoding, isNonNullObject } from "@iov/encoding"; -import axios, { AxiosError, AxiosInstance } from "axios"; +import { BroadcastMode, CosmosSdkTx, RestClient as BaseRestClient } from "@cosmwasm/sdk38"; +import { Encoding } from "@iov/encoding"; import { JsonObject, Model, parseWasmData, WasmData } from "./types"; const { fromBase64, fromUtf8, toHex, toUtf8 } = Encoding; -export interface CosmosSdkAccount { - /** Bech32 account address */ - readonly address: string; - readonly coins: ReadonlyArray; - /** Bech32 encoded pubkey */ - readonly public_key: string; - readonly account_number: number; - readonly sequence: number; -} - -export interface NodeInfo { - readonly protocol_version: { - readonly p2p: string; - readonly block: string; - readonly app: string; - }; - readonly id: string; - readonly listen_addr: string; - readonly network: string; - readonly version: string; - readonly channels: string; - readonly moniker: string; - readonly other: { - readonly tx_index: string; - readonly rpc_address: string; - }; -} - -export interface ApplicationVersion { - readonly name: string; - readonly server_name: string; - readonly client_name: string; - readonly version: string; - readonly commit: string; - readonly build_tags: string; - readonly go: string; -} - -export interface NodeInfoResponse { - readonly node_info: NodeInfo; - readonly application_version: ApplicationVersion; -} - -export interface BlockId { - readonly hash: string; - // TODO: here we also have this - // parts: { - // total: '1', - // hash: '7AF200C78FBF9236944E1AB270F4045CD60972B7C265E3A9DA42973397572931' - // } -} - -export interface BlockHeader { - readonly version: { - readonly block: string; - readonly app: string; - }; - readonly height: string; - readonly chain_id: string; - /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ - readonly time: string; - readonly last_commit_hash: string; - readonly last_block_id: BlockId; - /** Can be empty */ - readonly data_hash: string; - readonly validators_hash: string; - readonly next_validators_hash: string; - readonly consensus_hash: string; - readonly app_hash: string; - /** Can be empty */ - readonly last_results_hash: string; - /** Can be empty */ - readonly evidence_hash: string; - readonly proposer_address: string; -} - -export interface Block { - readonly header: BlockHeader; - readonly data: { - /** Array of base64 encoded transactions */ - readonly txs: ReadonlyArray | null; - }; -} - -export interface BlockResponse { - readonly block_id: BlockId; - readonly block: Block; -} - -interface AuthAccountsResponse { - readonly height: string; - readonly result: { - readonly type: "cosmos-sdk/Account"; - readonly value: CosmosSdkAccount; - }; -} - // Currently all wasm query responses return json-encoded strings... // later deprecate this and use the specific types for result // (assuming it is inlined, no second parse needed) @@ -134,33 +36,6 @@ export interface TxsResponse { readonly timestamp: string; } -interface SearchTxsResponse { - readonly total_count: string; - readonly count: string; - readonly page_number: string; - readonly page_total: string; - readonly limit: string; - readonly txs: readonly TxsResponse[]; -} - -export interface PostTxsResponse { - readonly height: string; - readonly txhash: string; - readonly code?: number; - readonly raw_log?: string; - /** The same as `raw_log` but deserialized? */ - readonly logs?: object; - /** The gas limit as set by the user */ - readonly gas_wanted?: string; - /** The gas used by the execution */ - readonly gas_used?: string; -} - -interface EncodeTxResponse { - // base64-encoded amino-binary encoded representation - readonly tx: string; -} - export interface CodeInfo { readonly id: number; /** Bech32 account address */ @@ -196,20 +71,6 @@ interface SmartQueryResponse { readonly smart: string; } -type RestClientResponse = - | NodeInfoResponse - | BlockResponse - | AuthAccountsResponse - | TxsResponse - | SearchTxsResponse - | PostTxsResponse - | EncodeTxResponse - | WasmResponse - | WasmResponse - | WasmResponse - | WasmResponse - | WasmResponse; - /** Unfortunately, Cosmos SDK encodes empty arrays as null */ type CosmosSdkArray = ReadonlyArray | null; @@ -217,20 +78,6 @@ function normalizeArray(backend: CosmosSdkArray): ReadonlyArray { return backend || []; } -/** - * The mode used to send transaction - * - * @see https://cosmos.network/rpc/#/Transactions/post_txs - */ -export enum BroadcastMode { - /** Return after tx commit */ - Block = "block", - /** Return afer CheckTx */ - Sync = "sync", - /** Return right away */ - Async = "async", -} - function isWasmError(resp: WasmResponse): resp is WasmError { return (resp as WasmError).error !== undefined; } @@ -242,32 +89,7 @@ function unwrapWasmResponse(response: WasmResponse): T { return response.result; } -// We want to get message data from 500 errors -// https://stackoverflow.com/questions/56577124/how-to-handle-500-error-message-with-axios -// this should be chained to catch one error and throw a more informative one -function parseAxiosError(err: AxiosError): never { - // use the error message sent from server, not default 500 msg - if (err.response?.data) { - let errorText: string; - const data = err.response.data; - // expect { error: string }, but otherwise dump - if (data.error && typeof data.error === "string") { - errorText = data.error; - } else if (typeof data === "string") { - errorText = data; - } else { - errorText = JSON.stringify(data); - } - throw new Error(`${errorText} (HTTP ${err.response.status})`); - } else { - throw err; - } -} - -export class RestClient { - private readonly client: AxiosInstance; - private readonly broadcastMode: BroadcastMode; - +export class RestClient extends BaseRestClient { /** * Creates a new client to interact with a Cosmos SDK light client daemon. * This class tries to be a direct mapping onto the API. Some basic decoding and normalizatin is done @@ -280,116 +102,7 @@ export class RestClient { * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns */ public constructor(apiUrl: string, broadcastMode = BroadcastMode.Block) { - const headers = { - post: { "Content-Type": "application/json" }, - }; - this.client = axios.create({ - baseURL: apiUrl, - headers: headers, - }); - this.broadcastMode = broadcastMode; - } - - public async get(path: string): Promise { - const { data } = await this.client.get(path).catch(parseAxiosError); - if (data === null) { - throw new Error("Received null response from server"); - } - return data; - } - - public async post(path: string, params: any): Promise { - if (!isNonNullObject(params)) throw new Error("Got unexpected type of params. Expected object."); - const { data } = await this.client.post(path, params).catch(parseAxiosError); - if (data === null) { - throw new Error("Received null response from server"); - } - return data; - } - - // The /auth endpoints - - public async authAccounts(address: string): Promise { - const path = `/auth/accounts/${address}`; - const responseData = await this.get(path); - if ((responseData as any).result.type !== "cosmos-sdk/Account") { - throw new Error("Unexpected response data format"); - } - return responseData as AuthAccountsResponse; - } - - // The /blocks endpoints - - public async blocksLatest(): Promise { - const responseData = await this.get("/blocks/latest"); - if (!(responseData as any).block) { - throw new Error("Unexpected response data format"); - } - return responseData as BlockResponse; - } - - public async blocks(height: number): Promise { - const responseData = await this.get(`/blocks/${height}`); - if (!(responseData as any).block) { - throw new Error("Unexpected response data format"); - } - return responseData as BlockResponse; - } - - // The /node_info endpoint - - public async nodeInfo(): Promise { - const responseData = await this.get("/node_info"); - if (!(responseData as any).node_info) { - throw new Error("Unexpected response data format"); - } - return responseData as NodeInfoResponse; - } - - // The /txs endpoints - - public async txById(id: string): Promise { - const responseData = await this.get(`/txs/${id}`); - if (!(responseData as any).tx) { - throw new Error("Unexpected response data format"); - } - return responseData as TxsResponse; - } - - public async txsQuery(query: string): Promise { - const responseData = await this.get(`/txs?${query}`); - if (!(responseData as any).txs) { - throw new Error("Unexpected response data format"); - } - return responseData as SearchTxsResponse; - } - - /** returns the amino-encoding of the transaction performed by the server */ - public async encodeTx(tx: CosmosSdkTx): Promise { - const responseData = await this.post("/txs/encode", tx); - if (!(responseData as any).tx) { - throw new Error("Unexpected response data format"); - } - return Encoding.fromBase64((responseData as EncodeTxResponse).tx); - } - - /** - * Broadcasts a signed transaction to into the transaction pool. - * Depending on the RestClient's broadcast mode, this might or might - * wait for checkTx or deliverTx to be executed before returning. - * - * @param tx a signed transaction as StdTx (i.e. not wrapped in type/value container) - */ - public async postTx(tx: StdTx): Promise { - const params = { - tx: tx, - mode: this.broadcastMode, - }; - const responseData = await this.post("/txs", params); - if (!(responseData as any).txhash) { - throw new Error("Unexpected response data format"); - } - return responseData as PostTxsResponse; + super(apiUrl, broadcastMode); } // The /wasm endpoints diff --git a/packages/cosmwasm/src/signingcosmwasmclient.ts b/packages/cosmwasm/src/signingcosmwasmclient.ts index 11ed27dd..05918010 100644 --- a/packages/cosmwasm/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm/src/signingcosmwasmclient.ts @@ -1,4 +1,4 @@ -import { Coin, coins, makeSignBytes, MsgSend, StdFee, StdSignature } from "@cosmwasm/sdk38"; +import { BroadcastMode, Coin, coins, makeSignBytes, MsgSend, StdFee, StdSignature } from "@cosmwasm/sdk38"; import { Sha256 } from "@iov/crypto"; import { Encoding } from "@iov/encoding"; import pako from "pako"; @@ -7,7 +7,6 @@ import { isValidBuilder } from "./builder"; import { Account, CosmWasmClient, GetNonceResult, PostTxResult } from "./cosmwasmclient"; import { findAttribute, Log } from "./logs"; import { MsgExecuteContract, MsgInstantiateContract, MsgStoreCode } from "./msgs"; -import { BroadcastMode } from "./restclient"; export interface SigningCallback { (signBytes: Uint8Array): Promise; diff --git a/packages/cosmwasm/types/cosmwasmclient.d.ts b/packages/cosmwasm/types/cosmwasmclient.d.ts index 4978b694..15f973e2 100644 --- a/packages/cosmwasm/types/cosmwasmclient.d.ts +++ b/packages/cosmwasm/types/cosmwasmclient.d.ts @@ -1,6 +1,6 @@ -import { Coin, CosmosSdkTx, IndexedTx, PubKey, StdTx } from "@cosmwasm/sdk38"; +import { BroadcastMode, Coin, CosmosSdkTx, IndexedTx, PubKey, StdTx } from "@cosmwasm/sdk38"; import { Log } from "./logs"; -import { BroadcastMode, RestClient } from "./restclient"; +import { RestClient } from "./restclient"; import { JsonObject } from "./types"; export interface GetNonceResult { readonly accountNumber: number; diff --git a/packages/cosmwasm/types/index.d.ts b/packages/cosmwasm/types/index.d.ts index e070d48c..047f950e 100644 --- a/packages/cosmwasm/types/index.d.ts +++ b/packages/cosmwasm/types/index.d.ts @@ -1,6 +1,6 @@ import * as logs from "./logs"; export { logs }; -export { BroadcastMode, RestClient, TxsResponse } from "./restclient"; +export { RestClient, TxsResponse } from "./restclient"; export { Account, Block, diff --git a/packages/cosmwasm/types/restclient.d.ts b/packages/cosmwasm/types/restclient.d.ts index 3bf65fe2..c5c3d1ee 100644 --- a/packages/cosmwasm/types/restclient.d.ts +++ b/packages/cosmwasm/types/restclient.d.ts @@ -1,96 +1,5 @@ -import { Coin, CosmosSdkTx, StdTx } from "@cosmwasm/sdk38"; +import { BroadcastMode, CosmosSdkTx, RestClient as BaseRestClient } from "@cosmwasm/sdk38"; import { JsonObject, Model } from "./types"; -export interface CosmosSdkAccount { - /** Bech32 account address */ - readonly address: string; - readonly coins: ReadonlyArray; - /** Bech32 encoded pubkey */ - readonly public_key: string; - readonly account_number: number; - readonly sequence: number; -} -export interface NodeInfo { - readonly protocol_version: { - readonly p2p: string; - readonly block: string; - readonly app: string; - }; - readonly id: string; - readonly listen_addr: string; - readonly network: string; - readonly version: string; - readonly channels: string; - readonly moniker: string; - readonly other: { - readonly tx_index: string; - readonly rpc_address: string; - }; -} -export interface ApplicationVersion { - readonly name: string; - readonly server_name: string; - readonly client_name: string; - readonly version: string; - readonly commit: string; - readonly build_tags: string; - readonly go: string; -} -export interface NodeInfoResponse { - readonly node_info: NodeInfo; - readonly application_version: ApplicationVersion; -} -export interface BlockId { - readonly hash: string; -} -export interface BlockHeader { - readonly version: { - readonly block: string; - readonly app: string; - }; - readonly height: string; - readonly chain_id: string; - /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */ - readonly time: string; - readonly last_commit_hash: string; - readonly last_block_id: BlockId; - /** Can be empty */ - readonly data_hash: string; - readonly validators_hash: string; - readonly next_validators_hash: string; - readonly consensus_hash: string; - readonly app_hash: string; - /** Can be empty */ - readonly last_results_hash: string; - /** Can be empty */ - readonly evidence_hash: string; - readonly proposer_address: string; -} -export interface Block { - readonly header: BlockHeader; - readonly data: { - /** Array of base64 encoded transactions */ - readonly txs: ReadonlyArray | null; - }; -} -export interface BlockResponse { - readonly block_id: BlockId; - readonly block: Block; -} -interface AuthAccountsResponse { - readonly height: string; - readonly result: { - readonly type: "cosmos-sdk/Account"; - readonly value: CosmosSdkAccount; - }; -} -declare type WasmResponse = WasmSuccess | WasmError; -interface WasmSuccess { - readonly height: string; - readonly result: T; -} -interface WasmError { - readonly error: string; -} export interface TxsResponse { readonly height: string; readonly txhash: string; @@ -107,29 +16,6 @@ export interface TxsResponse { readonly gas_used?: string; readonly timestamp: string; } -interface SearchTxsResponse { - readonly total_count: string; - readonly count: string; - readonly page_number: string; - readonly page_total: string; - readonly limit: string; - readonly txs: readonly TxsResponse[]; -} -export interface PostTxsResponse { - readonly height: string; - readonly txhash: string; - readonly code?: number; - readonly raw_log?: string; - /** The same as `raw_log` but deserialized? */ - readonly logs?: object; - /** The gas limit as set by the user */ - readonly gas_wanted?: string; - /** The gas used by the execution */ - readonly gas_used?: string; -} -interface EncodeTxResponse { - readonly tx: string; -} export interface CodeInfo { readonly id: number; /** Bech32 account address */ @@ -154,35 +40,7 @@ export interface ContractDetails extends ContractInfo { /** Argument passed on initialization of the contract */ readonly init_msg: object; } -declare type RestClientResponse = - | NodeInfoResponse - | BlockResponse - | AuthAccountsResponse - | TxsResponse - | SearchTxsResponse - | PostTxsResponse - | EncodeTxResponse - | WasmResponse - | WasmResponse - | WasmResponse - | WasmResponse - | WasmResponse; -/** - * The mode used to send transaction - * - * @see https://cosmos.network/rpc/#/Transactions/post_txs - */ -export declare enum BroadcastMode { - /** Return after tx commit */ - Block = "block", - /** Return afer CheckTx */ - Sync = "sync", - /** Return right away */ - Async = "async", -} -export declare class RestClient { - private readonly client; - private readonly broadcastMode; +export declare class RestClient extends BaseRestClient { /** * Creates a new client to interact with a Cosmos SDK light client daemon. * This class tries to be a direct mapping onto the API. Some basic decoding and normalizatin is done @@ -195,24 +53,6 @@ export declare class RestClient { * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns */ constructor(apiUrl: string, broadcastMode?: BroadcastMode); - get(path: string): Promise; - post(path: string, params: any): Promise; - authAccounts(address: string): Promise; - blocksLatest(): Promise; - blocks(height: number): Promise; - nodeInfo(): Promise; - txById(id: string): Promise; - txsQuery(query: string): Promise; - /** returns the amino-encoding of the transaction performed by the server */ - encodeTx(tx: CosmosSdkTx): Promise; - /** - * Broadcasts a signed transaction to into the transaction pool. - * Depending on the RestClient's broadcast mode, this might or might - * wait for checkTx or deliverTx to be executed before returning. - * - * @param tx a signed transaction as StdTx (i.e. not wrapped in type/value container) - */ - postTx(tx: StdTx): Promise; listCodeInfo(): Promise; getCode(id: number): Promise; listContractsByCodeId(id: number): Promise; @@ -228,4 +68,3 @@ export declare class RestClient { */ queryContractSmart(address: string, query: object): Promise; } -export {}; diff --git a/packages/cosmwasm/types/signingcosmwasmclient.d.ts b/packages/cosmwasm/types/signingcosmwasmclient.d.ts index 75bd9b76..b0c3b280 100644 --- a/packages/cosmwasm/types/signingcosmwasmclient.d.ts +++ b/packages/cosmwasm/types/signingcosmwasmclient.d.ts @@ -1,7 +1,6 @@ -import { Coin, StdFee, StdSignature } from "@cosmwasm/sdk38"; +import { BroadcastMode, Coin, StdFee, StdSignature } from "@cosmwasm/sdk38"; import { Account, CosmWasmClient, GetNonceResult, PostTxResult } from "./cosmwasmclient"; import { Log } from "./logs"; -import { BroadcastMode } from "./restclient"; export interface SigningCallback { (signBytes: Uint8Array): Promise; } diff --git a/packages/sdk38/src/index.ts b/packages/sdk38/src/index.ts index 7e86fdc9..f0cfeafb 100644 --- a/packages/sdk38/src/index.ts +++ b/packages/sdk38/src/index.ts @@ -21,7 +21,16 @@ export { } from "./cosmosclient"; export { unmarshalTx } from "./decoding"; export { makeSignBytes, marshalTx } from "./encoding"; -export { BroadcastMode, RestClient, TxsResponse } from "./restclient"; +export { + AuthAccountsResponse, + BlockResponse, + BroadcastMode, + PostTxsResponse, + NodeInfoResponse, + RestClient, + SearchTxsResponse, + TxsResponse, +} from "./restclient"; export { Pen, Secp256k1Pen, makeCosmoshubPath } from "./pen"; export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; export { findSequenceForSignedTx } from "./sequence"; diff --git a/packages/sdk38/src/restclient.ts b/packages/sdk38/src/restclient.ts index a0d07624..85afb05f 100644 --- a/packages/sdk38/src/restclient.ts +++ b/packages/sdk38/src/restclient.ts @@ -14,7 +14,7 @@ export interface CosmosSdkAccount { readonly sequence: number; } -export interface NodeInfo { +interface NodeInfo { readonly protocol_version: { readonly p2p: string; readonly block: string; @@ -32,7 +32,7 @@ export interface NodeInfo { }; } -export interface ApplicationVersion { +interface ApplicationVersion { readonly name: string; readonly server_name: string; readonly client_name: string; @@ -47,7 +47,7 @@ export interface NodeInfoResponse { readonly application_version: ApplicationVersion; } -export interface BlockId { +interface BlockId { readonly hash: string; // TODO: here we also have this // parts: { @@ -56,7 +56,7 @@ export interface BlockId { // } } -export interface BlockHeader { +interface BlockHeader { readonly version: { readonly block: string; readonly app: string; @@ -80,7 +80,7 @@ export interface BlockHeader { readonly proposer_address: string; } -export interface Block { +interface Block { readonly header: BlockHeader; readonly data: { /** Array of base64 encoded transactions */ @@ -93,7 +93,7 @@ export interface BlockResponse { readonly block: Block; } -interface AuthAccountsResponse { +export interface AuthAccountsResponse { readonly height: string; readonly result: { readonly type: "cosmos-sdk/Account"; @@ -132,7 +132,7 @@ export interface TxsResponse { readonly timestamp: string; } -interface SearchTxsResponse { +export interface SearchTxsResponse { readonly total_count: string; readonly count: string; readonly page_number: string; @@ -159,55 +159,6 @@ interface EncodeTxResponse { readonly tx: string; } -export interface CodeInfo { - readonly id: number; - /** Bech32 account address */ - readonly creator: string; - /** Hex-encoded sha256 hash of the code stored here */ - readonly data_hash: string; - // TODO: these are not supported in current wasmd - readonly source?: string; - readonly builder?: string; -} - -export interface CodeDetails extends CodeInfo { - /** Base64 encoded raw wasm data */ - readonly data: string; -} - -// This is list view, without contract info -export interface ContractInfo { - readonly address: string; - readonly code_id: number; - /** Bech32 account address */ - readonly creator: string; - readonly label: string; -} - -export interface ContractDetails extends ContractInfo { - /** Argument passed on initialization of the contract */ - readonly init_msg: object; -} - -interface SmartQueryResponse { - // base64 encoded response - readonly smart: string; -} - -type RestClientResponse = - | NodeInfoResponse - | BlockResponse - | AuthAccountsResponse - | TxsResponse - | SearchTxsResponse - | PostTxsResponse - | EncodeTxResponse - | WasmResponse - | WasmResponse - | WasmResponse - | WasmResponse - | WasmResponse; - /** * The mode used to send transaction * @@ -270,7 +221,7 @@ export class RestClient { this.broadcastMode = broadcastMode; } - public async get(path: string): Promise { + public async get(path: string): Promise { const { data } = await this.client.get(path).catch(parseAxiosError); if (data === null) { throw new Error("Received null response from server"); @@ -278,7 +229,7 @@ export class RestClient { return data; } - public async post(path: string, params: any): Promise { + public async post(path: string, params: any): Promise { if (!isNonNullObject(params)) throw new Error("Got unexpected type of params. Expected object."); const { data } = await this.client.post(path, params).catch(parseAxiosError); if (data === null) { @@ -292,7 +243,7 @@ export class RestClient { public async authAccounts(address: string): Promise { const path = `/auth/accounts/${address}`; const responseData = await this.get(path); - if ((responseData as any).result.type !== "cosmos-sdk/Account") { + if (responseData.result.type !== "cosmos-sdk/Account") { throw new Error("Unexpected response data format"); } return responseData as AuthAccountsResponse; @@ -302,7 +253,7 @@ export class RestClient { public async blocksLatest(): Promise { const responseData = await this.get("/blocks/latest"); - if (!(responseData as any).block) { + if (!responseData.block) { throw new Error("Unexpected response data format"); } return responseData as BlockResponse; @@ -310,7 +261,7 @@ export class RestClient { public async blocks(height: number): Promise { const responseData = await this.get(`/blocks/${height}`); - if (!(responseData as any).block) { + if (!responseData.block) { throw new Error("Unexpected response data format"); } return responseData as BlockResponse; @@ -320,7 +271,7 @@ export class RestClient { public async nodeInfo(): Promise { const responseData = await this.get("/node_info"); - if (!(responseData as any).node_info) { + if (!responseData.node_info) { throw new Error("Unexpected response data format"); } return responseData as NodeInfoResponse; @@ -330,7 +281,7 @@ export class RestClient { public async txById(id: string): Promise { const responseData = await this.get(`/txs/${id}`); - if (!(responseData as any).tx) { + if (!responseData.tx) { throw new Error("Unexpected response data format"); } return responseData as TxsResponse; @@ -338,7 +289,7 @@ export class RestClient { public async txsQuery(query: string): Promise { const responseData = await this.get(`/txs?${query}`); - if (!(responseData as any).txs) { + if (!responseData.txs) { throw new Error("Unexpected response data format"); } return responseData as SearchTxsResponse; @@ -347,7 +298,7 @@ export class RestClient { /** returns the amino-encoding of the transaction performed by the server */ public async encodeTx(tx: CosmosSdkTx): Promise { const responseData = await this.post("/txs/encode", tx); - if (!(responseData as any).tx) { + if (!responseData.tx) { throw new Error("Unexpected response data format"); } return Encoding.fromBase64((responseData as EncodeTxResponse).tx); @@ -366,7 +317,7 @@ export class RestClient { mode: this.broadcastMode, }; const responseData = await this.post("/txs", params); - if (!(responseData as any).txhash) { + if (!responseData.txhash) { throw new Error("Unexpected response data format"); } return responseData as PostTxsResponse; diff --git a/packages/sdk38/types/index.d.ts b/packages/sdk38/types/index.d.ts index 7e10a5ff..2823e7e2 100644 --- a/packages/sdk38/types/index.d.ts +++ b/packages/sdk38/types/index.d.ts @@ -19,7 +19,16 @@ export { } from "./cosmosclient"; export { unmarshalTx } from "./decoding"; export { makeSignBytes, marshalTx } from "./encoding"; -export { BroadcastMode, RestClient, TxsResponse } from "./restclient"; +export { + AuthAccountsResponse, + BlockResponse, + BroadcastMode, + PostTxsResponse, + NodeInfoResponse, + RestClient, + SearchTxsResponse, + TxsResponse, +} from "./restclient"; export { Pen, Secp256k1Pen, makeCosmoshubPath } from "./pen"; export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; export { findSequenceForSignedTx } from "./sequence"; diff --git a/packages/sdk38/types/restclient.d.ts b/packages/sdk38/types/restclient.d.ts index b93175d3..0ecbfe88 100644 --- a/packages/sdk38/types/restclient.d.ts +++ b/packages/sdk38/types/restclient.d.ts @@ -9,7 +9,7 @@ export interface CosmosSdkAccount { readonly account_number: number; readonly sequence: number; } -export interface NodeInfo { +interface NodeInfo { readonly protocol_version: { readonly p2p: string; readonly block: string; @@ -26,7 +26,7 @@ export interface NodeInfo { readonly rpc_address: string; }; } -export interface ApplicationVersion { +interface ApplicationVersion { readonly name: string; readonly server_name: string; readonly client_name: string; @@ -39,10 +39,10 @@ export interface NodeInfoResponse { readonly node_info: NodeInfo; readonly application_version: ApplicationVersion; } -export interface BlockId { +interface BlockId { readonly hash: string; } -export interface BlockHeader { +interface BlockHeader { readonly version: { readonly block: string; readonly app: string; @@ -65,7 +65,7 @@ export interface BlockHeader { readonly evidence_hash: string; readonly proposer_address: string; } -export interface Block { +interface Block { readonly header: BlockHeader; readonly data: { /** Array of base64 encoded transactions */ @@ -76,21 +76,13 @@ export interface BlockResponse { readonly block_id: BlockId; readonly block: Block; } -interface AuthAccountsResponse { +export interface AuthAccountsResponse { readonly height: string; readonly result: { readonly type: "cosmos-sdk/Account"; readonly value: CosmosSdkAccount; }; } -declare type WasmResponse = WasmSuccess | WasmError; -interface WasmSuccess { - readonly height: string; - readonly result: T; -} -interface WasmError { - readonly error: string; -} export interface TxsResponse { readonly height: string; readonly txhash: string; @@ -107,7 +99,7 @@ export interface TxsResponse { readonly gas_used?: string; readonly timestamp: string; } -interface SearchTxsResponse { +export interface SearchTxsResponse { readonly total_count: string; readonly count: string; readonly page_number: string; @@ -127,46 +119,6 @@ export interface PostTxsResponse { /** The gas used by the execution */ readonly gas_used?: string; } -interface EncodeTxResponse { - readonly tx: string; -} -export interface CodeInfo { - readonly id: number; - /** Bech32 account address */ - readonly creator: string; - /** Hex-encoded sha256 hash of the code stored here */ - readonly data_hash: string; - readonly source?: string; - readonly builder?: string; -} -export interface CodeDetails extends CodeInfo { - /** Base64 encoded raw wasm data */ - readonly data: string; -} -export interface ContractInfo { - readonly address: string; - readonly code_id: number; - /** Bech32 account address */ - readonly creator: string; - readonly label: string; -} -export interface ContractDetails extends ContractInfo { - /** Argument passed on initialization of the contract */ - readonly init_msg: object; -} -declare type RestClientResponse = - | NodeInfoResponse - | BlockResponse - | AuthAccountsResponse - | TxsResponse - | SearchTxsResponse - | PostTxsResponse - | EncodeTxResponse - | WasmResponse - | WasmResponse - | WasmResponse - | WasmResponse - | WasmResponse; /** * The mode used to send transaction * @@ -195,8 +147,8 @@ export declare class RestClient { * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns */ constructor(apiUrl: string, broadcastMode?: BroadcastMode); - get(path: string): Promise; - post(path: string, params: any): Promise; + get(path: string): Promise; + post(path: string, params: any): Promise; authAccounts(address: string): Promise; blocksLatest(): Promise; blocks(height: number): Promise; From 326ebd3d1883ac627445b5637af2b22590285d1d Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 3 Jun 2020 13:23:32 +0200 Subject: [PATCH 15/15] Remove SDK testdata from cosmwasm package --- .../cosmwasm/src/testdata/txresponse1.json | 57 ------------------- .../cosmwasm/src/testdata/txresponse2.json | 57 ------------------- .../cosmwasm/src/testdata/txresponse3.json | 57 ------------------- 3 files changed, 171 deletions(-) delete mode 100644 packages/cosmwasm/src/testdata/txresponse1.json delete mode 100644 packages/cosmwasm/src/testdata/txresponse2.json delete mode 100644 packages/cosmwasm/src/testdata/txresponse3.json diff --git a/packages/cosmwasm/src/testdata/txresponse1.json b/packages/cosmwasm/src/testdata/txresponse1.json deleted file mode 100644 index 703ea696..00000000 --- a/packages/cosmwasm/src/testdata/txresponse1.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "height": "15888", - "txhash": "672DEDE8EF4DE8B5818959F417CCA357079D4D7A19C4B65443C7FBF8176AABF9", - "raw_log": "[{\"msg_index\":0,\"log\":\"\",\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2\"},{\"key\":\"amount\",\"value\":\"75000ucosm\"}]}]}]", - "logs": [ - { - "msg_index": 0, - "log": "", - "events": [ - { - "type": "message", - "attributes": [ - { "key": "action", "value": "send" }, - { "key": "sender", "value": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6" }, - { "key": "module", "value": "bank" } - ] - }, - { - "type": "transfer", - "attributes": [ - { "key": "recipient", "value": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2" }, - { "key": "amount", "value": "75000ucosm" } - ] - } - ] - } - ], - "gas_wanted": "200000", - "gas_used": "65407", - "tx": { - "type": "cosmos-sdk/StdTx", - "value": { - "msg": [ - { - "type": "cosmos-sdk/MsgSend", - "value": { - "from_address": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - "to_address": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2", - "amount": [{ "denom": "ucosm", "amount": "75000" }] - } - } - ], - "fee": { "amount": [{ "denom": "ucosm", "amount": "5000" }], "gas": "200000" }, - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ" - }, - "signature": "US7oH8S/8TxVrtBQkOhHxAM+oDB2spNAEawgh6H8CCFLRMOJK+uvQZZ6ceUgUsvDbxwCz7re1RU272fymMYRZQ==" - } - ], - "memo": "My first payment" - } - }, - "timestamp": "2020-02-14T11:25:55Z" -} diff --git a/packages/cosmwasm/src/testdata/txresponse2.json b/packages/cosmwasm/src/testdata/txresponse2.json deleted file mode 100644 index 0d1a37f9..00000000 --- a/packages/cosmwasm/src/testdata/txresponse2.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "height": "16456", - "txhash": "7BFE4B93AF190F60132C62D08FDF50BE462FBCE374EB13D3FD0C32461E771EC0", - "raw_log": "[{\"msg_index\":0,\"log\":\"\",\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2\"},{\"key\":\"amount\",\"value\":\"75000ucosm\"}]}]}]", - "logs": [ - { - "msg_index": 0, - "log": "", - "events": [ - { - "type": "message", - "attributes": [ - { "key": "action", "value": "send" }, - { "key": "sender", "value": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6" }, - { "key": "module", "value": "bank" } - ] - }, - { - "type": "transfer", - "attributes": [ - { "key": "recipient", "value": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2" }, - { "key": "amount", "value": "75000ucosm" } - ] - } - ] - } - ], - "gas_wanted": "200000", - "gas_used": "65407", - "tx": { - "type": "cosmos-sdk/StdTx", - "value": { - "msg": [ - { - "type": "cosmos-sdk/MsgSend", - "value": { - "from_address": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - "to_address": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2", - "amount": [{ "denom": "ucosm", "amount": "75000" }] - } - } - ], - "fee": { "amount": [{ "denom": "ucosm", "amount": "5000" }], "gas": "200000" }, - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ" - }, - "signature": "ltvd9Rb3RF4zjbUVrpDpkok34g+py7XR8ZcM0tZUYRxxVdcMEin010x+ZFd/mOuutPj9fDmSENnienc/yi4msw==" - } - ], - "memo": "My first payment" - } - }, - "timestamp": "2020-02-14T11:35:41Z" -} diff --git a/packages/cosmwasm/src/testdata/txresponse3.json b/packages/cosmwasm/src/testdata/txresponse3.json deleted file mode 100644 index 8ffd0727..00000000 --- a/packages/cosmwasm/src/testdata/txresponse3.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "height": "20730", - "txhash": "625BC75E697F73DA037387C34002BB2F682E7ACDCC4E015D3E90420516C6D0C8", - "raw_log": "[{\"msg_index\":0,\"log\":\"\",\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2\"},{\"key\":\"amount\",\"value\":\"75000ucosm\"}]}]}]", - "logs": [ - { - "msg_index": 0, - "log": "", - "events": [ - { - "type": "message", - "attributes": [ - { "key": "action", "value": "send" }, - { "key": "sender", "value": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6" }, - { "key": "module", "value": "bank" } - ] - }, - { - "type": "transfer", - "attributes": [ - { "key": "recipient", "value": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2" }, - { "key": "amount", "value": "75000ucosm" } - ] - } - ] - } - ], - "gas_wanted": "200000", - "gas_used": "65407", - "tx": { - "type": "cosmos-sdk/StdTx", - "value": { - "msg": [ - { - "type": "cosmos-sdk/MsgSend", - "value": { - "from_address": "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - "to_address": "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2", - "amount": [{ "denom": "ucosm", "amount": "75000" }] - } - } - ], - "fee": { "amount": [{ "denom": "ucosm", "amount": "5000" }], "gas": "200000" }, - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ" - }, - "signature": "eOFGl1tIHDMv3JdCK9fRSikVbYUD8+B0ksb3dJFya8MPYgpEpdSA7zZc+5n/cW6LR/BJdib4nqmJQv1yD9lm3g==" - } - ], - "memo": "My first payment" - } - }, - "timestamp": "2020-02-14T12:48:56Z" -}