Merge pull request #571 from cosmos/491-wasm-stargate

Add cosmwasm-stargate package
This commit is contained in:
Will Clark 2020-12-15 13:38:00 +01:00 committed by GitHub
commit cd3b99bda5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 15816 additions and 17 deletions

View File

@ -6,6 +6,7 @@
@cosmjs/launchpad instead.
- @cosmjs/cosmwasm: Export `JsonObject`, `ChangeAdminResult` and `WasmData`
types as well as `isValidBuilder` and `parseWasmData` functions.
- @cosmjs/cosmwasm-stargate: Add new package for CosmWasm Stargate support.
- @cosmjs/launchpad: Add `Secp256k1Wallet` to manage a single raw secp256k1
keypair.
- @cosmjs/launchpad: `OfflineSigner` types `sign` method renamed `signAmino`
@ -25,6 +26,7 @@
which allows skipping the auto-detection.
- @cosmjs/tendermint-rpc: Remove export `v0_33` in favour of `adaptor33` and
`adaptor34`. Export the `Adaptor` type.
- @cosmjs/tendermint-rpc: Export `DateTime` class.
## 0.23.1 (2020-10-27)

View File

@ -0,0 +1 @@
../../.eslintignore

7
packages/cosmwasm-stargate/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
build/
dist/
docs/
# protobuf code generation
proto/
tmp/

View File

@ -0,0 +1 @@
../../.nycrc.yml

View File

@ -0,0 +1,27 @@
# @cosmjs/cosmwasm-stargate
[![npm version](https://img.shields.io/npm/v/@cosmjs/cosmwasm-stargate.svg)](https://www.npmjs.com/package/@cosmjs/cosmwasm-stargate)
An SDK to build CosmWasm clients.
## Compatibility
| CosmWasm | x/wasm | @cosmjs/cosmwasm-stargate |
| -------- | ------ | ------------------------- |
| 0.12 | 0.13 | `^0.24.0` |
## Development
Updating Hackatom development contract in `src/testdata/contract.json`:
```sh
cd packages/cosmwasm-stargate
export HACKATOM_URL=https://github.com/CosmWasm/cosmwasm/releases/download/v0.12.0/hackatom.wasm
echo "{\"// source\": \"$HACKATOM_URL\", \"data\": \"$(curl -sS --location $HACKATOM_URL | base64)\" }" | jq > src/testdata/contract.json
```
## License
This package is part of the cosmjs repository, licensed under the Apache License
2.0 (see [NOTICE](https://github.com/CosmWasm/cosmjs/blob/master/NOTICE) and
[LICENSE](https://github.com/CosmWasm/cosmjs/blob/master/LICENSE)).

View File

@ -0,0 +1,33 @@
#!/usr/bin/env node
/* eslint-disable @typescript-eslint/naming-convention */
require("source-map-support").install();
const defaultSpecReporterConfig = require("../../jasmine-spec-reporter.config.json");
// setup Jasmine
const Jasmine = require("jasmine");
const jasmine = new Jasmine();
jasmine.loadConfig({
spec_dir: "build",
spec_files: ["**/*.spec.js"],
helpers: [],
random: false,
seed: null,
stopSpecOnExpectationFailure: false,
});
jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 15 * 1000;
// setup reporter
const { SpecReporter } = require("jasmine-spec-reporter");
const reporter = new SpecReporter({
...defaultSpecReporterConfig,
spec: {
...defaultSpecReporterConfig.spec,
displaySuccessful: !process.argv.includes("--quiet"),
},
});
// initialize and execute
jasmine.env.clearReporters();
jasmine.addReporter(reporter);
jasmine.execute();

View File

@ -0,0 +1,47 @@
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,
});
};

View File

@ -0,0 +1,68 @@
{
"name": "@cosmjs/cosmwasm-stargate",
"version": "0.24.0-alpha.10",
"description": "CosmWasm SDK",
"contributors": [
"Will Clark <willclarktech@users.noreply.github.com>"
],
"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/cosmjs/tree/master/packages/cosmwasm-stargate"
},
"publishConfig": {
"access": "public"
},
"private": true,
"scripts": {
"docs": "typedoc --options typedoc.js",
"format": "prettier --write --loglevel warn \"./src/**/*.ts\"",
"format-text": "prettier --write --prose-wrap always --print-width 80 \"./*.md\"",
"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 ./types/*.spec.d.ts ./types/*/*.spec.d.ts",
"format-types": "prettier --write --loglevel warn \"./types/**/*.d.ts\"",
"prebuild": "shx rm -rf ./build",
"build": "tsc && shx mkdir -p build/codec/generated && shx cp ./src/codec/generated/*.js ./build/codec/generated",
"postbuild": "shx mkdir -p ./build/types/codec/generated && shx cp ./src/codec/generated/*.d.ts ./build/types/codec/generated && 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 ChromeHeadless",
"test": "yarn build-or-skip && yarn test-node",
"coverage": "nyc --reporter=text --reporter=lcov yarn test --quiet",
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js",
"preget-proto": "shx rm -rf proto",
"get-proto": "WASM_REF=v0.13.0 COSMOS_REF=v0.40.0-rc3 ./scripts/get-proto.sh",
"predefine-proto": "./scripts/predefine-proto.sh",
"define-proto": "./scripts/define-proto.sh",
"postdefine-proto": "prettier --write \"src/codec/generated/codecimpl.*\""
},
"dependencies": {
"@cosmjs/cosmwasm": "^0.24.0-alpha.10",
"@cosmjs/crypto": "^0.24.0-alpha.10",
"@cosmjs/encoding": "^0.24.0-alpha.10",
"@cosmjs/launchpad": "^0.24.0-alpha.10",
"@cosmjs/math": "^0.24.0-alpha.10",
"@cosmjs/proto-signing": "^0.24.0-alpha.10",
"@cosmjs/stargate": "^0.24.0-alpha.10",
"@cosmjs/tendermint-rpc": "^0.24.0-alpha.10",
"@cosmjs/utils": "^0.24.0-alpha.10",
"long": "^4.0.0",
"pako": "^2.0.2",
"protobufjs": "~6.10.2"
},
"devDependencies": {
"@types/pako": "^1.0.1",
"readonly-date": "^1.0.0"
}
}

View File

@ -0,0 +1,16 @@
#!/bin/bash
set -o errexit -o nounset -o pipefail
command -v shellcheck >/dev/null && shellcheck "$0"
TMP_DIR="./tmp"
JS_SOURCE_FILE="$TMP_DIR/codecimpl.js"
DEFINITIONS_FILE="$TMP_DIR/codecimpl.d.ts"
OUTPUT_DIR="./src/codec/generated/"
yarn pbts "$JS_SOURCE_FILE" -o "$DEFINITIONS_FILE"
# Remove comments after using them for the .d.ts
# Note "When input files are specified on the command line, tsconfig.json files are ignored." (https://www.typescriptlang.org/docs/handbook/tsconfig-json.html)
yarn tsc --removeComments --target es2017 --module commonjs --outDir "$OUTPUT_DIR" --allowJs "$JS_SOURCE_FILE"
cp "$DEFINITIONS_FILE" "$OUTPUT_DIR"
rm "$DEFINITIONS_FILE" "$JS_SOURCE_FILE"

View File

@ -0,0 +1,31 @@
#!/bin/bash
set -o errexit -o nounset -o pipefail
command -v shellcheck >/dev/null && shellcheck "$0"
PROTO_DIR="./proto"
COSMOS_DIR="$PROTO_DIR/cosmos"
COSMOS_SDK_DIR="$COSMOS_DIR/cosmos-sdk"
COSMOS_SDK_ZIP_FILE="$COSMOS_DIR/tmp.zip"
COSMOS_REF=${COSMOS_REF:-"master"}
COSMOS_SUFFIX=${COSMOS_REF}
[[ $COSMOS_SUFFIX =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-.+)?$ ]] && COSMOS_SUFFIX=${COSMOS_SUFFIX#v}
COSMWASM_DIR="$PROTO_DIR/cosmwasm"
WASMD_DIR="$COSMWASM_DIR/wasmd"
WASMD_ZIP_FILE="$COSMWASM_DIR/tmp.zip"
WASM_REF=${WASM_REF:-"master"}
WASM_SUFFIX=${WASM_REF}
[[ $WASM_SUFFIX =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-.+)?$ ]] && WASM_SUFFIX=${WASM_SUFFIX#v}
mkdir -p "$COSMOS_DIR"
wget -qO "$COSMOS_SDK_ZIP_FILE" "https://github.com/cosmos/cosmos-sdk/archive/$COSMOS_REF.zip"
unzip "$COSMOS_SDK_ZIP_FILE" "*.proto" -d "$COSMOS_DIR"
mv "$COSMOS_SDK_DIR-$COSMOS_SUFFIX" "$COSMOS_SDK_DIR"
rm "$COSMOS_SDK_ZIP_FILE"
mkdir -p "$COSMWASM_DIR"
wget -qO "$WASMD_ZIP_FILE" "https://github.com/cosmwasm/wasmd/archive/$WASM_REF.zip"
unzip "$WASMD_ZIP_FILE" "*.proto" -d "$COSMWASM_DIR"
mv "$WASMD_DIR-$WASM_SUFFIX" "$WASMD_DIR"
rm "$WASMD_ZIP_FILE"

View File

@ -0,0 +1,30 @@
#!/bin/bash
set -o errexit -o nounset -o pipefail
command -v shellcheck >/dev/null && shellcheck "$0"
GENERATED_DIR="./tmp"
ROOT_PROTO_DIR="./proto"
WASM_PROTO_DIR="$ROOT_PROTO_DIR/cosmwasm/wasmd/x/wasm"
COSMOS_PROTO_DIR="$ROOT_PROTO_DIR/cosmos/cosmos-sdk/proto/cosmos"
mkdir -p "$GENERATED_DIR"
# Can't use --sparse for some reason. Seems related to https://github.com/protobufjs/protobuf.js/issues/1165
yarn pbjs \
-t static-module \
--es6 \
-w commonjs \
-o "$GENERATED_DIR/codecimpl.js" \
--no-beautify \
--no-delimited \
--no-verify \
--no-convert \
--force-long \
"$WASM_PROTO_DIR/internal/types/msg.proto" \
"$WASM_PROTO_DIR/internal/types/query.proto" \
"$WASM_PROTO_DIR/internal/types/types.proto" \
"$COSMOS_PROTO_DIR/base/v1beta1/coin.proto" \
"$COSMOS_PROTO_DIR/base/query/v1beta1/pagination.proto"
# Work around https://github.com/protobufjs/protobuf.js/issues/1477
# shellcheck disable=SC2016
sed -i "" -e 's/^const \$root =.*$/const \$root = {};/' "$GENERATED_DIR/codecimpl.js"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
import Long from "long";
import protobuf from "protobufjs/minimal";
// Ensure the protobuf module has a Long implementation, which otherwise only works
// in Node.js (see https://github.com/protobufjs/protobuf.js/issues/921#issuecomment-334925145)
protobuf.util.Long = Long;
protobuf.configure();
export { cosmwasm } from "./generated/codecimpl";

View File

@ -0,0 +1,437 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Code } from "@cosmjs/cosmwasm";
import { sha256 } from "@cosmjs/crypto";
import { Bech32, fromAscii, fromBase64, fromHex, toAscii, toBase64 } from "@cosmjs/encoding";
import { coins, logs, StdFee } from "@cosmjs/launchpad";
import { Int53 } from "@cosmjs/math";
import {
DirectSecp256k1HdWallet,
encodePubkey,
makeAuthInfoBytes,
makeSignDoc,
Registry,
} from "@cosmjs/proto-signing";
import { assertIsBroadcastTxSuccess, codec, parseRawLog } from "@cosmjs/stargate";
import { assert, sleep } from "@cosmjs/utils";
import { ReadonlyDate } from "readonly-date";
import { CosmWasmClient, PrivateCosmWasmClient } from "./cosmwasmclient";
import { SigningCosmWasmClient } from "./signingcosmwasmclient";
import {
alice,
deployedHackatom,
getHackatom,
makeRandomAddress,
pendingWithoutWasmd,
tendermintIdMatcher,
unused,
wasmd,
wasmdEnabled,
} from "./testutils.spec";
const { TxRaw } = codec.cosmos.tx.v1beta1;
interface HackatomInstance {
readonly initMsg: {
readonly verifier: string;
readonly beneficiary: string;
};
readonly address: string;
}
describe("CosmWasmClient", () => {
describe("connect", () => {
it("can be constructed", async () => {
const client = await CosmWasmClient.connect(wasmd.endpoint);
expect(client).toBeTruthy();
});
});
describe("getChainId", () => {
it("works", async () => {
pendingWithoutWasmd();
const client = await CosmWasmClient.connect(wasmd.endpoint);
expect(await client.getChainId()).toEqual(wasmd.chainId);
});
it("caches chain ID", async () => {
pendingWithoutWasmd();
const client = await CosmWasmClient.connect(wasmd.endpoint);
const openedClient = (client as unknown) as PrivateCosmWasmClient;
const getCodeSpy = spyOn(openedClient.tmClient, "status").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("works", async () => {
pendingWithoutWasmd();
const client = await CosmWasmClient.connect(wasmd.endpoint);
const height1 = await client.getHeight();
expect(height1).toBeGreaterThan(0);
await sleep(wasmd.blockTime * 1.4); // tolerate chain being 40% slower than expected
const height2 = await client.getHeight();
expect(height2).toBeGreaterThanOrEqual(height1 + 1);
expect(height2).toBeLessThanOrEqual(height1 + 2);
});
});
describe("getSequence", () => {
it("works", async () => {
pendingWithoutWasmd();
const client = await CosmWasmClient.connect(wasmd.endpoint);
expect(await client.getSequence(unused.address)).toEqual({
accountNumber: unused.accountNumber,
sequence: unused.sequence,
});
});
it("returns null for missing accounts", async () => {
pendingWithoutWasmd();
const client = await CosmWasmClient.connect(wasmd.endpoint);
const missing = makeRandomAddress();
expect(await client.getSequence(missing)).toBeNull();
});
});
describe("getAccount", () => {
it("works", async () => {
pendingWithoutWasmd();
const client = await CosmWasmClient.connect(wasmd.endpoint);
expect(await client.getAccount(unused.address)).toEqual({
address: unused.address,
accountNumber: unused.accountNumber,
sequence: unused.sequence,
pubkey: null,
});
});
it("returns null for missing accounts", async () => {
pendingWithoutWasmd();
const client = await CosmWasmClient.connect(wasmd.endpoint);
const missing = makeRandomAddress();
expect(await client.getAccount(missing)).toBeNull();
});
});
describe("getBlock", () => {
it("works for latest block", async () => {
pendingWithoutWasmd();
const client = await CosmWasmClient.connect(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 = await CosmWasmClient.connect(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("broadcastTx", () => {
it("works", async () => {
pendingWithoutWasmd();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const client = await CosmWasmClient.connect(wasmd.endpoint);
const registry = new Registry();
const memo = "My first contract on chain";
const sendMsg = {
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
value: {
fromAddress: alice.address0,
toAddress: makeRandomAddress(),
amount: coins(1234567, "ucosm"),
},
};
const fee: StdFee = {
amount: coins(5000, "ucosm"),
gas: "890000",
};
const chainId = await client.getChainId();
const sequenceResponse = await client.getSequence(alice.address0);
assert(sequenceResponse);
const { accountNumber, sequence } = sequenceResponse;
const pubkeyAny = encodePubkey(alice.pubkey0);
const txBody = {
messages: [sendMsg],
memo: memo,
};
const txBodyBytes = registry.encode({
typeUrl: "/cosmos.tx.v1beta1.TxBody",
value: txBody,
});
const gasLimit = Int53.fromString(fee.gas).toNumber();
const authInfoBytes = makeAuthInfoBytes([pubkeyAny], fee.amount, gasLimit, sequence);
const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber);
const { signed, signature } = await wallet.signDirect(alice.address0, signDoc);
const txRaw = TxRaw.create({
bodyBytes: signed.bodyBytes,
authInfoBytes: signed.authInfoBytes,
signatures: [fromBase64(signature.signature)],
});
const signedTx = Uint8Array.from(TxRaw.encode(txRaw).finish());
const result = await client.broadcastTx(signedTx);
assertIsBroadcastTxSuccess(result);
const amountAttr = logs.findAttribute(parseRawLog(result.rawLog), "transfer", "amount");
expect(amountAttr.value).toEqual("1234567ucosm");
expect(result.transactionHash).toMatch(/^[0-9A-F]{64}$/);
});
});
describe("getCodes", () => {
it("works", async () => {
pendingWithoutWasmd();
const client = await CosmWasmClient.connect(wasmd.endpoint);
const result = await client.getCodes();
expect(result.length).toBeGreaterThanOrEqual(1);
const [first] = result;
expect(first).toEqual({
id: deployedHackatom.codeId,
source: deployedHackatom.source,
builder: deployedHackatom.builder,
checksum: deployedHackatom.checksum,
creator: alice.address0,
});
});
});
describe("getCodeDetails", () => {
it("works", async () => {
pendingWithoutWasmd();
const client = await CosmWasmClient.connect(wasmd.endpoint);
const result = await client.getCodeDetails(1);
const expectedInfo: Code = {
id: deployedHackatom.codeId,
source: deployedHackatom.source,
builder: deployedHackatom.builder,
checksum: deployedHackatom.checksum,
creator: alice.address0,
};
// check info
expect(result).toEqual(jasmine.objectContaining(expectedInfo));
// check data
expect(sha256(result.data)).toEqual(fromHex(expectedInfo.checksum));
});
it("caches downloads", async () => {
pendingWithoutWasmd();
const client = await CosmWasmClient.connect(wasmd.endpoint);
const openedClient = (client as unknown) as PrivateCosmWasmClient;
const getCodeSpy = spyOn(openedClient.queryClient.unverified.wasm, "getCode").and.callThrough();
const result1 = await client.getCodeDetails(deployedHackatom.codeId); // from network
const result2 = await client.getCodeDetails(deployedHackatom.codeId); // from cache
expect(result2).toEqual(result1);
expect(getCodeSpy).toHaveBeenCalledTimes(1);
});
});
describe("getContracts", () => {
it("works", async () => {
pendingWithoutWasmd();
const client = await CosmWasmClient.connect(wasmd.endpoint);
const result = await client.getContracts(1);
expect(result.length).toBeGreaterThanOrEqual(3);
const [zero, one, two] = result;
expect(zero).toEqual({
address: deployedHackatom.instances[0].address,
codeId: deployedHackatom.codeId,
creator: alice.address0,
admin: undefined,
label: deployedHackatom.instances[0].label,
});
expect(one).toEqual({
address: deployedHackatom.instances[1].address,
codeId: deployedHackatom.codeId,
creator: alice.address0,
admin: undefined,
label: deployedHackatom.instances[1].label,
});
expect(two).toEqual({
address: deployedHackatom.instances[2].address,
codeId: deployedHackatom.codeId,
creator: alice.address0,
admin: alice.address1,
label: deployedHackatom.instances[2].label,
});
});
});
describe("getContract", () => {
it("works for instance without admin", async () => {
pendingWithoutWasmd();
const client = await CosmWasmClient.connect(wasmd.endpoint);
const zero = await client.getContract(deployedHackatom.instances[0].address);
expect(zero).toEqual({
address: deployedHackatom.instances[0].address,
codeId: deployedHackatom.codeId,
creator: alice.address0,
label: deployedHackatom.instances[0].label,
admin: undefined,
});
});
it("works for instance with admin", async () => {
pendingWithoutWasmd();
const client = await CosmWasmClient.connect(wasmd.endpoint);
const two = await client.getContract(deployedHackatom.instances[2].address);
expect(two).toEqual(
jasmine.objectContaining({
address: deployedHackatom.instances[2].address,
codeId: deployedHackatom.codeId,
creator: alice.address0,
label: deployedHackatom.instances[2].label,
admin: alice.address1,
}),
);
});
});
describe("queryContractRaw", () => {
const configKey = toAscii("config");
const otherKey = toAscii("this_does_not_exist");
let contract: HackatomInstance | undefined;
beforeAll(async () => {
if (wasmdEnabled()) {
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
const { codeId } = await client.upload(alice.address0, getHackatom().data);
const initMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() };
const { contractAddress } = await client.instantiate(
alice.address0,
codeId,
initMsg,
"random hackatom",
);
contract = { initMsg: initMsg, address: contractAddress };
}
});
it("can query existing key", async () => {
pendingWithoutWasmd();
assert(contract);
const client = await CosmWasmClient.connect(wasmd.endpoint);
const raw = await client.queryContractRaw(contract.address, configKey);
assert(raw, "must get result");
expect(JSON.parse(fromAscii(raw))).toEqual({
verifier: toBase64(Bech32.decode(contract.initMsg.verifier).data),
beneficiary: toBase64(Bech32.decode(contract.initMsg.beneficiary).data),
funder: toBase64(Bech32.decode(alice.address0).data),
});
});
it("can query non-existent key", async () => {
pendingWithoutWasmd();
assert(contract);
const client = await CosmWasmClient.connect(wasmd.endpoint);
const raw = await client.queryContractRaw(contract.address, otherKey);
assert(raw, "must get result");
expect(Uint8Array.from(raw)).toEqual(new Uint8Array());
});
it("errors for non-existent contract", async () => {
pendingWithoutWasmd();
assert(contract);
const nonExistentAddress = makeRandomAddress();
const client = await CosmWasmClient.connect(wasmd.endpoint);
await expectAsync(client.queryContractRaw(nonExistentAddress, configKey)).toBeRejectedWithError(
/not found/i,
);
});
});
describe("queryContractSmart", () => {
let contract: HackatomInstance | undefined;
beforeAll(async () => {
if (wasmdEnabled()) {
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
const { codeId } = await client.upload(alice.address0, getHackatom().data);
const initMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() };
const { contractAddress } = await client.instantiate(
alice.address0,
codeId,
initMsg,
"a different hackatom",
);
contract = { initMsg: initMsg, address: contractAddress };
}
});
it("works", async () => {
pendingWithoutWasmd();
assert(contract);
const client = await CosmWasmClient.connect(wasmd.endpoint);
const { data } = await client.queryContractSmart(contract.address, { verifier: {} });
const parsedData = JSON.parse(fromAscii(data));
expect(parsedData).toEqual({ verifier: contract.initMsg.verifier });
});
it("errors for malformed query message", async () => {
pendingWithoutWasmd();
assert(contract);
const client = await CosmWasmClient.connect(wasmd.endpoint);
await expectAsync(client.queryContractSmart(contract.address, { broken: {} })).toBeRejectedWithError(
/Error parsing into type hackatom::contract::QueryMsg: unknown variant/i,
);
});
it("errors for non-existent contract", async () => {
pendingWithoutWasmd();
const nonExistentAddress = makeRandomAddress();
const client = await CosmWasmClient.connect(wasmd.endpoint);
await expectAsync(
client.queryContractSmart(nonExistentAddress, { verifier: {} }),
).toBeRejectedWithError(/not found/i);
});
});
});

View File

@ -0,0 +1,343 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Code, CodeDetails, Contract, ContractCodeHistoryEntry, JsonObject } from "@cosmjs/cosmwasm";
import { fromAscii, toHex } from "@cosmjs/encoding";
import {
Block,
Coin,
isSearchByHeightQuery,
isSearchByIdQuery,
isSearchBySentFromOrToQuery,
isSearchByTagsQuery,
SearchTxFilter,
SearchTxQuery,
} from "@cosmjs/launchpad";
import { Uint53 } from "@cosmjs/math";
import {
Account,
accountFromProto,
AuthExtension,
BankExtension,
BroadcastTxResponse,
codec,
coinFromProto,
IndexedTx,
QueryClient,
SequenceResponse,
setupAuthExtension,
setupBankExtension,
} from "@cosmjs/stargate";
import {
adaptor34,
broadcastTxCommitSuccess,
Client as TendermintClient,
DateTime,
QueryString,
} from "@cosmjs/tendermint-rpc";
import { assert } from "@cosmjs/utils";
import { cosmwasm } from "./codec";
import { setupWasmExtension, WasmExtension } from "./queries";
type ICodeInfoResponse = cosmwasm.wasm.v1beta1.ICodeInfoResponse;
type ContractCodeHistoryOperationType = cosmwasm.wasm.v1beta1.ContractCodeHistoryOperationType;
const { TxMsgData } = codec.cosmos.base.abci.v1beta1;
const { ContractCodeHistoryOperationType } = cosmwasm.wasm.v1beta1;
/** Use for testing only */
export interface PrivateCosmWasmClient {
readonly tmClient: TendermintClient;
readonly queryClient: QueryClient & AuthExtension & BankExtension & WasmExtension;
}
export class CosmWasmClient {
private readonly tmClient: TendermintClient;
private readonly queryClient: QueryClient & AuthExtension & BankExtension & WasmExtension;
private readonly codesCache = new Map<number, CodeDetails>();
private chainId: string | undefined;
public static async connect(endpoint: string): Promise<CosmWasmClient> {
const tmClient = await TendermintClient.connect(endpoint, adaptor34);
return new CosmWasmClient(tmClient);
}
protected constructor(tmClient: TendermintClient) {
this.tmClient = tmClient;
this.queryClient = QueryClient.withExtensions(
tmClient,
setupAuthExtension,
setupBankExtension,
setupWasmExtension,
);
}
public async getChainId(): Promise<string> {
if (!this.chainId) {
const response = await this.tmClient.status();
const chainId = response.nodeInfo.network;
if (!chainId) throw new Error("Chain ID must not be empty");
this.chainId = chainId;
}
return this.chainId;
}
public async getHeight(): Promise<number> {
const status = await this.tmClient.status();
return status.syncInfo.latestBlockHeight;
}
public async getAccount(searchAddress: string): Promise<Account | null> {
const account = await this.queryClient.auth.account(searchAddress);
return account ? accountFromProto(account) : null;
}
public async getSequence(address: string): Promise<SequenceResponse | null> {
const account = await this.getAccount(address);
if (account) {
return {
accountNumber: account.accountNumber,
sequence: account.sequence,
};
} else {
return null;
}
}
public async getBlock(height?: number): Promise<Block> {
const response = await this.tmClient.block(height);
return {
id: toHex(response.blockId.hash).toUpperCase(),
header: {
version: {
block: new Uint53(response.block.header.version.block).toString(),
app: new Uint53(response.block.header.version.app).toString(),
},
height: response.block.header.height,
chainId: response.block.header.chainId,
time: DateTime.encode(response.block.header.time),
},
txs: response.block.txs,
};
}
public async getBalance(address: string, searchDenom: string): Promise<Coin | null> {
const balance = await this.queryClient.bank.balance(address, searchDenom);
return balance ? coinFromProto(balance) : null;
}
public async searchTx(query: SearchTxQuery, filter: SearchTxFilter = {}): Promise<readonly IndexedTx[]> {
const minHeight = filter.minHeight || 0;
const maxHeight = filter.maxHeight || Number.MAX_SAFE_INTEGER;
if (maxHeight < minHeight) return []; // optional optimization
let txs: readonly IndexedTx[];
if (isSearchByIdQuery(query)) {
txs = await this.txsQuery(`tx.hash='${query.id}'`);
} else if (isSearchByHeightQuery(query)) {
txs =
query.height >= minHeight && query.height <= maxHeight
? await this.txsQuery(`tx.height=${query.height}`)
: [];
} else if (isSearchBySentFromOrToQuery(query)) {
throw new Error(
"This type of search query is not yet implemented. See https://github.com/cosmos/cosmjs/issues/533.",
);
} else if (isSearchByTagsQuery(query)) {
throw new Error(
"This type of search query is not yet implemented. See https://github.com/cosmos/cosmjs/issues/532.",
);
} else {
throw new Error("Unknown query type");
}
const filtered = txs.filter((tx) => tx.height >= minHeight && tx.height <= maxHeight);
return filtered;
}
public disconnect(): void {
this.tmClient.disconnect();
}
public async broadcastTx(tx: Uint8Array): Promise<BroadcastTxResponse> {
const response = await this.tmClient.broadcastTxCommit({ tx });
if (broadcastTxCommitSuccess(response)) {
return {
height: response.height,
transactionHash: toHex(response.hash).toUpperCase(),
rawLog: response.deliverTx?.log,
data: response.deliverTx?.data ? TxMsgData.decode(response.deliverTx?.data).data : undefined,
};
}
return response.checkTx.code !== 0
? {
height: response.height,
code: response.checkTx.code,
transactionHash: toHex(response.hash).toUpperCase(),
rawLog: response.checkTx.log,
data: response.checkTx.data ? TxMsgData.decode(response.checkTx.data).data : undefined,
}
: {
height: response.height,
code: response.deliverTx?.code,
transactionHash: toHex(response.hash).toUpperCase(),
rawLog: response.deliverTx?.log,
data: response.deliverTx?.data ? TxMsgData.decode(response.deliverTx?.data).data : undefined,
};
}
public async getCodes(): Promise<readonly Code[]> {
const { codeInfos } = await this.queryClient.unverified.wasm.listCodeInfo();
return (codeInfos || []).map(
(entry: ICodeInfoResponse): Code => {
assert(entry.creator && entry.codeId && entry.dataHash, "entry incomplete");
return {
id: entry.codeId.toNumber(),
creator: entry.creator,
checksum: toHex(entry.dataHash),
source: entry.source || undefined,
builder: entry.builder || undefined,
};
},
);
}
public async getCodeDetails(codeId: number): Promise<CodeDetails> {
const cached = this.codesCache.get(codeId);
if (cached) return cached;
const { codeInfo, data } = await this.queryClient.unverified.wasm.getCode(codeId);
assert(
codeInfo && codeInfo.codeId && codeInfo.creator && codeInfo.dataHash && data,
"codeInfo missing or incomplete",
);
const codeDetails: CodeDetails = {
id: codeInfo.codeId.toNumber(),
creator: codeInfo.creator,
checksum: toHex(codeInfo.dataHash),
source: codeInfo.source || undefined,
builder: codeInfo.builder || undefined,
data: data,
};
this.codesCache.set(codeId, codeDetails);
return codeDetails;
}
public async getContracts(codeId: number): Promise<readonly Contract[]> {
const { contractInfos } = await this.queryClient.unverified.wasm.listContractsByCodeId(codeId);
return (contractInfos || []).map(
({ address, contractInfo }): Contract => {
assert(address, "address missing");
assert(
contractInfo && contractInfo.codeId && contractInfo.creator && contractInfo.label,
"contractInfo missing or incomplete",
);
return {
address: address,
codeId: contractInfo.codeId.toNumber(),
creator: contractInfo.creator,
admin: contractInfo.admin || undefined,
label: contractInfo.label,
};
},
);
}
/**
* Throws an error if no contract was found at the address
*/
public async getContract(address: string): Promise<Contract> {
const {
address: retrievedAddress,
contractInfo,
} = await this.queryClient.unverified.wasm.getContractInfo(address);
if (!contractInfo) throw new Error(`No contract found at address "${address}"`);
assert(retrievedAddress, "address missing");
assert(contractInfo.codeId && contractInfo.creator && contractInfo.label, "contractInfo incomplete");
return {
address: retrievedAddress,
codeId: contractInfo.codeId.toNumber(),
creator: contractInfo.creator,
admin: contractInfo.admin || undefined,
label: contractInfo.label,
};
}
/**
* Throws an error if no contract was found at the address
*/
public async getContractCodeHistory(address: string): Promise<readonly ContractCodeHistoryEntry[]> {
const result = await this.queryClient.unverified.wasm.getContractCodeHistory(address);
if (!result) throw new Error(`No contract history found for address "${address}"`);
const operations: Record<number, "Init" | "Genesis" | "Migrate"> = {
[ContractCodeHistoryOperationType.CONTRACT_CODE_HISTORY_OPERATION_TYPE_INIT]: "Init",
[ContractCodeHistoryOperationType.CONTRACT_CODE_HISTORY_OPERATION_TYPE_GENESIS]: "Genesis",
[ContractCodeHistoryOperationType.CONTRACT_CODE_HISTORY_OPERATION_TYPE_MIGRATE]: "Migrate",
};
return (result.entries || []).map(
(entry): ContractCodeHistoryEntry => {
assert(entry.operation && entry.codeId && entry.msg);
return {
operation: operations[entry.operation],
codeId: entry.codeId.toNumber(),
msg: JSON.parse(fromAscii(entry.msg)),
};
},
);
}
/**
* Returns the data at the key if present (raw contract dependent storage data)
* or null if no data at this key.
*
* Promise is rejected when contract does not exist.
*/
public async queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null> {
// just test contract existence
await this.getContract(address);
const { data } = await this.queryClient.unverified.wasm.queryContractRaw(address, key);
return data ?? null;
}
/**
* Makes a smart query on the contract, returns the parsed JSON document.
*
* Promise is rejected when contract does not exist.
* Promise is rejected for invalid query format.
* Promise is rejected for invalid response format.
*/
public async queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject> {
try {
return await this.queryClient.unverified.wasm.queryContractSmart(address, queryMsg);
} catch (error) {
if (error instanceof Error) {
if (error.message.startsWith("not found: contract")) {
throw new Error(`No contract found at address "${address}"`);
} else {
throw error;
}
} else {
throw error;
}
}
}
private async txsQuery(query: string): Promise<readonly IndexedTx[]> {
const params = {
query: query as QueryString,
};
const results = await this.tmClient.txSearchAll(params);
return results.txs.map((tx) => {
return {
height: tx.height,
hash: toHex(tx.hash).toUpperCase(),
code: tx.result.code,
rawLog: tx.result.log || "",
tx: tx.tx,
};
});
}
}

View File

@ -0,0 +1,3 @@
export * as codec from "./codec";
export { CosmWasmClient } from "./cosmwasmclient";
export { SigningCosmWasmClient, SigningCosmWasmClientOptions } from "./signingcosmwasmclient";

View File

@ -0,0 +1 @@
export { setupWasmExtension, WasmExtension } from "./wasm";

View File

@ -0,0 +1,447 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { sha256 } from "@cosmjs/crypto";
import { fromAscii, fromHex, toAscii, toHex } from "@cosmjs/encoding";
import { Coin, coin, coins, logs, StdFee } from "@cosmjs/launchpad";
import { DirectSecp256k1HdWallet, OfflineDirectSigner, Registry } from "@cosmjs/proto-signing";
import {
assertIsBroadcastTxSuccess,
BroadcastTxResponse,
parseRawLog,
SigningStargateClient,
} from "@cosmjs/stargate";
import { assert } from "@cosmjs/utils";
import Long from "long";
import { cosmwasm } from "../codec";
import { SigningCosmWasmClient } from "../signingcosmwasmclient";
import {
alice,
base64Matcher,
bech32AddressMatcher,
ContractUploadInstructions,
getHackatom,
makeRandomAddress,
makeWasmClient,
pendingWithoutWasmd,
wasmd,
wasmdEnabled,
} from "../testutils.spec";
const { MsgExecuteContract, MsgInstantiateContract, MsgStoreCode } = cosmwasm.wasm.v1beta1;
const registry = new Registry([
["/cosmwasm.wasm.v1beta1.MsgExecuteContract", MsgExecuteContract],
["/cosmwasm.wasm.v1beta1.MsgStoreCode", MsgStoreCode],
["/cosmwasm.wasm.v1beta1.MsgInstantiateContract", MsgInstantiateContract],
]);
async function uploadContract(
signer: OfflineDirectSigner,
contract: ContractUploadInstructions,
): Promise<BroadcastTxResponse> {
const memo = "My first contract on chain";
const theMsg = {
typeUrl: "/cosmwasm.wasm.v1beta1.MsgStoreCode",
value: MsgStoreCode.create({
sender: alice.address0,
wasmByteCode: contract.data,
source: contract.source || "",
builder: contract.builder || "",
}),
};
const fee: StdFee = {
amount: coins(5000000, "ucosm"),
gas: "89000000",
};
const firstAddress = (await signer.getAccounts())[0].address;
const client = await SigningStargateClient.connectWithWallet(wasmd.endpoint, signer, { registry });
return client.signAndBroadcast(firstAddress, [theMsg], fee, memo);
}
async function instantiateContract(
signer: OfflineDirectSigner,
codeId: number,
beneficiaryAddress: string,
transferAmount?: readonly Coin[],
): Promise<BroadcastTxResponse> {
const memo = "Create an escrow instance";
const theMsg = {
typeUrl: "/cosmwasm.wasm.v1beta1.MsgInstantiateContract",
value: MsgInstantiateContract.create({
sender: alice.address0,
codeId: Long.fromNumber(codeId),
label: "my escrow",
initMsg: toAscii(
JSON.stringify({
verifier: alice.address0,
beneficiary: beneficiaryAddress,
}),
),
initFunds: transferAmount ? [...transferAmount] : [],
}),
};
const fee: StdFee = {
amount: coins(5000000, "ucosm"),
gas: "89000000",
};
const firstAddress = (await signer.getAccounts())[0].address;
const client = await SigningStargateClient.connectWithWallet(wasmd.endpoint, signer, { registry });
return client.signAndBroadcast(firstAddress, [theMsg], fee, memo);
}
async function executeContract(
signer: OfflineDirectSigner,
contractAddress: string,
msg: Record<string, unknown>,
): Promise<BroadcastTxResponse> {
const memo = "Time for action";
const theMsg = {
typeUrl: "/cosmwasm.wasm.v1beta1.MsgExecuteContract",
value: MsgExecuteContract.create({
sender: alice.address0,
contract: contractAddress,
msg: toAscii(JSON.stringify(msg)),
sentFunds: [],
}),
};
const fee: StdFee = {
amount: coins(5000000, "ucosm"),
gas: "89000000",
};
const firstAddress = (await signer.getAccounts())[0].address;
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, signer, { registry });
return client.signAndBroadcast(firstAddress, [theMsg], fee, memo);
}
describe("WasmExtension", () => {
const hackatom = getHackatom();
const hackatomConfigKey = toAscii("config");
let hackatomCodeId: number | undefined;
let hackatomContractAddress: string | undefined;
beforeAll(async () => {
if (wasmdEnabled()) {
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, "wasm");
const result = await uploadContract(wallet, hackatom);
assertIsBroadcastTxSuccess(result);
hackatomCodeId = Number.parseInt(
JSON.parse(result.rawLog!)[0].events[0].attributes.find(
(attribute: any) => attribute.key === "code_id",
).value,
10,
);
const instantiateResult = await instantiateContract(wallet, hackatomCodeId, makeRandomAddress());
assertIsBroadcastTxSuccess(instantiateResult);
hackatomContractAddress = JSON.parse(instantiateResult.rawLog!)[0]
.events.find((event: any) => event.type === "message")
.attributes.find((attribute: any) => attribute.key === "contract_address").value;
}
});
describe("listCodeInfo", () => {
it("has recently uploaded contract as last entry", async () => {
pendingWithoutWasmd();
assert(hackatomCodeId);
const client = await makeWasmClient(wasmd.endpoint);
const { codeInfos } = await client.unverified.wasm.listCodeInfo();
assert(codeInfos);
const lastCode = codeInfos[codeInfos.length - 1];
expect(lastCode.codeId!.toNumber()).toEqual(hackatomCodeId);
expect(lastCode.creator).toEqual(alice.address0);
expect(lastCode.source).toEqual(hackatom.source);
expect(lastCode.builder).toEqual(hackatom.builder);
expect(toHex(lastCode.dataHash!)).toEqual(toHex(sha256(hackatom.data)));
});
});
describe("getCode", () => {
it("contains fill code information", async () => {
pendingWithoutWasmd();
assert(hackatomCodeId);
const client = await makeWasmClient(wasmd.endpoint);
const { codeInfo, data } = await client.unverified.wasm.getCode(hackatomCodeId);
expect(codeInfo!.codeId!.toNumber()).toEqual(hackatomCodeId);
expect(codeInfo!.creator).toEqual(alice.address0);
expect(codeInfo!.source).toEqual(hackatom.source);
expect(codeInfo!.builder).toEqual(hackatom.builder);
expect(toHex(codeInfo!.dataHash!)).toEqual(toHex(sha256(hackatom.data)));
expect(data).toEqual(hackatom.data);
});
});
// TODO: move listContractsByCodeId tests out of here
describe("getContractInfo", () => {
it("works", async () => {
pendingWithoutWasmd();
assert(hackatomCodeId);
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, "wasm");
const client = await makeWasmClient(wasmd.endpoint);
const beneficiaryAddress = makeRandomAddress();
const transferAmount = coins(707707, "ucosm");
// create new instance and compare before and after
const { contractInfos: existingContractInfos } = await client.unverified.wasm.listContractsByCodeId(
hackatomCodeId,
);
assert(existingContractInfos);
for (const { address, contractInfo } of existingContractInfos) {
expect(address).toMatch(bech32AddressMatcher);
expect(contractInfo!.codeId!.toNumber()).toEqual(hackatomCodeId);
expect(contractInfo!.creator).toMatch(bech32AddressMatcher);
expect(contractInfo!.label).toMatch(/^.+$/);
}
const result = await instantiateContract(wallet, hackatomCodeId, beneficiaryAddress, transferAmount);
assertIsBroadcastTxSuccess(result);
const myAddress = JSON.parse(result.rawLog!)[0]
.events.find((event: any) => event.type === "message")
.attributes!.find((attribute: any) => attribute.key === "contract_address").value;
const { contractInfos: newContractInfos } = await client.unverified.wasm.listContractsByCodeId(
hackatomCodeId,
);
assert(newContractInfos);
expect(newContractInfos.length).toEqual(existingContractInfos.length + 1);
const newContract = newContractInfos[newContractInfos.length - 1];
expect({ ...newContract.contractInfo }).toEqual({
codeId: Long.fromNumber(hackatomCodeId, true),
creator: alice.address0,
label: "my escrow",
});
const { contractInfo } = await client.unverified.wasm.getContractInfo(myAddress);
assert(contractInfo);
expect({ ...contractInfo }).toEqual({
codeId: Long.fromNumber(hackatomCodeId, true),
creator: alice.address0,
label: "my escrow",
});
expect(contractInfo.admin).toEqual("");
});
it("rejects for non-existent address", async () => {
pendingWithoutWasmd();
assert(hackatomCodeId);
const client = await makeWasmClient(wasmd.endpoint);
const nonExistentAddress = makeRandomAddress();
await expectAsync(client.unverified.wasm.getContractInfo(nonExistentAddress)).toBeRejectedWithError(
/not found/i,
);
});
});
describe("getContractCodeHistory", () => {
it("can list contract history", async () => {
pendingWithoutWasmd();
assert(hackatomCodeId);
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, "wasm");
const client = await makeWasmClient(wasmd.endpoint);
const beneficiaryAddress = makeRandomAddress();
const transferAmount = coins(707707, "ucosm");
// create new instance and compare before and after
const result = await instantiateContract(wallet, hackatomCodeId, beneficiaryAddress, transferAmount);
assertIsBroadcastTxSuccess(result);
const myAddress = JSON.parse(result.rawLog!)[0]
.events.find((event: any) => event.type === "message")
.attributes!.find((attribute: any) => attribute.key === "contract_address").value;
const history = await client.unverified.wasm.getContractCodeHistory(myAddress);
assert(history.entries);
expect(history.entries).toContain(
jasmine.objectContaining({
codeId: Long.fromNumber(hackatomCodeId, true),
operation:
cosmwasm.wasm.v1beta1.ContractCodeHistoryOperationType.CONTRACT_CODE_HISTORY_OPERATION_TYPE_INIT,
msg: toAscii(
JSON.stringify({
verifier: alice.address0,
beneficiary: beneficiaryAddress,
}),
),
}),
);
});
it("returns empty list for non-existent address", async () => {
pendingWithoutWasmd();
assert(hackatomCodeId);
const client = await makeWasmClient(wasmd.endpoint);
const nonExistentAddress = makeRandomAddress();
const history = await client.unverified.wasm.getContractCodeHistory(nonExistentAddress);
expect(history.entries).toEqual([]);
});
});
describe("getAllContractState", () => {
it("can get all state", async () => {
pendingWithoutWasmd();
assert(hackatomContractAddress);
const client = await makeWasmClient(wasmd.endpoint);
const { models } = await client.unverified.wasm.getAllContractState(hackatomContractAddress);
assert(models);
expect(models.length).toEqual(1);
const data = models[0];
expect(data.key).toEqual(hackatomConfigKey);
const value = JSON.parse(fromAscii(data.value!));
expect(value.verifier).toMatch(base64Matcher);
expect(value.beneficiary).toMatch(base64Matcher);
});
it("rejects for non-existent address", async () => {
pendingWithoutWasmd();
const client = await makeWasmClient(wasmd.endpoint);
const nonExistentAddress = makeRandomAddress();
await expectAsync(client.unverified.wasm.getAllContractState(nonExistentAddress)).toBeRejectedWithError(
/not found/i,
);
});
});
describe("queryContractRaw", () => {
it("can query by key", async () => {
pendingWithoutWasmd();
assert(hackatomContractAddress);
const client = await makeWasmClient(wasmd.endpoint);
const raw = await client.unverified.wasm.queryContractRaw(hackatomContractAddress, hackatomConfigKey);
assert(raw.data, "must get result");
const model = JSON.parse(fromAscii(raw.data));
expect(model.verifier).toMatch(base64Matcher);
expect(model.beneficiary).toMatch(base64Matcher);
});
it("returns empty object for missing key", async () => {
pendingWithoutWasmd();
assert(hackatomContractAddress);
const client = await makeWasmClient(wasmd.endpoint);
const response = await client.unverified.wasm.queryContractRaw(
hackatomContractAddress,
fromHex("cafe0dad"),
);
expect({ ...response }).toEqual({});
});
it("returns null for non-existent address", async () => {
pendingWithoutWasmd();
const client = await makeWasmClient(wasmd.endpoint);
const nonExistentAddress = makeRandomAddress();
await expectAsync(
client.unverified.wasm.queryContractRaw(nonExistentAddress, hackatomConfigKey),
).toBeRejectedWithError(/not found/i);
});
});
describe("queryContractSmart", () => {
it("can make smart queries", async () => {
pendingWithoutWasmd();
assert(hackatomContractAddress);
const client = await makeWasmClient(wasmd.endpoint);
const request = { verifier: {} };
const { data } = await client.unverified.wasm.queryContractSmart(hackatomContractAddress, request);
assert(data);
const parsedData = JSON.parse(fromAscii(data));
expect(parsedData).toEqual({ verifier: alice.address0 });
});
it("throws for invalid query requests", async () => {
pendingWithoutWasmd();
assert(hackatomContractAddress);
const client = await makeWasmClient(wasmd.endpoint);
const request = { nosuchkey: {} };
await expectAsync(
client.unverified.wasm.queryContractSmart(hackatomContractAddress, request),
).toBeRejectedWithError(/Error parsing into type hackatom::contract::QueryMsg: unknown variant/i);
});
it("throws for non-existent address", async () => {
pendingWithoutWasmd();
const client = await makeWasmClient(wasmd.endpoint);
const nonExistentAddress = makeRandomAddress();
const request = { verifier: {} };
await expectAsync(
client.unverified.wasm.queryContractSmart(nonExistentAddress, request),
).toBeRejectedWithError(/not found/i);
});
});
describe("broadcastTx", () => {
it("can upload, instantiate and execute wasm", async () => {
pendingWithoutWasmd();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const client = await makeWasmClient(wasmd.endpoint);
const transferAmount = [coin(1234, "ucosm"), coin(321, "ustake")];
const beneficiaryAddress = makeRandomAddress();
let codeId: number;
// upload
{
const result = await uploadContract(wallet, getHackatom());
assertIsBroadcastTxSuccess(result);
const parsedLogs = logs.parseLogs(parseRawLog(result.rawLog));
const codeIdAttr = logs.findAttribute(parsedLogs, "message", "code_id");
codeId = Number.parseInt(codeIdAttr.value, 10);
expect(codeId).toBeGreaterThanOrEqual(1);
expect(codeId).toBeLessThanOrEqual(200);
expect(result.data!.length).toEqual(1);
expect({ ...result.data![0] }).toEqual({
msgType: "store-code",
data: toAscii(`${codeId}`),
});
}
let contractAddress: string;
// instantiate
{
const result = await instantiateContract(wallet, codeId, beneficiaryAddress, transferAmount);
assertIsBroadcastTxSuccess(result);
const parsedLogs = logs.parseLogs(parseRawLog(result.rawLog));
const contractAddressAttr = logs.findAttribute(parsedLogs, "message", "contract_address");
contractAddress = contractAddressAttr.value;
const amountAttr = logs.findAttribute(parsedLogs, "transfer", "amount");
expect(amountAttr.value).toEqual("1234ucosm,321ustake");
expect(result.data!.length).toEqual(1);
expect({ ...result.data![0] }).toEqual({
msgType: "instantiate",
data: toAscii(contractAddress),
});
const balanceUcosm = await client.bank.balance(contractAddress, "ucosm");
expect(balanceUcosm).toEqual(transferAmount[0]);
const balanceUstake = await client.bank.balance(contractAddress, "ustake");
expect(balanceUstake).toEqual(transferAmount[1]);
}
// execute
{
const result = await executeContract(wallet, contractAddress, { release: {} });
assertIsBroadcastTxSuccess(result);
expect(result.data!.length).toEqual(1);
expect({ ...result.data![0] }).toEqual({
msgType: "execute",
data: fromHex("F00BAA"),
});
const parsedLogs = logs.parseLogs(parseRawLog(result.rawLog));
const wasmEvent = parsedLogs.find(() => true)?.events.find((e) => e.type === "wasm");
assert(wasmEvent, "Event of type wasm expected");
expect(wasmEvent.attributes).toContain({ key: "action", value: "release" });
expect(wasmEvent.attributes).toContain({
key: "destination",
value: beneficiaryAddress,
});
// Verify token transfer from contract to beneficiary
const beneficiaryBalanceUcosm = await client.bank.balance(beneficiaryAddress, "ucosm");
expect(beneficiaryBalanceUcosm).toEqual(transferAmount[0]);
const beneficiaryBalanceUstake = await client.bank.balance(beneficiaryAddress, "ustake");
expect(beneficiaryBalanceUstake).toEqual(transferAmount[1]);
}
});
});
});

View File

@ -0,0 +1,125 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { toAscii } from "@cosmjs/encoding";
import { QueryClient } from "@cosmjs/stargate";
import Long from "long";
import { cosmwasm } from "../codec";
type IQueryAllContractStateResponse = cosmwasm.wasm.v1beta1.IQueryAllContractStateResponse;
type IQueryCodesResponse = cosmwasm.wasm.v1beta1.IQueryCodesResponse;
type IQueryCodeResponse = cosmwasm.wasm.v1beta1.IQueryCodeResponse;
type IQueryContractHistoryResponse = cosmwasm.wasm.v1beta1.IQueryContractHistoryResponse;
type IQueryContractInfoResponse = cosmwasm.wasm.v1beta1.IQueryContractInfoResponse;
type IQueryContractsByCodeResponse = cosmwasm.wasm.v1beta1.IQueryContractsByCodeResponse;
type IQueryRawContractStateResponse = cosmwasm.wasm.v1beta1.IQueryRawContractStateResponse;
type IQuerySmartContractStateResponse = cosmwasm.wasm.v1beta1.IQuerySmartContractStateResponse;
const { Query } = cosmwasm.wasm.v1beta1;
export interface WasmExtension {
readonly unverified: {
readonly wasm: {
readonly listCodeInfo: (paginationKey?: Uint8Array) => Promise<IQueryCodesResponse>;
/**
* Downloads the original wasm bytecode by code ID.
*
* Throws an error if no code with this id
*/
readonly getCode: (id: number) => Promise<IQueryCodeResponse>;
readonly listContractsByCodeId: (
id: number,
paginationKey?: Uint8Array,
) => Promise<IQueryContractsByCodeResponse>;
/**
* Returns null when contract was not found at this address.
*/
readonly getContractInfo: (address: string) => Promise<IQueryContractInfoResponse>;
/**
* Returns null when contract history was not found for this address.
*/
readonly getContractCodeHistory: (
address: string,
paginationKey?: Uint8Array,
) => Promise<IQueryContractHistoryResponse>;
/**
* Returns all contract state.
* This is an empty array if no such contract, or contract has no data.
*/
readonly getAllContractState: (
address: string,
paginationKey?: Uint8Array,
) => Promise<IQueryAllContractStateResponse>;
/**
* Returns the data at the key if present (unknown decoded json),
* or null if no data at this (contract address, key) pair
*/
readonly queryContractRaw: (
address: string,
key: Uint8Array,
) => Promise<IQueryRawContractStateResponse>;
/**
* Makes a smart query on the contract and parses the response as JSON.
* Throws error if no such contract exists, the query format is invalid or the response is invalid.
*/
readonly queryContractSmart: (
address: string,
query: Record<string, unknown>,
) => Promise<IQuerySmartContractStateResponse>;
};
};
}
export function setupWasmExtension(base: QueryClient): WasmExtension {
const queryService = Query.create((method: any, requestData, callback) => {
const path = `/cosmwasm.wasm.v1beta1.Query/${method.name}`;
base
.queryUnverified(path, requestData)
.then((response) => callback(null, response))
.catch((error) => callback(error));
});
return {
unverified: {
wasm: {
listCodeInfo: async (paginationKey?: Uint8Array) => {
const request = paginationKey ? { pagination: { key: paginationKey } } : {};
return queryService.codes(request);
},
getCode: async (id: number) => {
const request = { codeId: Long.fromNumber(id) };
return queryService.code(request);
},
listContractsByCodeId: async (id: number, paginationKey?: Uint8Array) => {
const pagination = paginationKey ? { pagination: { key: paginationKey } } : {};
const request = { ...pagination, codeId: Long.fromNumber(id) };
return queryService.contractsByCode(request);
},
getContractInfo: async (address: string) => {
const request = { address: address };
return queryService.contractInfo(request);
},
getContractCodeHistory: async (address: string, paginationKey?: Uint8Array) => {
const pagination = paginationKey ? { pagination: { key: paginationKey } } : {};
const request = { ...pagination, address: address };
return queryService.contractHistory(request);
},
getAllContractState: async (address: string, paginationKey?: Uint8Array) => {
const pagination = paginationKey ? { pagination: { key: paginationKey } } : {};
const request = { ...pagination, address: address };
return queryService.allContractState(request);
},
queryContractRaw: async (address: string, key: Uint8Array) => {
const request = { address: address, queryData: key };
return queryService.rawContractState(request);
},
queryContractSmart: async (address: string, query: Record<string, unknown>) => {
const request = { address: address, queryData: toAscii(JSON.stringify(query)) };
return queryService.smartContractState(request);
},
},
},
};
}

View File

@ -0,0 +1,583 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { UploadMeta } from "@cosmjs/cosmwasm";
import { sha256 } from "@cosmjs/crypto";
import { toHex } from "@cosmjs/encoding";
import { coin, coins, GasPrice } from "@cosmjs/launchpad";
import { Coin, cosmosField, DirectSecp256k1HdWallet, registered, Registry } from "@cosmjs/proto-signing";
import { assertIsBroadcastTxSuccess, codec } from "@cosmjs/stargate";
import { assert, sleep } from "@cosmjs/utils";
import Long from "long";
import { Message } from "protobufjs";
import { PrivateSigningCosmWasmClient, SigningCosmWasmClient } from "./signingcosmwasmclient";
import {
alice,
getHackatom,
makeRandomAddress,
makeWasmClient,
ModifyingDirectSecp256k1HdWallet,
ModifyingSecp256k1HdWallet,
pendingWithoutWasmd,
unused,
validator,
wasmd,
} from "./testutils.spec";
const { MsgDelegate } = codec.cosmos.staking.v1beta1;
const { Tx } = codec.cosmos.tx.v1beta1;
describe("SigningCosmWasmClient", () => {
describe("connectWithWallet", () => {
it("can be constructed", async () => {
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
expect(client).toBeTruthy();
});
it("can be constructed with custom gas price", async () => {
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const gasPrice = GasPrice.fromString("3.14utest");
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet, { gasPrice });
const openedClient = (client as unknown) as PrivateSigningCosmWasmClient;
expect(openedClient.fees).toEqual({
upload: {
amount: coins(4710000, "utest"),
gas: "1500000",
},
init: {
amount: coins(1570000, "utest"),
gas: "500000",
},
migrate: {
amount: coins(628000, "utest"),
gas: "200000",
},
exec: {
amount: coins(628000, "utest"),
gas: "200000",
},
send: {
amount: coins(251200, "utest"),
gas: "80000",
},
changeAdmin: {
amount: coins(251200, "utest"),
gas: "80000",
},
});
});
it("can be constructed with custom gas limits", async () => {
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const gasLimits = {
send: 160000,
};
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet, { gasLimits });
const openedClient = (client as unknown) as PrivateSigningCosmWasmClient;
expect(openedClient.fees).toEqual({
upload: {
amount: coins(37500, "ucosm"),
gas: "1500000",
},
init: {
amount: coins(12500, "ucosm"),
gas: "500000",
},
migrate: {
amount: coins(5000, "ucosm"),
gas: "200000",
},
exec: {
amount: coins(5000, "ucosm"),
gas: "200000",
},
send: {
amount: coins(4000, "ucosm"),
gas: "160000",
},
changeAdmin: {
amount: coins(2000, "ucosm"),
gas: "80000",
},
});
});
it("can be constructed with custom gas price and gas limits", async () => {
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const gasPrice = GasPrice.fromString("3.14utest");
const gasLimits = {
send: 160000,
};
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet, {
gasPrice,
gasLimits,
});
const openedClient = (client as unknown) as PrivateSigningCosmWasmClient;
expect(openedClient.fees).toEqual({
upload: {
amount: coins(4710000, "utest"),
gas: "1500000",
},
init: {
amount: coins(1570000, "utest"),
gas: "500000",
},
migrate: {
amount: coins(628000, "utest"),
gas: "200000",
},
exec: {
amount: coins(628000, "utest"),
gas: "200000",
},
send: {
amount: coins(502400, "utest"),
gas: "160000",
},
changeAdmin: {
amount: coins(251200, "utest"),
gas: "80000",
},
});
});
});
describe("upload", () => {
it("works", async () => {
pendingWithoutWasmd();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
const wasm = getHackatom().data;
const {
codeId,
originalChecksum,
originalSize,
compressedChecksum,
compressedSize,
} = await client.upload(alice.address0, wasm);
expect(originalChecksum).toEqual(toHex(sha256(wasm)));
expect(originalSize).toEqual(wasm.length);
expect(compressedChecksum).toMatch(/^[0-9a-f]{64}$/);
expect(compressedSize).toBeLessThan(wasm.length * 0.5);
expect(codeId).toBeGreaterThanOrEqual(1);
});
it("can set builder and source", async () => {
pendingWithoutWasmd();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
const hackatom = 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(alice.address0, hackatom.data, 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 wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
const { codeId } = await client.upload(alice.address0, getHackatom().data);
const transferAmount = [coin(1234, "ucosm"), coin(321, "ustake")];
const beneficiaryAddress = makeRandomAddress();
const { contractAddress } = await client.instantiate(
alice.address0,
codeId,
{
verifier: alice.address0,
beneficiary: beneficiaryAddress,
},
"My cool label",
{
memo: "Let's see if the memo is used",
transferAmount,
},
);
const wasmClient = await makeWasmClient(wasmd.endpoint);
const ucosmBalance = await wasmClient.bank.balance(contractAddress, "ucosm");
expect(ucosmBalance).toEqual(transferAmount[0]);
const ustakeBalance = await wasmClient.bank.balance(contractAddress, "ustake");
expect(ustakeBalance).toEqual(transferAmount[1]);
});
it("works with admin", async () => {
pendingWithoutWasmd();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
const { codeId } = await client.upload(alice.address0, getHackatom().data);
const beneficiaryAddress = makeRandomAddress();
const { contractAddress } = await client.instantiate(
alice.address0,
codeId,
{
verifier: alice.address0,
beneficiary: beneficiaryAddress,
},
"My cool label",
{ admin: unused.address },
);
const wasmClient = await makeWasmClient(wasmd.endpoint);
const { contractInfo } = await wasmClient.unverified.wasm.getContractInfo(contractAddress);
assert(contractInfo);
expect(contractInfo.admin).toEqual(unused.address);
});
it("can instantiate one code multiple times", async () => {
pendingWithoutWasmd();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
const { codeId } = await client.upload(alice.address0, getHackatom().data);
const contractAddress1 = await client.instantiate(
alice.address0,
codeId,
{
verifier: alice.address0,
beneficiary: makeRandomAddress(),
},
"contract 1",
);
const contractAddress2 = await client.instantiate(
alice.address0,
codeId,
{
verifier: alice.address0,
beneficiary: makeRandomAddress(),
},
"contract 2",
);
expect(contractAddress1).not.toEqual(contractAddress2);
});
});
describe("updateAdmin", () => {
it("can update an admin", async () => {
pendingWithoutWasmd();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
const { codeId } = await client.upload(alice.address0, getHackatom().data);
const beneficiaryAddress = makeRandomAddress();
const { contractAddress } = await client.instantiate(
alice.address0,
codeId,
{
verifier: alice.address0,
beneficiary: beneficiaryAddress,
},
"My cool label",
{
admin: alice.address0,
},
);
const wasmClient = await makeWasmClient(wasmd.endpoint);
const { contractInfo: contractInfo1 } = await wasmClient.unverified.wasm.getContractInfo(
contractAddress,
);
assert(contractInfo1);
expect(contractInfo1.admin).toEqual(alice.address0);
await client.updateAdmin(alice.address0, contractAddress, unused.address);
const { contractInfo: contractInfo2 } = await wasmClient.unverified.wasm.getContractInfo(
contractAddress,
);
assert(contractInfo2);
expect(contractInfo2.admin).toEqual(unused.address);
});
});
describe("clearAdmin", () => {
it("can clear an admin", async () => {
pendingWithoutWasmd();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
const { codeId } = await client.upload(alice.address0, getHackatom().data);
const beneficiaryAddress = makeRandomAddress();
const { contractAddress } = await client.instantiate(
alice.address0,
codeId,
{
verifier: alice.address0,
beneficiary: beneficiaryAddress,
},
"My cool label",
{
admin: alice.address0,
},
);
const wasmClient = await makeWasmClient(wasmd.endpoint);
const { contractInfo: contractInfo1 } = await wasmClient.unverified.wasm.getContractInfo(
contractAddress,
);
assert(contractInfo1);
expect(contractInfo1.admin).toEqual(alice.address0);
await client.clearAdmin(alice.address0, contractAddress);
const { contractInfo: contractInfo2 } = await wasmClient.unverified.wasm.getContractInfo(
contractAddress,
);
assert(contractInfo2);
expect(contractInfo2.admin).toEqual("");
});
});
describe("migrate", () => {
it("can can migrate from one code ID to another", async () => {
pendingWithoutWasmd();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
const { codeId: codeId1 } = await client.upload(alice.address0, getHackatom().data);
const { codeId: codeId2 } = await client.upload(alice.address0, getHackatom().data);
const beneficiaryAddress = makeRandomAddress();
const { contractAddress } = await client.instantiate(
alice.address0,
codeId1,
{
verifier: alice.address0,
beneficiary: beneficiaryAddress,
},
"My cool label",
{
admin: alice.address0,
},
);
const wasmClient = await makeWasmClient(wasmd.endpoint);
const { contractInfo: contractInfo1 } = await wasmClient.unverified.wasm.getContractInfo(
contractAddress,
);
assert(contractInfo1);
expect(contractInfo1.admin).toEqual(alice.address0);
const newVerifier = makeRandomAddress();
await client.migrate(alice.address0, contractAddress, codeId2, { verifier: newVerifier });
const { contractInfo: contractInfo2 } = await wasmClient.unverified.wasm.getContractInfo(
contractAddress,
);
assert(contractInfo2);
expect({ ...contractInfo2 }).toEqual({
...contractInfo1,
codeId: Long.fromNumber(codeId2, true),
});
});
});
describe("execute", () => {
it("works", async () => {
pendingWithoutWasmd();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
const { codeId } = await client.upload(alice.address0, getHackatom().data);
// instantiate
const transferAmount = [coin(233444, "ucosm"), coin(5454, "ustake")];
const beneficiaryAddress = makeRandomAddress();
const { contractAddress } = await client.instantiate(
alice.address0,
codeId,
{
verifier: alice.address0,
beneficiary: beneficiaryAddress,
},
"amazing random contract",
{
transferAmount,
},
);
// execute
const result = await client.execute(alice.address0, contractAddress, { release: {} }, undefined);
const wasmEvent = result.logs[0].events.find((e) => e.type === "wasm");
assert(wasmEvent, "Event of type wasm expected");
expect(wasmEvent.attributes).toContain({ key: "action", value: "release" });
expect(wasmEvent.attributes).toContain({
key: "destination",
value: beneficiaryAddress,
});
// Verify token transfer from contract to beneficiary
const wasmClient = await makeWasmClient(wasmd.endpoint);
const beneficiaryBalanceUcosm = await wasmClient.bank.balance(beneficiaryAddress, "ucosm");
expect(beneficiaryBalanceUcosm).toEqual(transferAmount[0]);
const beneficiaryBalanceUstake = await wasmClient.bank.balance(beneficiaryAddress, "ustake");
expect(beneficiaryBalanceUstake).toEqual(transferAmount[1]);
const contractBalanceUcosm = await wasmClient.bank.balance(contractAddress, "ucosm");
expect(contractBalanceUcosm).toEqual(coin(0, "ucosm"));
const contractBalanceUstake = await wasmClient.bank.balance(contractAddress, "ustake");
expect(contractBalanceUstake).toEqual(coin(0, "ustake"));
});
});
describe("sendTokens", () => {
it("works", async () => {
pendingWithoutWasmd();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet);
const transferAmount = coins(7890, "ucosm");
const beneficiaryAddress = makeRandomAddress();
const memo = "for dinner";
// no tokens here
const before = await client.getBalance(beneficiaryAddress, "ucosm");
expect(before).toBeNull();
// send
const result = await client.sendTokens(alice.address0, beneficiaryAddress, transferAmount, memo);
assertIsBroadcastTxSuccess(result);
expect(result.rawLog).toBeTruthy();
// got tokens
const after = await client.getBalance(beneficiaryAddress, "ucosm");
assert(after);
expect(after).toEqual(transferAmount[0]);
});
});
describe("signAndBroadcast", () => {
describe("direct mode", () => {
it("works", async () => {
pendingWithoutWasmd();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate";
const registry = new Registry();
registry.register(msgDelegateTypeUrl, MsgDelegate);
const options = { registry: registry };
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet, options);
const msg = MsgDelegate.create({
delegatorAddress: alice.address0,
validatorAddress: validator.validatorAddress,
amount: coin(1234, "ustake"),
});
const msgAny = {
typeUrl: msgDelegateTypeUrl,
value: msg,
};
const fee = {
amount: coins(2000, "ucosm"),
gas: "180000", // 180k
};
const memo = "Use your power wisely";
const result = await client.signAndBroadcast(alice.address0, [msgAny], fee, memo);
assertIsBroadcastTxSuccess(result);
});
it("works with a modifying signer", async () => {
pendingWithoutWasmd();
const wallet = await ModifyingDirectSecp256k1HdWallet.fromMnemonic(
alice.mnemonic,
undefined,
wasmd.prefix,
);
const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate";
const registry = new Registry();
registry.register(msgDelegateTypeUrl, MsgDelegate);
const options = { registry: registry };
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet, options);
const msg = MsgDelegate.create({
delegatorAddress: alice.address0,
validatorAddress: validator.validatorAddress,
amount: coin(1234, "ustake"),
});
const msgAny = {
typeUrl: msgDelegateTypeUrl,
value: msg,
};
const fee = {
amount: coins(2000, "ucosm"),
gas: "180000", // 180k
};
const memo = "Use your power wisely";
const result = await client.signAndBroadcast(alice.address0, [msgAny], fee, memo);
assertIsBroadcastTxSuccess(result);
await sleep(1000);
const searchResult = await client.searchTx({ id: result.transactionHash });
const tx = Tx.decode(searchResult[0].tx);
// From ModifyingDirectSecp256k1HdWallet
expect(tx.body!.memo).toEqual("This was modified");
expect({ ...tx.authInfo!.fee!.amount![0] }).toEqual(coin(3000, "ucosm"));
expect(tx.authInfo!.fee!.gasLimit!.toNumber()).toEqual(333333);
});
});
describe("legacy Amino mode", () => {
// NOTE: One registry shared between tests
// See https://github.com/protobufjs/protobuf.js#using-decorators
// > Decorated types reside in protobuf.roots["decorated"] using a flat structure, so no duplicate names.
const registry = new Registry();
const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate";
@registered(registry, msgDelegateTypeUrl)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class CustomMsgDelegate extends Message {
@cosmosField.string(1)
public readonly delegator_address?: string;
@cosmosField.string(2)
public readonly validator_address?: string;
@cosmosField.message(3, Coin)
public readonly amount?: Coin;
}
it("works", async () => {
pendingWithoutWasmd();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const options = { registry: registry };
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet, options);
const msg = {
delegator_address: alice.address0,
validator_address: validator.validatorAddress,
amount: coin(1234, "ustake"),
};
const msgAny = {
typeUrl: msgDelegateTypeUrl,
value: msg,
};
const fee = {
amount: coins(2000, "ucosm"),
gas: "200000",
};
const memo = "Use your power wisely";
const result = await client.signAndBroadcast(alice.address0, [msgAny], fee, memo);
assertIsBroadcastTxSuccess(result);
});
it("works with a modifying signer", async () => {
pendingWithoutWasmd();
const wallet = await ModifyingSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const options = { registry: registry };
const client = await SigningCosmWasmClient.connectWithWallet(wasmd.endpoint, wallet, options);
const msg = {
delegator_address: alice.address0,
validator_address: validator.validatorAddress,
amount: coin(1234, "ustake"),
};
const msgAny = {
typeUrl: msgDelegateTypeUrl,
value: msg,
};
const fee = {
amount: coins(2000, "ucosm"),
gas: "200000",
};
const memo = "Use your power wisely";
const result = await client.signAndBroadcast(alice.address0, [msgAny], fee, memo);
assertIsBroadcastTxSuccess(result);
await sleep(1000);
const searchResult = await client.searchTx({ id: result.transactionHash });
const tx = Tx.decode(searchResult[0].tx);
// From ModifyingSecp256k1HdWallet
expect(tx.body!.memo).toEqual("This was modified");
expect({ ...tx.authInfo!.fee!.amount![0] }).toEqual(coin(3000, "ucosm"));
expect(tx.authInfo!.fee!.gasLimit!.toNumber()).toEqual(333333);
});
});
});
});

View File

@ -0,0 +1,404 @@
/* eslint-disable @typescript-eslint/naming-convention */
import {
ChangeAdminResult,
CosmWasmFeeTable,
ExecuteResult,
InstantiateOptions,
InstantiateResult,
isValidBuilder,
MigrateResult,
UploadMeta,
UploadResult,
} from "@cosmjs/cosmwasm";
import { sha256 } from "@cosmjs/crypto";
import { fromBase64, toAscii, toHex } from "@cosmjs/encoding";
import {
AccountData,
buildFeeTable,
Coin,
CosmosFeeTable,
encodeSecp256k1Pubkey,
GasLimits,
GasPrice,
logs,
makeSignDoc as makeSignDocAmino,
StdFee,
} from "@cosmjs/launchpad";
import { Int53, Uint53 } from "@cosmjs/math";
import {
EncodeObject,
encodePubkey,
isOfflineDirectSigner,
makeAuthInfoBytes,
makeSignDoc,
OfflineSigner,
Registry,
} from "@cosmjs/proto-signing";
import {
BroadcastTxFailure,
BroadcastTxResponse,
codec,
getMsgType,
getMsgTypeUrl,
isBroadcastTxFailure,
parseRawLog,
} from "@cosmjs/stargate";
import { adaptor34, Client as TendermintClient } from "@cosmjs/tendermint-rpc";
import Long from "long";
import pako from "pako";
import { cosmwasm } from "./codec";
import { CosmWasmClient } from "./cosmwasmclient";
const { TxRaw } = codec.cosmos.tx.v1beta1;
const { SignMode } = codec.cosmos.tx.signing.v1beta1;
const {
MsgClearAdmin,
MsgExecuteContract,
MsgInstantiateContract,
MsgMigrateContract,
MsgStoreCode,
MsgUpdateAdmin,
} = cosmwasm.wasm.v1beta1;
function prepareBuilder(builder: string | undefined): string {
if (builder === undefined) {
return ""; // normalization needed by backend
} else {
if (!isValidBuilder(builder)) throw new Error("The builder (Docker Hub image with tag) is not valid");
return builder;
}
}
const defaultGasPrice = GasPrice.fromString("0.025ucosm");
const defaultGasLimits: GasLimits<CosmWasmFeeTable> = {
upload: 1_500_000,
init: 500_000,
migrate: 200_000,
exec: 200_000,
send: 80_000,
changeAdmin: 80_000,
};
function createBroadcastTxErrorMessage(result: BroadcastTxFailure): string {
return `Error when broadcasting tx ${result.transactionHash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.rawLog}`;
}
function createDefaultRegistry(): Registry {
return new Registry([
["/cosmwasm.wasm.v1beta1.MsgClearAdmin", MsgClearAdmin],
["/cosmwasm.wasm.v1beta1.MsgExecuteContract", MsgExecuteContract],
["/cosmwasm.wasm.v1beta1.MsgMigrateContract", MsgMigrateContract],
["/cosmwasm.wasm.v1beta1.MsgStoreCode", MsgStoreCode],
["/cosmwasm.wasm.v1beta1.MsgInstantiateContract", MsgInstantiateContract],
["/cosmwasm.wasm.v1beta1.MsgUpdateAdmin", MsgUpdateAdmin],
]);
}
export interface SigningCosmWasmClientOptions {
readonly registry?: Registry;
readonly gasPrice?: GasPrice;
readonly gasLimits?: GasLimits<CosmosFeeTable>;
}
/** Use for testing only */
export interface PrivateSigningCosmWasmClient {
readonly fees: CosmWasmFeeTable;
}
export class SigningCosmWasmClient extends CosmWasmClient {
private readonly fees: CosmosFeeTable;
private readonly registry: Registry;
private readonly signer: OfflineSigner;
public static async connectWithWallet(
endpoint: string,
signer: OfflineSigner,
options: SigningCosmWasmClientOptions = {},
): Promise<SigningCosmWasmClient> {
const tmClient = await TendermintClient.connect(endpoint, adaptor34);
return new SigningCosmWasmClient(tmClient, signer, options);
}
private constructor(
tmClient: TendermintClient,
signer: OfflineSigner,
options: SigningCosmWasmClientOptions,
) {
super(tmClient);
const {
registry = createDefaultRegistry(),
gasPrice = defaultGasPrice,
gasLimits = defaultGasLimits,
} = options;
this.fees = buildFeeTable<CosmosFeeTable>(gasPrice, defaultGasLimits, gasLimits);
this.registry = registry;
this.signer = signer;
}
/** Uploads code and returns a receipt, including the code ID */
public async upload(
senderAddress: string,
wasmCode: Uint8Array,
meta: UploadMeta = {},
memo = "",
): Promise<UploadResult> {
const source = meta.source || "";
const builder = prepareBuilder(meta.builder);
const compressed = pako.gzip(wasmCode, { level: 9 });
const storeCodeMsg = {
typeUrl: "/cosmwasm.wasm.v1beta1.MsgStoreCode",
value: MsgStoreCode.create({
sender: senderAddress,
wasmByteCode: compressed,
source: source,
builder: builder,
}),
};
const result = await this.signAndBroadcast(senderAddress, [storeCodeMsg], this.fees.upload, memo);
if (isBroadcastTxFailure(result)) {
throw new Error(createBroadcastTxErrorMessage(result));
}
const parsedLogs = parseRawLog(result.rawLog);
const codeIdAttr = logs.findAttribute(parsedLogs, "message", "code_id");
return {
originalSize: wasmCode.length,
originalChecksum: toHex(sha256(wasmCode)),
compressedSize: compressed.length,
compressedChecksum: toHex(sha256(compressed)),
codeId: Number.parseInt(codeIdAttr.value, 10),
logs: parsedLogs,
transactionHash: result.transactionHash,
};
}
public async instantiate(
senderAddress: string,
codeId: number,
initMsg: Record<string, unknown>,
label: string,
options: InstantiateOptions = {},
): Promise<InstantiateResult> {
const instantiateMsg = {
typeUrl: "/cosmwasm.wasm.v1beta1.MsgInstantiateContract",
value: MsgInstantiateContract.create({
sender: senderAddress,
codeId: Long.fromString(new Uint53(codeId).toString()),
label: label,
initMsg: toAscii(JSON.stringify(initMsg)),
initFunds: [...(options.transferAmount || [])],
admin: options.admin,
}),
};
const result = await this.signAndBroadcast(senderAddress, [instantiateMsg], this.fees.init, options.memo);
if (isBroadcastTxFailure(result)) {
throw new Error(createBroadcastTxErrorMessage(result));
}
const parsedLogs = parseRawLog(result.rawLog);
const contractAddressAttr = logs.findAttribute(parsedLogs, "message", "contract_address");
return {
contractAddress: contractAddressAttr.value,
logs: parsedLogs,
transactionHash: result.transactionHash,
};
}
public async updateAdmin(
senderAddress: string,
contractAddress: string,
newAdmin: string,
memo = "",
): Promise<ChangeAdminResult> {
const updateAdminMsg = {
typeUrl: "/cosmwasm.wasm.v1beta1.MsgUpdateAdmin",
value: MsgUpdateAdmin.create({
sender: senderAddress,
contract: contractAddress,
newAdmin: newAdmin,
}),
};
const result = await this.signAndBroadcast(senderAddress, [updateAdminMsg], this.fees.changeAdmin, memo);
if (isBroadcastTxFailure(result)) {
throw new Error(createBroadcastTxErrorMessage(result));
}
return {
logs: parseRawLog(result.rawLog),
transactionHash: result.transactionHash,
};
}
public async clearAdmin(
senderAddress: string,
contractAddress: string,
memo = "",
): Promise<ChangeAdminResult> {
const clearAdminMsg = {
typeUrl: "/cosmwasm.wasm.v1beta1.MsgClearAdmin",
value: MsgClearAdmin.create({
sender: senderAddress,
contract: contractAddress,
}),
};
const result = await this.signAndBroadcast(senderAddress, [clearAdminMsg], this.fees.changeAdmin, memo);
if (isBroadcastTxFailure(result)) {
throw new Error(createBroadcastTxErrorMessage(result));
}
return {
logs: parseRawLog(result.rawLog),
transactionHash: result.transactionHash,
};
}
public async migrate(
senderAddress: string,
contractAddress: string,
codeId: number,
migrateMsg: Record<string, unknown>,
memo = "",
): Promise<MigrateResult> {
const msg = {
typeUrl: "/cosmwasm.wasm.v1beta1.MsgMigrateContract",
value: MsgMigrateContract.create({
sender: senderAddress,
contract: contractAddress,
codeId: Long.fromString(new Uint53(codeId).toString()),
migrateMsg: toAscii(JSON.stringify(migrateMsg)),
}),
};
const result = await this.signAndBroadcast(senderAddress, [msg], this.fees.migrate, memo);
if (isBroadcastTxFailure(result)) {
throw new Error(createBroadcastTxErrorMessage(result));
}
return {
logs: parseRawLog(result.rawLog),
transactionHash: result.transactionHash,
};
}
public async execute(
senderAddress: string,
contractAddress: string,
handleMsg: Record<string, unknown>,
memo = "",
transferAmount?: readonly Coin[],
): Promise<ExecuteResult> {
const executeMsg = {
typeUrl: "/cosmwasm.wasm.v1beta1.MsgExecuteContract",
value: MsgExecuteContract.create({
sender: senderAddress,
contract: contractAddress,
msg: toAscii(JSON.stringify(handleMsg)),
sentFunds: [...(transferAmount || [])],
}),
};
const result = await this.signAndBroadcast(senderAddress, [executeMsg], this.fees.exec, memo);
if (isBroadcastTxFailure(result)) {
throw new Error(createBroadcastTxErrorMessage(result));
}
return {
logs: parseRawLog(result.rawLog),
transactionHash: result.transactionHash,
};
}
public async sendTokens(
senderAddress: string,
recipientAddress: string,
transferAmount: readonly Coin[],
memo = "",
): Promise<BroadcastTxResponse> {
const sendMsg = {
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
value: {
fromAddress: senderAddress,
toAddress: recipientAddress,
amount: transferAmount,
},
};
return this.signAndBroadcast(senderAddress, [sendMsg], this.fees.send, memo);
}
public async signAndBroadcast(
address: string,
messages: readonly EncodeObject[],
fee: StdFee,
memo = "",
): Promise<BroadcastTxResponse> {
const accountFromSigner = (await this.signer.getAccounts()).find(
(account: AccountData) => account.address === address,
);
if (!accountFromSigner) {
throw new Error("Failed to retrieve account from signer");
}
const pubkey = encodeSecp256k1Pubkey(accountFromSigner.pubkey);
const accountFromChain = await this.getAccount(address);
if (!accountFromChain) {
throw new Error("Account not found");
}
const { accountNumber, sequence } = accountFromChain;
if (!pubkey) {
throw new Error("Pubkey not known");
}
const chainId = await this.getChainId();
const pubkeyAny = encodePubkey(pubkey);
const txBody = {
messages: messages,
memo: memo,
};
const txBodyBytes = this.registry.encode({
typeUrl: "/cosmos.tx.v1beta1.TxBody",
value: txBody,
});
const gasLimit = Int53.fromString(fee.gas).toNumber();
if (isOfflineDirectSigner(this.signer)) {
const authInfoBytes = makeAuthInfoBytes([pubkeyAny], fee.amount, gasLimit, sequence);
const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber);
const { signature, signed } = await this.signer.signDirect(address, signDoc);
const txRaw = TxRaw.create({
bodyBytes: signed.bodyBytes,
authInfoBytes: signed.authInfoBytes,
signatures: [fromBase64(signature.signature)],
});
const signedTx = Uint8Array.from(TxRaw.encode(txRaw).finish());
return this.broadcastTx(signedTx);
}
// Amino signer
const signMode = SignMode.SIGN_MODE_LEGACY_AMINO_JSON;
const msgs = messages.map((msg) => ({
type: getMsgType(msg.typeUrl),
value: msg.value,
}));
const signDoc = makeSignDocAmino(msgs, fee, chainId, memo, accountNumber, sequence);
const { signature, signed } = await this.signer.signAmino(address, signDoc);
const signedTxBody = {
messages: signed.msgs.map((msg) => ({
typeUrl: getMsgTypeUrl(msg.type),
value: msg.value,
})),
memo: signed.memo,
};
const signedTxBodyBytes = this.registry.encode({
typeUrl: "/cosmos.tx.v1beta1.TxBody",
value: signedTxBody,
});
const signedGasLimit = Int53.fromString(signed.fee.gas).toNumber();
const signedSequence = Int53.fromString(signed.sequence).toNumber();
const signedAuthInfoBytes = makeAuthInfoBytes(
[pubkeyAny],
signed.fee.amount,
signedGasLimit,
signedSequence,
signMode,
);
const txRaw = TxRaw.create({
bodyBytes: signedTxBodyBytes,
authInfoBytes: signedAuthInfoBytes,
signatures: [fromBase64(signature.signature)],
});
const signedTx = Uint8Array.from(TxRaw.encode(txRaw).finish());
return this.broadcastTx(signedTx);
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,302 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Bip39, EnglishMnemonic, Random, Secp256k1, Slip10, Slip10Curve } from "@cosmjs/crypto";
import { Bech32, fromBase64 } from "@cosmjs/encoding";
import {
AminoSignResponse,
coins,
makeCosmoshubPath,
Secp256k1HdWallet,
StdSignDoc,
} from "@cosmjs/launchpad";
import { DirectSecp256k1HdWallet, DirectSignResponse, makeAuthInfoBytes } from "@cosmjs/proto-signing";
import {
AuthExtension,
BankExtension,
codec,
QueryClient,
setupAuthExtension,
setupBankExtension,
} from "@cosmjs/stargate";
import { adaptor34, Client as TendermintClient } from "@cosmjs/tendermint-rpc";
import { setupWasmExtension, WasmExtension } from "./queries";
import hackatom from "./testdata/contract.json";
type ISignDoc = codec.cosmos.tx.v1beta1.ISignDoc;
const { AuthInfo, TxBody } = codec.cosmos.tx.v1beta1;
const { SignMode } = codec.cosmos.tx.signing.v1beta1;
/** An internal testing type. SigningCosmWasmClient has a similar but different interface */
export interface ContractUploadInstructions {
/** The wasm bytecode */
readonly data: Uint8Array;
readonly source?: string;
readonly builder?: string;
}
export const wasmd = {
blockTime: 1_000, // ms
chainId: "testing",
endpoint: "http://localhost:26659",
prefix: "wasm",
validator: {
address: "wasmvaloper1m4vhsgne6u74ff78vf0tvkjq3q4hjf9vjfrmy2",
},
};
export function getHackatom(): ContractUploadInstructions {
return {
data: fromBase64(hackatom.data),
source: "https://crates.io/api/v1/crates/hackatom/not-yet-released/download",
builder: "cosmwasm/rust-optimizer:0.9.1",
};
}
export function makeRandomAddress(): string {
return Bech32.encode("wasm", Random.getBytes(20));
}
export const tendermintIdMatcher = /^[0-9A-F]{64}$/;
/** @see https://rgxdb.com/r/1NUN74O6 */
export const base64Matcher = /^(?:[a-zA-Z0-9+/]{4})*(?:|(?:[a-zA-Z0-9+/]{3}=)|(?:[a-zA-Z0-9+/]{2}==)|(?:[a-zA-Z0-9+/]{1}===))$/;
// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32
export const bech32AddressMatcher = /^[\x21-\x7e]{1,83}1[02-9ac-hj-np-z]{38}$/;
export const alice = {
mnemonic: "enlist hip relief stomach skate base shallow young switch frequent cry park",
pubkey0: {
type: "tendermint/PubKeySecp256k1",
value: "A9cXhWb8ZpqCzkA8dQCPV29KdeRLV3rUYxrkHudLbQtS",
},
address0: "wasm14qemq0vw6y3gc3u3e0aty2e764u4gs5lndxgyk",
address1: "wasm1hhg2rlu9jscacku2wwckws7932qqqu8xm5ca8y",
address2: "wasm1xv9tklw7d82sezh9haa573wufgy59vmwnxhnsl",
address3: "wasm17yg9mssjenmc3jkqth6ulcwj9cxujrxxg9nmzk",
address4: "wasm1f7j7ryulwjfe9ljplvhtcaxa6wqgula3nh873j",
};
/** Unused account */
export const unused = {
pubkey: {
type: "tendermint/PubKeySecp256k1",
value: "ArkCaFUJ/IH+vKBmNRCdUVl3mCAhbopk9jjW4Ko4OfRQ",
},
address: "wasm1cjsxept9rkggzxztslae9ndgpdyt240842kpxh",
accountNumber: 16,
sequence: 0,
};
export const validator = {
/** From first gentx's auth_info.signer_infos in scripts/wasmd/template/.wasmd/config/genesis.json */
pubkey: {
type: "tendermint/PubKeySecp256k1",
value: "AsYCD9IZsnY3BhSrR3k7mf5iaJD0KkQdwqzLLl9PT+05",
},
/** delegator_address from /cosmos.staking.v1beta1.MsgCreateValidator in scripts/wasmd/template/.wasmd/config/genesis.json */
delegatorAddress: "wasm1m4vhsgne6u74ff78vf0tvkjq3q4hjf9v84k82s",
/** validator_address from /cosmos.staking.v1beta1.MsgCreateValidator in scripts/wasmd/template/.wasmd/config/genesis.json */
validatorAddress: "wasmvaloper1m4vhsgne6u74ff78vf0tvkjq3q4hjf9vjfrmy2",
accountNumber: 0,
sequence: 1,
};
/** Deployed as part of scripts/wasmd/init.sh */
export const deployedHackatom = {
codeId: 1,
source: "https://crates.io/api/v1/crates/hackatom/not-yet-released/download",
builder: "cosmwasm/rust-optimizer:0.9.1",
checksum: "3defc33a41f58c71d38b176d521c411d8e74d26403fde7660486930c7579a016",
instances: [
{
beneficiary: alice.address0,
address: "wasm18vd8fpwxzck93qlwghaj6arh4p7c5n89k7fvsl",
label: "From deploy_hackatom.js (0)",
},
{
beneficiary: alice.address1,
address: "wasm1hqrdl6wstt8qzshwc6mrumpjk9338k0lffu40x",
label: "From deploy_hackatom.js (1)",
},
{
beneficiary: alice.address2,
address: "wasm18r5szma8hm93pvx6lwpjwyxruw27e0k5kjkyan",
label: "From deploy_hackatom.js (2)",
},
],
};
// /** Deployed as part of scripts/wasmd/init.sh */
// export const deployedErc20 = {
// codeId: 2,
// source: "https://crates.io/api/v1/crates/cw-erc20/0.7.0/download",
// builder: "cosmwasm/rust-optimizer:0.10.4",
// checksum: "d04368320ad55089384adb171aaea39e43d710d7608829adba0300ed30aa2988",
// instances: [
// // TODO: Update this address
// "cosmos1vjecguu37pmd577339wrdp208ddzymkudc46zj", // HASH
// // TODO: Update this address
// "cosmos1ym5m5dw7pttft5w430nxx6uat8f84ck4algmhg", // ISA
// // TODO: Update this address
// "cosmos1gv07846a3867ezn3uqkk082c5ftke7hpllcu8q", // JADE
// ],
// };
// /** Deployed as part of scripts/wasmd/init.sh */
// export const deployedCw3 = {
// codeId: 3,
// source: "https://crates.io/api/v1/crates/cw3-fixed-multisig/0.3.1/download",
// builder: "cosmwasm/rust-optimizer:0.10.4",
// instances: [
// // TODO: Update this address
// "cosmos1xqeym28j9xgv0p93pwwt6qcxf9tdvf9zddufdw", // Multisig (1/3)
// // TODO: Update this address
// "cosmos1jka38ckju8cpjap00jf9xdvdyttz9caujtd6t5", // Multisig (2/3)
// // TODO: Update this address
// "cosmos12dnl585uxzddjw9hw4ca694f054shgpgr4zg80", // Multisig (uneven weights)
// ],
// };
// /** Deployed as part of scripts/wasmd/init.sh */
// export const deployedCw1 = {
// codeId: 4,
// source: "https://crates.io/api/v1/crates/cw1-subkeys/0.3.1/download",
// builder: "cosmwasm/rust-optimizer:0.10.4",
// // TODO: Update this address
// instances: ["cosmos1vs2vuks65rq7xj78mwtvn7vvnm2gn7ad5me0d2"],
// };
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");
}
}
// export function erc20Enabled(): boolean {
// return !!process.env.ERC20_ENABLED;
// }
// export function pendingWithoutErc20(): void {
// if (!erc20Enabled()) {
// return pending("Set ERC20_ENABLED to enable ERC20-based tests");
// }
// }
// export function cw3Enabled(): boolean {
// return !!process.env.CW3_ENABLED;
// }
// export function pendingWithoutCw3(): void {
// if (!cw3Enabled()) {
// return pending("Set CW3_ENABLED to enable CW3-based tests");
// }
// }
// export function cw1Enabled(): boolean {
// return !!process.env.CW1_ENABLED;
// }
// export function pendingWithoutCw1(): void {
// if (!cw1Enabled()) {
// return pending("Set CW1_ENABLED to enable CW1-based tests");
// }
// }
/** Returns first element. Throws if array has a different length than 1. */
export function fromOneElementArray<T>(elements: ArrayLike<T>): T {
if (elements.length !== 1) throw new Error(`Expected exactly one element but got ${elements.length}`);
return elements[0];
}
export async function makeWasmClient(
endpoint: string,
): Promise<QueryClient & AuthExtension & BankExtension & WasmExtension> {
const tmClient = await TendermintClient.connect(endpoint, adaptor34);
return QueryClient.withExtensions(tmClient, setupAuthExtension, setupBankExtension, setupWasmExtension);
}
/**
* A class for testing clients using an Amino signer which modifies the transaction it receives before signing
*/
export class ModifyingSecp256k1HdWallet extends Secp256k1HdWallet {
public static async fromMnemonic(
mnemonic: string,
hdPath = makeCosmoshubPath(0),
prefix = "cosmos",
): Promise<ModifyingSecp256k1HdWallet> {
const mnemonicChecked = new EnglishMnemonic(mnemonic);
const seed = await Bip39.mnemonicToSeed(mnemonicChecked);
const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, hdPath);
const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey;
return new ModifyingSecp256k1HdWallet(
mnemonicChecked,
hdPath,
privkey,
Secp256k1.compressPubkey(uncompressed),
prefix,
);
}
public async signAmino(signerAddress: string, signDoc: StdSignDoc): Promise<AminoSignResponse> {
const modifiedSignDoc = {
...signDoc,
fee: {
amount: coins(3000, "ucosm"),
gas: "333333",
},
memo: "This was modified",
};
return super.signAmino(signerAddress, modifiedSignDoc);
}
}
/**
* A class for testing clients using a direct signer which modifies the transaction it receives before signing
*/
export class ModifyingDirectSecp256k1HdWallet extends DirectSecp256k1HdWallet {
public static async fromMnemonic(
mnemonic: string,
hdPath = makeCosmoshubPath(0),
prefix = "cosmos",
): Promise<DirectSecp256k1HdWallet> {
const mnemonicChecked = new EnglishMnemonic(mnemonic);
const seed = await Bip39.mnemonicToSeed(mnemonicChecked);
const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, hdPath);
const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey;
return new ModifyingDirectSecp256k1HdWallet(
mnemonicChecked,
hdPath,
privkey,
Secp256k1.compressPubkey(uncompressed),
prefix,
);
}
public async signDirect(address: string, signDoc: ISignDoc): Promise<DirectSignResponse> {
const txBody = TxBody.decode(signDoc.bodyBytes!);
const modifiedTxBody = TxBody.create({
...txBody,
memo: "This was modified",
});
const authInfo = AuthInfo.decode(signDoc.authInfoBytes!);
const pubkeys = authInfo.signerInfos.map((signerInfo) => signerInfo.publicKey!);
const sequence = authInfo.signerInfos[0].sequence!.toNumber();
const modifiedFeeAmount = coins(3000, "ucosm");
const modifiedGasLimit = 333333;
const modifiedSignDoc = {
...signDoc,
bodyBytes: Uint8Array.from(TxBody.encode(modifiedTxBody).finish()),
authInfoBytes: makeAuthInfoBytes(
pubkeys,
modifiedFeeAmount,
modifiedGasLimit,
sequence,
SignMode.SIGN_MODE_DIRECT,
),
};
return super.signDirect(address, modifiedSignDoc);
}
}

View File

@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"outDir": "build",
"declarationDir": "build/types",
"experimentalDecorators": true,
"rootDir": "src"
},
"include": [
"src/**/*"
]
}

View File

@ -0,0 +1,13 @@
const packageJson = require("./package.json");
module.exports = {
inputFiles: ["./src"],
out: "docs",
exclude: "**/*.spec.ts",
name: `${packageJson.name} Documentation`,
readme: "README.md",
mode: "file",
excludeExternals: true,
excludeNotExported: true,
excludePrivate: true,
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
export { cosmwasm } from "./generated/codecimpl";

View File

@ -0,0 +1,62 @@
import { Code, CodeDetails, Contract, ContractCodeHistoryEntry, JsonObject } from "@cosmjs/cosmwasm";
import { Block, Coin, SearchTxFilter, SearchTxQuery } from "@cosmjs/launchpad";
import {
Account,
AuthExtension,
BankExtension,
BroadcastTxResponse,
IndexedTx,
QueryClient,
SequenceResponse,
} from "@cosmjs/stargate";
import { Client as TendermintClient } from "@cosmjs/tendermint-rpc";
import { WasmExtension } from "./queries";
/** Use for testing only */
export interface PrivateCosmWasmClient {
readonly tmClient: TendermintClient;
readonly queryClient: QueryClient & AuthExtension & BankExtension & WasmExtension;
}
export declare class CosmWasmClient {
private readonly tmClient;
private readonly queryClient;
private readonly codesCache;
private chainId;
static connect(endpoint: string): Promise<CosmWasmClient>;
protected constructor(tmClient: TendermintClient);
getChainId(): Promise<string>;
getHeight(): Promise<number>;
getAccount(searchAddress: string): Promise<Account | null>;
getSequence(address: string): Promise<SequenceResponse | null>;
getBlock(height?: number): Promise<Block>;
getBalance(address: string, searchDenom: string): Promise<Coin | null>;
searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise<readonly IndexedTx[]>;
disconnect(): void;
broadcastTx(tx: Uint8Array): Promise<BroadcastTxResponse>;
getCodes(): Promise<readonly Code[]>;
getCodeDetails(codeId: number): Promise<CodeDetails>;
getContracts(codeId: number): Promise<readonly Contract[]>;
/**
* Throws an error if no contract was found at the address
*/
getContract(address: string): Promise<Contract>;
/**
* Throws an error if no contract was found at the address
*/
getContractCodeHistory(address: string): Promise<readonly ContractCodeHistoryEntry[]>;
/**
* 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<Uint8Array | null>;
/**
* Makes a smart query on the contract, returns the parsed JSON document.
*
* Promise is rejected when contract does not exist.
* Promise is rejected for invalid query format.
* Promise is rejected for invalid response format.
*/
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
private txsQuery;
}

View File

@ -0,0 +1,3 @@
export * as codec from "./codec";
export { CosmWasmClient } from "./cosmwasmclient";
export { SigningCosmWasmClient, SigningCosmWasmClientOptions } from "./signingcosmwasmclient";

View File

@ -0,0 +1 @@
export { setupWasmExtension, WasmExtension } from "./wasm";

View File

@ -0,0 +1,64 @@
import { QueryClient } from "@cosmjs/stargate";
import { cosmwasm } from "../codec";
declare type IQueryAllContractStateResponse = cosmwasm.wasm.v1beta1.IQueryAllContractStateResponse;
declare type IQueryCodesResponse = cosmwasm.wasm.v1beta1.IQueryCodesResponse;
declare type IQueryCodeResponse = cosmwasm.wasm.v1beta1.IQueryCodeResponse;
declare type IQueryContractHistoryResponse = cosmwasm.wasm.v1beta1.IQueryContractHistoryResponse;
declare type IQueryContractInfoResponse = cosmwasm.wasm.v1beta1.IQueryContractInfoResponse;
declare type IQueryContractsByCodeResponse = cosmwasm.wasm.v1beta1.IQueryContractsByCodeResponse;
declare type IQueryRawContractStateResponse = cosmwasm.wasm.v1beta1.IQueryRawContractStateResponse;
declare type IQuerySmartContractStateResponse = cosmwasm.wasm.v1beta1.IQuerySmartContractStateResponse;
export interface WasmExtension {
readonly unverified: {
readonly wasm: {
readonly listCodeInfo: (paginationKey?: Uint8Array) => Promise<IQueryCodesResponse>;
/**
* Downloads the original wasm bytecode by code ID.
*
* Throws an error if no code with this id
*/
readonly getCode: (id: number) => Promise<IQueryCodeResponse>;
readonly listContractsByCodeId: (
id: number,
paginationKey?: Uint8Array,
) => Promise<IQueryContractsByCodeResponse>;
/**
* Returns null when contract was not found at this address.
*/
readonly getContractInfo: (address: string) => Promise<IQueryContractInfoResponse>;
/**
* Returns null when contract history was not found for this address.
*/
readonly getContractCodeHistory: (
address: string,
paginationKey?: Uint8Array,
) => Promise<IQueryContractHistoryResponse>;
/**
* Returns all contract state.
* This is an empty array if no such contract, or contract has no data.
*/
readonly getAllContractState: (
address: string,
paginationKey?: Uint8Array,
) => Promise<IQueryAllContractStateResponse>;
/**
* Returns the data at the key if present (unknown decoded json),
* or null if no data at this (contract address, key) pair
*/
readonly queryContractRaw: (
address: string,
key: Uint8Array,
) => Promise<IQueryRawContractStateResponse>;
/**
* Makes a smart query on the contract and parses the response as JSON.
* Throws error if no such contract exists, the query format is invalid or the response is invalid.
*/
readonly queryContractSmart: (
address: string,
query: Record<string, unknown>,
) => Promise<IQuerySmartContractStateResponse>;
};
};
}
export declare function setupWasmExtension(base: QueryClient): WasmExtension;
export {};

View File

@ -0,0 +1,81 @@
import {
ChangeAdminResult,
CosmWasmFeeTable,
ExecuteResult,
InstantiateOptions,
InstantiateResult,
MigrateResult,
UploadMeta,
UploadResult,
} from "@cosmjs/cosmwasm";
import { Coin, CosmosFeeTable, GasLimits, GasPrice, StdFee } from "@cosmjs/launchpad";
import { EncodeObject, OfflineSigner, Registry } from "@cosmjs/proto-signing";
import { BroadcastTxResponse } from "@cosmjs/stargate";
import { CosmWasmClient } from "./cosmwasmclient";
export interface SigningCosmWasmClientOptions {
readonly registry?: Registry;
readonly gasPrice?: GasPrice;
readonly gasLimits?: GasLimits<CosmosFeeTable>;
}
/** Use for testing only */
export interface PrivateSigningCosmWasmClient {
readonly fees: CosmWasmFeeTable;
}
export declare class SigningCosmWasmClient extends CosmWasmClient {
private readonly fees;
private readonly registry;
private readonly signer;
static connectWithWallet(
endpoint: string,
signer: OfflineSigner,
options?: SigningCosmWasmClientOptions,
): Promise<SigningCosmWasmClient>;
private constructor();
/** Uploads code and returns a receipt, including the code ID */
upload(
senderAddress: string,
wasmCode: Uint8Array,
meta?: UploadMeta,
memo?: string,
): Promise<UploadResult>;
instantiate(
senderAddress: string,
codeId: number,
initMsg: Record<string, unknown>,
label: string,
options?: InstantiateOptions,
): Promise<InstantiateResult>;
updateAdmin(
senderAddress: string,
contractAddress: string,
newAdmin: string,
memo?: string,
): Promise<ChangeAdminResult>;
clearAdmin(senderAddress: string, contractAddress: string, memo?: string): Promise<ChangeAdminResult>;
migrate(
senderAddress: string,
contractAddress: string,
codeId: number,
migrateMsg: Record<string, unknown>,
memo?: string,
): Promise<MigrateResult>;
execute(
senderAddress: string,
contractAddress: string,
handleMsg: Record<string, unknown>,
memo?: string,
transferAmount?: readonly Coin[],
): Promise<ExecuteResult>;
sendTokens(
senderAddress: string,
recipientAddress: string,
transferAmount: readonly Coin[],
memo?: string,
): Promise<BroadcastTxResponse>;
signAndBroadcast(
address: string,
messages: readonly EncodeObject[],
fee: StdFee,
memo?: string,
): Promise<BroadcastTxResponse>;
}

View File

@ -0,0 +1,21 @@
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", "ERC20_ENABLED", "CW3_ENABLED", "CW1_ENABLED"]),
],
},
];

View File

@ -45,7 +45,7 @@
"@cosmjs/launchpad": "^0.24.0-alpha.10",
"@cosmjs/math": "^0.24.0-alpha.10",
"@cosmjs/utils": "^0.24.0-alpha.10",
"pako": "^1.0.11"
"pako": "^2.0.2"
},
"devDependencies": {
"@types/pako": "^1.0.1",

View File

@ -14,6 +14,8 @@ module.exports = [
path: distdir,
filename: "tests.js",
},
plugins: [new webpack.EnvironmentPlugin(["LAUNCHPAD_ENABLED", "ERC20_ENABLED"])],
plugins: [
new webpack.EnvironmentPlugin(["LAUNCHPAD_ENABLED", "ERC20_ENABLED", "CW3_ENABLED", "CW1_ENABLED"]),
],
},
];

View File

@ -41,7 +41,7 @@
"test": "yarn build-or-skip && yarn test-node",
"coverage": "nyc --reporter=text --reporter=lcov yarn test --quiet",
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js",
"preget-proto": "rm -rf proto",
"preget-proto": "shx rm -rf proto",
"get-proto": "REF=v0.40.0-rc3 ./scripts/get-proto.sh",
"predefine-proto": "./scripts/predefine-proto.sh",
"define-proto": "./scripts/define-proto.sh",

View File

@ -40,7 +40,7 @@
"test": "yarn build-or-skip && yarn test-node",
"coverage": "nyc --reporter=text --reporter=lcov yarn test --quiet",
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js",
"preget-proto": "rm -rf proto",
"preget-proto": "shx rm -rf proto",
"get-proto": "REF=v0.40.0-rc3 ./scripts/get-proto.sh",
"predefine-proto": "./scripts/predefine-proto.sh",
"define-proto": "./scripts/define-proto.sh",

View File

@ -13,7 +13,6 @@ import {
Base64,
Base64String,
DateTime,
DateTimeString,
dictionaryToStringMap,
Hex,
HexString,
@ -286,7 +285,7 @@ interface RpcHeader {
readonly version: RpcBlockVersion;
readonly chain_id: string;
readonly height: IntegerString;
readonly time: DateTimeString;
readonly time: string;
readonly num_txs: IntegerString;
readonly total_txs: IntegerString;
@ -382,7 +381,7 @@ function decodeBroadcastTxCommit(data: RpcBroadcastTxCommitResponse): responses.
type RpcSignature = {
readonly block_id_flag: number;
readonly validator_address: HexString;
readonly timestamp: DateTimeString;
readonly timestamp: string;
readonly signature: Base64String;
};
@ -436,7 +435,7 @@ function decodeValidatorGenesis(data: RpcValidatorGenesis): responses.Validator
}
interface RpcGenesisResponse {
readonly genesis_time: DateTimeString;
readonly genesis_time: string;
readonly chain_id: string;
readonly consensus_params: RpcConsensusParams;
// The validators key is used to specify a set of validators for testnets or PoA blockchains.
@ -519,7 +518,7 @@ interface RpcSyncInfo {
readonly latest_block_hash: HexString;
readonly latest_app_hash: HexString;
readonly latest_block_height: IntegerString;
readonly latest_block_time: DateTimeString;
readonly latest_block_time: string;
readonly catching_up: boolean;
}

View File

@ -1,8 +1,53 @@
import { ReadonlyDate } from "readonly-date";
import { encodeBlockId, encodeBytes, encodeInt, encodeString, encodeTime, encodeVersion } from "./encodings";
import {
DateTime,
encodeBlockId,
encodeBytes,
encodeInt,
encodeString,
encodeTime,
encodeVersion,
} from "./encodings";
import { ReadonlyDateWithNanoseconds } from "./responses";
describe("encodings", () => {
describe("DateTime", () => {
it("decodes a string", () => {
expect(DateTime.decode("2020-12-15T10:57:26.778Z").nanoseconds).toEqual(0);
expect(DateTime.decode("2020-12-15T10:57:26.7789Z").nanoseconds).toEqual(900000);
expect(DateTime.decode("2020-12-15T10:57:26.77809Z").nanoseconds).toEqual(90000);
expect(DateTime.decode("2020-12-15T10:57:26.778009Z").nanoseconds).toEqual(9000);
expect(DateTime.decode("2020-12-15T10:57:26.7780009Z").nanoseconds).toEqual(900);
expect(DateTime.decode("2020-12-15T10:57:26.77800009Z").nanoseconds).toEqual(90);
expect(DateTime.decode("2020-12-15T10:57:26.778000009Z").nanoseconds).toEqual(9);
});
it("encodes a string", () => {
const date1 = new ReadonlyDate("2020-12-15T10:57:26.778Z") as ReadonlyDateWithNanoseconds;
(date1 as any).nanoseconds = 0;
expect(DateTime.encode(date1)).toEqual("2020-12-15T10:57:26.778000000Z");
const date2 = new ReadonlyDate("2020-12-15T10:57:26.778Z") as ReadonlyDateWithNanoseconds;
(date2 as any).nanoseconds = 900000;
expect(DateTime.encode(date2)).toEqual("2020-12-15T10:57:26.778900000Z");
const date3 = new ReadonlyDate("2020-12-15T10:57:26.778Z") as ReadonlyDateWithNanoseconds;
(date3 as any).nanoseconds = 90000;
expect(DateTime.encode(date3)).toEqual("2020-12-15T10:57:26.778090000Z");
const date4 = new ReadonlyDate("2020-12-15T10:57:26.778Z") as ReadonlyDateWithNanoseconds;
(date4 as any).nanoseconds = 9000;
expect(DateTime.encode(date4)).toEqual("2020-12-15T10:57:26.778009000Z");
const date5 = new ReadonlyDate("2020-12-15T10:57:26.778Z") as ReadonlyDateWithNanoseconds;
(date5 as any).nanoseconds = 900;
expect(DateTime.encode(date5)).toEqual("2020-12-15T10:57:26.778000900Z");
const date6 = new ReadonlyDate("2020-12-15T10:57:26.778Z") as ReadonlyDateWithNanoseconds;
(date6 as any).nanoseconds = 90;
expect(DateTime.encode(date6)).toEqual("2020-12-15T10:57:26.778000090Z");
const date7 = new ReadonlyDate("2020-12-15T10:57:26.778Z") as ReadonlyDateWithNanoseconds;
(date7 as any).nanoseconds = 9;
expect(DateTime.encode(date7)).toEqual("2020-12-15T10:57:26.778000009Z");
});
});
describe("encodeString", () => {
it("works", () => {
expect(encodeString("")).toEqual(Uint8Array.from([0]));

View File

@ -7,7 +7,6 @@ import { BlockId, ReadonlyDateWithNanoseconds, Version } from "./responses";
export type Base64String = string & As<"base64">;
export type HexString = string & As<"hex">;
export type IntegerString = string & As<"integer">;
export type DateTimeString = string & As<"datetime">;
/**
* A runtime checker that ensures a given value is set (i.e. not undefined or null)
@ -172,13 +171,19 @@ export class Base64 {
}
export class DateTime {
public static decode(dateTimeString: DateTimeString): ReadonlyDateWithNanoseconds {
public static decode(dateTimeString: string): ReadonlyDateWithNanoseconds {
const readonlyDate = fromRfc3339(dateTimeString);
const nanosecondsMatch = dateTimeString.match(/\.(\d+)Z$/);
const nanoseconds = nanosecondsMatch ? nanosecondsMatch[1].slice(3) : "";
(readonlyDate as any).nanoseconds = parseInt(nanoseconds.padEnd(6, "0"), 10);
return readonlyDate as ReadonlyDateWithNanoseconds;
}
public static encode(dateTime: ReadonlyDateWithNanoseconds): string {
const millisecondIso = dateTime.toISOString();
const nanoseconds = dateTime.nanoseconds?.toString() ?? "";
return `${millisecondIso.slice(0, -1)}${nanoseconds.padStart(6, "0")}Z`;
}
}
export class Hex {

View File

@ -1,6 +1,7 @@
export { Adaptor } from "./adaptor";
export { adaptor33, adaptor34 } from "./adaptors";
export { Client } from "./client";
export { DateTime } from "./encodings";
export {
AbciInfoRequest,
AbciQueryParams,

View File

@ -3,7 +3,6 @@ import { BlockId, ReadonlyDateWithNanoseconds, Version } from "./responses";
export declare type Base64String = string & As<"base64">;
export declare type HexString = string & As<"hex">;
export declare type IntegerString = string & As<"integer">;
export declare type DateTimeString = string & As<"datetime">;
/**
* A runtime checker that ensures a given value is set (i.e. not undefined or null)
*
@ -67,7 +66,8 @@ export declare class Base64 {
static decode(base64String: Base64String): Uint8Array;
}
export declare class DateTime {
static decode(dateTimeString: DateTimeString): ReadonlyDateWithNanoseconds;
static decode(dateTimeString: string): ReadonlyDateWithNanoseconds;
static encode(dateTime: ReadonlyDateWithNanoseconds): string;
}
export declare class Hex {
static encode(data: Uint8Array): HexString;

View File

@ -1,6 +1,7 @@
export { Adaptor } from "./adaptor";
export { adaptor33, adaptor34 } from "./adaptors";
export { Client } from "./client";
export { DateTime } from "./encodings";
export {
AbciInfoRequest,
AbciQueryParams,

View File

@ -0,0 +1,76 @@
#!/usr/bin/env node
/* eslint-disable @typescript-eslint/naming-convention */
const { DirectSecp256k1HdWallet } = require("@cosmjs/proto-signing");
const { SigningCosmWasmClient } = require("@cosmjs/cosmwasm-stargate");
const fs = require("fs");
const endpoint = "http://localhost:26659";
const alice = {
mnemonic: "enlist hip relief stomach skate base shallow young switch frequent cry park",
address0: "wasm14qemq0vw6y3gc3u3e0aty2e764u4gs5lndxgyk",
address1: "wasm1hhg2rlu9jscacku2wwckws7932qqqu8xm5ca8y",
address2: "wasm1xv9tklw7d82sezh9haa573wufgy59vmwnxhnsl",
address3: "wasm17yg9mssjenmc3jkqth6ulcwj9cxujrxxg9nmzk",
address4: "wasm1f7j7ryulwjfe9ljplvhtcaxa6wqgula3nh873j",
};
const codeMeta = {
source: "https://crates.io/api/v1/crates/hackatom/not-yet-released/download",
builder: "cosmwasm/rust-optimizer:0.9.1",
};
const inits = [
{
label: "From deploy_hackatom.js (0)",
msg: {
beneficiary: alice.address0,
verifier: alice.address0,
},
admin: undefined,
},
{
label: "From deploy_hackatom.js (1)",
msg: {
beneficiary: alice.address1,
verifier: alice.address1,
},
admin: undefined,
},
{
label: "From deploy_hackatom.js (2)",
msg: {
beneficiary: alice.address2,
verifier: alice.address2,
},
admin: alice.address1,
},
];
async function main() {
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, "wasm");
const client = await SigningCosmWasmClient.connectWithWallet(endpoint, wallet);
const wasm = fs.readFileSync(__dirname + "/contracts/hackatom.wasm");
const uploadReceipt = await client.upload(alice.address0, wasm, codeMeta, "Upload hackatom contract");
console.info(`Upload succeeded. Receipt: ${JSON.stringify(uploadReceipt)}`);
for (const { label, msg, admin } of inits) {
const { contractAddress } = await client.instantiate(alice.address0, uploadReceipt.codeId, msg, label, {
memo: `Create a hackatom instance in deploy_hackatom.js`,
admin: admin,
});
console.info(`Contract instantiated at ${contractAddress}`);
}
}
main().then(
() => {
console.info("All done, let the coins flow.");
process.exit(0);
},
(error) => {
console.error(error);
process.exit(1);
},
);

View File

@ -5,7 +5,7 @@ command -v shellcheck >/dev/null && shellcheck "$0"
echo "Waiting for blockchain and REST server to be available ..."
timeout 60 bash -c "until curl -s http://localhost:1319/node_info > /dev/null; do sleep 0.5; done"
# The chain is unreliable in the first second of its existence (https://gist.github.com/webmaster128/8175692d4af5e6c572fddda7a9ef437c)
sleep 1
sleep 2
echo "Waiting for height to be >= 1 ..."
timeout 20 bash -c "until [ \"\$( curl -s http://localhost:1319/blocks/latest | jq -r '.block.header.height // 0' )\" -ge 1 ]; do sleep 0.5; done"
echo "Okay, thank you for your patience."
@ -25,7 +25,7 @@ SCRIPT_DIR="$(realpath "$(dirname "$0")")"
cd "$SCRIPT_DIR/contracts"
sha256sum --check checksums.sha256
)
# "$SCRIPT_DIR/deploy_hackatom.js"
"$SCRIPT_DIR/deploy_hackatom.js"
# "$SCRIPT_DIR/deploy_erc20.js"
# "$SCRIPT_DIR/deploy_cw3.js"
# "$SCRIPT_DIR/deploy_cw1.js"

View File

@ -6802,7 +6802,12 @@ package-hash@^4.0.0:
lodash.flattendeep "^4.4.0"
release-zalgo "^1.0.0"
pako@^1.0.11, pako@~1.0.5:
pako@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/pako/-/pako-2.0.2.tgz#8a72af7a93431ef22aa97e0d53b0acaa3c689220"
integrity sha512-9e8DRI3+dRLomCmMBAH30B2ejh+blwXr7VmMEx/pVFZlSDA7oyI8uKMhKXr8IrZpoxBF2YlxUvhqRXzTT1i0NA==
pako@~1.0.5:
version "1.0.11"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==