commit
bd05fc3f67
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "0.0.8",
|
||||
"version": "0.7.0-alpha.2",
|
||||
"useWorkspaces": true,
|
||||
"npmClient": "yarn"
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cosmwasm/bcp",
|
||||
"version": "0.0.8",
|
||||
"version": "0.7.0-alpha.2",
|
||||
"description": "Transaction codec and client to communicate with any wasmd blockchain",
|
||||
"author": "Ethan Frey <ethanfrey@users.noreply.github.com>",
|
||||
"license": "Apache-2.0",
|
||||
@ -38,7 +38,7 @@
|
||||
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cosmwasm/sdk": "^0.0.8",
|
||||
"@cosmwasm/sdk": "^0.7.0-alpha.2",
|
||||
"@iov/bcp": "^2.1.0",
|
||||
"@iov/crypto": "^2.1.0",
|
||||
"@iov/encoding": "^2.1.0",
|
||||
|
||||
@ -425,7 +425,7 @@ describe("CosmWasmConnection", () => {
|
||||
assert(isConfirmedTransaction(getResponse), "Expected transaction to succeed");
|
||||
assert(getResponse.log, "Log must be available");
|
||||
const [firstLog] = JSON.parse(getResponse.log);
|
||||
expect(firstLog.events.length).toEqual(1);
|
||||
expect(firstLog.events.length).toEqual(2);
|
||||
|
||||
const { transaction, signatures } = getResponse;
|
||||
assert(isSendTransaction(transaction), "Expected send transaction");
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cosmwasm/cli",
|
||||
"version": "0.0.8",
|
||||
"version": "0.7.0-alpha.2",
|
||||
"description": "Command line interface",
|
||||
"contributors": [
|
||||
"IOV SAS <admin@iov.one>",
|
||||
@ -36,7 +36,7 @@
|
||||
"!**/testdata/"
|
||||
],
|
||||
"dependencies": {
|
||||
"@cosmwasm/sdk": "^0.0.8",
|
||||
"@cosmwasm/sdk": "^0.7.0-alpha.2",
|
||||
"@iov/crypto": "^2.1.0",
|
||||
"@iov/encoding": "^2.1.0",
|
||||
"@iov/utils": "^2.0.2",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cosmwasm/faucet",
|
||||
"version": "0.0.8",
|
||||
"version": "0.7.0-alpha.2",
|
||||
"description": "The faucet",
|
||||
"author": "Ethan Frey <ethanfrey@users.noreply.github.com>",
|
||||
"license": "Apache-2.0",
|
||||
@ -35,10 +35,10 @@
|
||||
"test": "yarn build-or-skip && yarn test-node"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cosmwasm/bcp": "^0.0.8",
|
||||
"@cosmwasm/bcp": "^0.7.0-alpha.2",
|
||||
"@iov/bcp": "^2.1.0",
|
||||
"@iov/crypto": "^2.1.0",
|
||||
"@iov/encoding":"^2.1.0",
|
||||
"@iov/encoding": "^2.1.0",
|
||||
"@iov/keycontrol": "^2.1.0",
|
||||
"@iov/utils": "^2.0.2",
|
||||
"@koa/cors": "^3.0.0",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cosmwasm/sdk",
|
||||
"version": "0.0.8",
|
||||
"version": "0.7.0-alpha.2",
|
||||
"description": "CosmWasm SDK",
|
||||
"author": "Ethan Frey <ethanfrey@users.noreply.github.com>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@ -4,7 +4,7 @@ import { Bech32, Encoding } from "@iov/encoding";
|
||||
import { assert, sleep } from "@iov/utils";
|
||||
import { ReadonlyDate } from "readonly-date";
|
||||
|
||||
import { CosmWasmClient } from "./cosmwasmclient";
|
||||
import { Code, CosmWasmClient } from "./cosmwasmclient";
|
||||
import { makeSignBytes } from "./encoding";
|
||||
import { findAttribute } from "./logs";
|
||||
import { Secp256k1Pen } from "./pen";
|
||||
@ -20,7 +20,7 @@ import {
|
||||
} from "./testutils.spec";
|
||||
import { CosmosSdkTx, MsgSend, StdFee } from "./types";
|
||||
|
||||
const { fromAscii, fromHex, fromUtf8, toAscii } = Encoding;
|
||||
const { fromAscii, fromHex, fromUtf8, toAscii, toBase64 } = Encoding;
|
||||
|
||||
const httpUrl = "http://localhost:1317";
|
||||
|
||||
@ -406,7 +406,7 @@ describe("CosmWasmClient", () => {
|
||||
const [first] = result;
|
||||
expect(first).toEqual({
|
||||
id: 1,
|
||||
checksum: "b26861a6aa9858585ed905a590272735bd4fe8177c708940236224e8c9ff73ca",
|
||||
checksum: "aff8c8873d79d2153a8b9066a0683fec3c903669267eb806ffa831dcd4b3daae",
|
||||
source: undefined,
|
||||
builder: undefined,
|
||||
creator: faucet.address,
|
||||
@ -419,8 +419,19 @@ describe("CosmWasmClient", () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = new CosmWasmClient(httpUrl);
|
||||
const result = await client.getCodeDetails(1);
|
||||
const checksum = new Sha256(result.wasm).digest();
|
||||
expect(checksum).toEqual(fromHex("b26861a6aa9858585ed905a590272735bd4fe8177c708940236224e8c9ff73ca"));
|
||||
|
||||
const expectedInfo: Code = {
|
||||
id: 1,
|
||||
checksum: "aff8c8873d79d2153a8b9066a0683fec3c903669267eb806ffa831dcd4b3daae",
|
||||
source: undefined,
|
||||
builder: undefined,
|
||||
creator: faucet.address,
|
||||
};
|
||||
|
||||
// check info
|
||||
expect(result).toEqual(jasmine.objectContaining(expectedInfo));
|
||||
// check data
|
||||
expect(new Sha256(result.data).digest()).toEqual(fromHex(expectedInfo.checksum));
|
||||
});
|
||||
});
|
||||
|
||||
@ -430,67 +441,24 @@ describe("CosmWasmClient", () => {
|
||||
const client = new CosmWasmClient(httpUrl);
|
||||
const result = await client.getContracts(1);
|
||||
expect(result.length).toBeGreaterThanOrEqual(3);
|
||||
const [jade, hash, isa] = result;
|
||||
const [hash, isa, jade] = result;
|
||||
expect(hash).toEqual({
|
||||
address: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
|
||||
codeId: 1,
|
||||
creator: faucet.address,
|
||||
initMsg: {
|
||||
decimals: 5,
|
||||
name: "Hash token",
|
||||
symbol: "HASH",
|
||||
initial_balances: [
|
||||
{
|
||||
address: faucet.address,
|
||||
amount: "11",
|
||||
},
|
||||
{
|
||||
address: unused.address,
|
||||
amount: "12812345",
|
||||
},
|
||||
{
|
||||
address: guest.address,
|
||||
amount: "22004000000",
|
||||
},
|
||||
],
|
||||
},
|
||||
label: "HASH",
|
||||
});
|
||||
expect(isa).toEqual({
|
||||
address: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
|
||||
codeId: 1,
|
||||
creator: faucet.address,
|
||||
initMsg: {
|
||||
decimals: 0,
|
||||
name: "Isa Token",
|
||||
symbol: "ISA",
|
||||
initial_balances: [
|
||||
{
|
||||
address: faucet.address,
|
||||
amount: "999999999",
|
||||
},
|
||||
{
|
||||
address: unused.address,
|
||||
amount: "42",
|
||||
},
|
||||
],
|
||||
},
|
||||
label: "ISA",
|
||||
});
|
||||
expect(jade).toEqual({
|
||||
address: "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c",
|
||||
codeId: 1,
|
||||
creator: faucet.address,
|
||||
initMsg: {
|
||||
decimals: 18,
|
||||
name: "Jade Token",
|
||||
symbol: "JADE",
|
||||
initial_balances: [
|
||||
{
|
||||
address: faucet.address,
|
||||
amount: "189189189000000000000000000", // 189189189 JADE
|
||||
},
|
||||
{
|
||||
address: guest.address,
|
||||
amount: "189500000000000000000", // 189.5 JADE
|
||||
},
|
||||
],
|
||||
},
|
||||
label: "JADE",
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -501,8 +469,10 @@ describe("CosmWasmClient", () => {
|
||||
const client = new CosmWasmClient(httpUrl);
|
||||
const hash = await client.getContract("cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5");
|
||||
expect(hash).toEqual({
|
||||
address: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
|
||||
codeId: 1,
|
||||
creator: faucet.address,
|
||||
label: "HASH",
|
||||
initMsg: {
|
||||
decimals: 5,
|
||||
name: "Hash token",
|
||||
@ -538,7 +508,7 @@ describe("CosmWasmClient", () => {
|
||||
const client = new SigningCosmWasmClient(httpUrl, faucet.address, signBytes => pen.sign(signBytes));
|
||||
const { codeId } = await client.upload(getRandomizedHackatom());
|
||||
const initMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() };
|
||||
const contractAddress = await client.instantiate(codeId, initMsg);
|
||||
const contractAddress = await client.instantiate(codeId, initMsg, "random hackatom");
|
||||
contract = { initMsg: initMsg, address: contractAddress };
|
||||
}
|
||||
});
|
||||
@ -551,9 +521,9 @@ describe("CosmWasmClient", () => {
|
||||
const raw = await client.queryContractRaw(contract.address, configKey);
|
||||
assert(raw, "must get result");
|
||||
expect(JSON.parse(fromUtf8(raw))).toEqual({
|
||||
verifier: Array.from(Bech32.decode(contract.initMsg.verifier).data),
|
||||
beneficiary: Array.from(Bech32.decode(contract.initMsg.beneficiary).data),
|
||||
funder: Array.from(Bech32.decode(faucet.address).data),
|
||||
verifier: toBase64(Bech32.decode(contract.initMsg.verifier).data),
|
||||
beneficiary: toBase64(Bech32.decode(contract.initMsg.beneficiary).data),
|
||||
funder: toBase64(Bech32.decode(faucet.address).data),
|
||||
});
|
||||
});
|
||||
|
||||
@ -589,7 +559,7 @@ describe("CosmWasmClient", () => {
|
||||
const client = new SigningCosmWasmClient(httpUrl, faucet.address, signBytes => pen.sign(signBytes));
|
||||
const { codeId } = await client.upload(getRandomizedHackatom());
|
||||
const initMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() };
|
||||
const contractAddress = await client.instantiate(codeId, initMsg);
|
||||
const contractAddress = await client.instantiate(codeId, initMsg, "a different hackatom");
|
||||
contract = { initMsg: initMsg, address: contractAddress };
|
||||
}
|
||||
});
|
||||
|
||||
@ -58,16 +58,20 @@ export interface Code {
|
||||
readonly builder?: string;
|
||||
}
|
||||
|
||||
export interface CodeDetails {
|
||||
export interface CodeDetails extends Code {
|
||||
/** The original wasm bytes */
|
||||
readonly wasm: Uint8Array;
|
||||
readonly data: Uint8Array;
|
||||
}
|
||||
|
||||
export interface Contract {
|
||||
// TODO: add contract address (https://github.com/cosmwasm/wasmd/issues/75)
|
||||
readonly address: string;
|
||||
readonly codeId: number;
|
||||
/** Bech32 account address */
|
||||
readonly creator: string;
|
||||
readonly label: string;
|
||||
}
|
||||
|
||||
export interface ContractDetails extends Contract {
|
||||
/** Argument passed on initialization of the contract */
|
||||
readonly initMsg: object;
|
||||
}
|
||||
@ -192,36 +196,53 @@ export class CosmWasmClient {
|
||||
|
||||
public async getCodes(): Promise<readonly Code[]> {
|
||||
const result = await this.restClient.listCodeInfo();
|
||||
return result.map(r => ({
|
||||
id: r.id,
|
||||
creator: r.creator,
|
||||
checksum: Encoding.toHex(Encoding.fromHex(r.code_hash)),
|
||||
source: r.source || undefined,
|
||||
builder: r.builder || undefined,
|
||||
}));
|
||||
return result.map(
|
||||
(entry): Code => ({
|
||||
id: entry.id,
|
||||
creator: entry.creator,
|
||||
checksum: Encoding.toHex(Encoding.fromHex(entry.code_hash)),
|
||||
source: entry.source || undefined,
|
||||
builder: entry.builder || undefined,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public async getCodeDetails(codeId: number): Promise<CodeDetails> {
|
||||
const result = await this.restClient.getCode(codeId);
|
||||
// TODO: implement as one request when https://github.com/cosmwasm/wasmd/issues/90 is done
|
||||
const [codeInfos, getCodeResult] = await Promise.all([this.getCodes(), this.restClient.getCode(codeId)]);
|
||||
|
||||
const codeInfo = codeInfos.find(code => code.id === codeId);
|
||||
if (!codeInfo) throw new Error("No code info found");
|
||||
|
||||
return {
|
||||
wasm: result,
|
||||
...codeInfo,
|
||||
data: getCodeResult,
|
||||
};
|
||||
}
|
||||
|
||||
public async getContracts(codeId: number): Promise<readonly Contract[]> {
|
||||
const result = await this.restClient.listContractsByCodeId(codeId);
|
||||
return result.map(r => ({
|
||||
codeId: r.code_id,
|
||||
creator: r.creator,
|
||||
initMsg: r.init_msg,
|
||||
}));
|
||||
return result.map(
|
||||
(entry): Contract => ({
|
||||
address: entry.address,
|
||||
codeId: entry.code_id,
|
||||
creator: entry.creator,
|
||||
label: entry.label,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public async getContract(address: string): Promise<Contract> {
|
||||
/**
|
||||
* Throws an error if no contract was found at the address
|
||||
*/
|
||||
public async getContract(address: string): Promise<ContractDetails> {
|
||||
const result = await this.restClient.getContractInfo(address);
|
||||
if (!result) throw new Error(`No contract found at address "${address}"`);
|
||||
return {
|
||||
address: result.address,
|
||||
codeId: result.code_id,
|
||||
creator: result.creator,
|
||||
label: result.label,
|
||||
initMsg: result.init_msg,
|
||||
};
|
||||
}
|
||||
@ -234,7 +255,7 @@ export class CosmWasmClient {
|
||||
*/
|
||||
public async queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null> {
|
||||
// just test contract existence
|
||||
const _info = await this.restClient.getContractInfo(address);
|
||||
const _info = await this.getContract(address);
|
||||
|
||||
return this.restClient.queryContractRaw(address, key);
|
||||
}
|
||||
@ -250,7 +271,7 @@ export class CosmWasmClient {
|
||||
return await this.restClient.queryContractSmart(address, queryMsg);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
if (error.message === "not found: contract") {
|
||||
if (error.message.startsWith("not found: contract")) {
|
||||
throw new Error(`No contract found at address "${address}"`);
|
||||
} else {
|
||||
throw error;
|
||||
|
||||
@ -1,13 +1,22 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
import { isNonNullObject } from "@iov/encoding";
|
||||
|
||||
const supportedEventTypes: readonly string[] = ["message", "transfer", "wasm"];
|
||||
|
||||
export type SupportedEventType = "message" | "transfer" | "wasm";
|
||||
|
||||
export function isSupportedEventType(data: any): data is SupportedEventType {
|
||||
if (typeof data !== "string") return false;
|
||||
return supportedEventTypes.includes(data);
|
||||
}
|
||||
|
||||
export interface Attribute {
|
||||
readonly key: string;
|
||||
readonly value: string;
|
||||
}
|
||||
|
||||
export interface Event {
|
||||
readonly type: "message" | "transfer";
|
||||
readonly type: SupportedEventType;
|
||||
readonly attributes: readonly Attribute[];
|
||||
}
|
||||
|
||||
@ -34,7 +43,9 @@ export function parseAttribute(input: unknown): Attribute {
|
||||
export function parseEvent(input: unknown): Event {
|
||||
if (!isNonNullObject(input)) throw new Error("Event must be a non-null object");
|
||||
const { type, attributes } = input as any;
|
||||
if (type !== "message" && type !== "transfer") throw new Error("Event must be of type message or transfer");
|
||||
if (!isSupportedEventType(type)) {
|
||||
throw new Error(`Event type must be one of ${supportedEventTypes.join(", ")}; got ${type}`);
|
||||
}
|
||||
if (!Array.isArray(attributes)) throw new Error("Event's attributes must be an array");
|
||||
return {
|
||||
type: type,
|
||||
|
||||
@ -12,6 +12,7 @@ import { PostTxsResponse, RestClient, TxsResponse } from "./restclient";
|
||||
import { SigningCosmWasmClient } from "./signingcosmwasmclient";
|
||||
import cosmoshub from "./testdata/cosmoshub.json";
|
||||
import {
|
||||
bech32AddressMatcher,
|
||||
getRandomizedHackatom,
|
||||
makeRandomAddress,
|
||||
pendingWithoutWasmd,
|
||||
@ -108,6 +109,7 @@ async function instantiateContract(
|
||||
value: {
|
||||
sender: faucet.address,
|
||||
code_id: codeId.toString(),
|
||||
label: "my escrow",
|
||||
init_msg: {
|
||||
verifier: faucet.address,
|
||||
beneficiary: beneficiaryAddress,
|
||||
@ -143,7 +145,7 @@ async function executeContract(
|
||||
value: {
|
||||
sender: faucet.address,
|
||||
contract: contractAddress,
|
||||
msg: {},
|
||||
msg: { release: {} },
|
||||
sent_funds: [],
|
||||
},
|
||||
};
|
||||
@ -580,9 +582,15 @@ describe("RestClient", () => {
|
||||
{
|
||||
const result = await executeContract(client, pen, contractAddress);
|
||||
expect(result.code).toBeFalsy();
|
||||
// console.log("Raw log:", result.raw_log);
|
||||
const [firstLog] = parseLogs(result.logs);
|
||||
expect(firstLog.log).toEqual(`released funds to ${beneficiaryAddress}`);
|
||||
// console.log("Raw log:", result.logs);
|
||||
const logs = parseLogs(result.logs);
|
||||
const wasmEvent = logs.find(() => true)?.events.find(e => e.type === "wasm");
|
||||
assert(wasmEvent, "Event of type wasm expected");
|
||||
expect(wasmEvent.attributes).toContain({ key: "action", value: "release" });
|
||||
expect(wasmEvent.attributes).toContain({
|
||||
key: "destination",
|
||||
value: beneficiaryAddress,
|
||||
});
|
||||
|
||||
// Verify token transfer from contract to beneficiary
|
||||
const beneficiaryBalance = (await client.authAccounts(beneficiaryAddress)).result.value.coins;
|
||||
@ -662,9 +670,13 @@ describe("RestClient", () => {
|
||||
}
|
||||
|
||||
// create new instance and compare before and after
|
||||
const existingContracts = await client.listContractAddresses();
|
||||
const existingContractsByCode = await client.listContractsByCodeId(codeId);
|
||||
existingContractsByCode.forEach(ctc => expect(ctc.code_id).toEqual(codeId));
|
||||
for (const contract of existingContractsByCode) {
|
||||
expect(contract.address).toMatch(bech32AddressMatcher);
|
||||
expect(contract.code_id).toEqual(codeId);
|
||||
expect(contract.creator).toMatch(bech32AddressMatcher);
|
||||
expect(contract.label).toMatch(/^.+$/);
|
||||
}
|
||||
|
||||
const result = await instantiateContract(client, pen, codeId, beneficiaryAddress, transferAmount);
|
||||
expect(result.code).toBeFalsy();
|
||||
@ -672,32 +684,27 @@ describe("RestClient", () => {
|
||||
const contractAddressAttr = findAttribute(logs, "message", "contract_address");
|
||||
const myAddress = contractAddressAttr.value;
|
||||
|
||||
// ensure we were added to the list
|
||||
const newContracts = await client.listContractAddresses();
|
||||
expect(newContracts.length).toEqual(existingContracts.length + 1);
|
||||
// note: we are NOT guaranteed to be added to the end
|
||||
const diff = newContracts.filter(x => !existingContracts.includes(x));
|
||||
expect(diff.length).toEqual(1);
|
||||
const lastContract = diff[0];
|
||||
expect(lastContract).toEqual(myAddress);
|
||||
|
||||
// also by codeID list
|
||||
const newContractsByCode = await client.listContractsByCodeId(codeId);
|
||||
newContractsByCode.forEach(ctc => expect(ctc.code_id).toEqual(codeId));
|
||||
expect(newContractsByCode.length).toEqual(existingContractsByCode.length + 1);
|
||||
const newContract = newContractsByCode[newContractsByCode.length - 1];
|
||||
expect(newContract).toEqual(
|
||||
jasmine.objectContaining({
|
||||
code_id: codeId,
|
||||
creator: faucet.address,
|
||||
label: "my escrow",
|
||||
}),
|
||||
);
|
||||
|
||||
// check out info
|
||||
const myInfo = await client.getContractInfo(myAddress);
|
||||
assert(myInfo);
|
||||
expect(myInfo.code_id).toEqual(codeId);
|
||||
expect(myInfo.creator).toEqual(faucet.address);
|
||||
expect((myInfo.init_msg as any).beneficiary).toEqual(beneficiaryAddress);
|
||||
|
||||
// make sure random addresses don't give useful info
|
||||
const nonExistentAddress = makeRandomAddress();
|
||||
await client
|
||||
.getContractInfo(nonExistentAddress)
|
||||
.then(() => fail("this shouldn't succeed"))
|
||||
.catch(error => expect(error).toMatch(`No contract found at address "${nonExistentAddress}"`));
|
||||
expect(await client.getContractInfo(nonExistentAddress)).toBeNull();
|
||||
});
|
||||
|
||||
describe("contract state", () => {
|
||||
|
||||
@ -133,10 +133,16 @@ export interface CodeInfo {
|
||||
readonly builder?: string;
|
||||
}
|
||||
|
||||
// This is list view, without contract info
|
||||
export interface ContractInfo {
|
||||
readonly address: string;
|
||||
readonly code_id: number;
|
||||
/** Bech32 account address */
|
||||
readonly creator: string;
|
||||
readonly label: string;
|
||||
}
|
||||
|
||||
export interface ContractDetails extends ContractInfo {
|
||||
/** Argument passed on initialization of the contract */
|
||||
readonly init_msg: object;
|
||||
}
|
||||
@ -160,6 +166,9 @@ type RestClientResponse =
|
||||
| PostTxsResponse
|
||||
| EncodeTxResponse
|
||||
| WasmResponse<string>
|
||||
| WasmResponse<CodeInfo[]>
|
||||
| WasmResponse<ContractInfo[] | null>
|
||||
| WasmResponse<ContractDetails>
|
||||
| WasmResponse<GetCodeResult>;
|
||||
|
||||
/**
|
||||
@ -187,28 +196,23 @@ function unwrapWasmResponse<T>(response: WasmResponse<T>): T {
|
||||
return response.result;
|
||||
}
|
||||
|
||||
function parseWasmResponse(response: WasmResponse<string>): any {
|
||||
if (isWasmError(response)) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
return JSON.parse(response.result);
|
||||
}
|
||||
|
||||
// We want to get message data from 500 errors
|
||||
// https://stackoverflow.com/questions/56577124/how-to-handle-500-error-message-with-axios
|
||||
// this should be chained to catch one error and throw a more informative one
|
||||
function parseAxios500error(err: AxiosError): never {
|
||||
function parseAxiosError(err: AxiosError): never {
|
||||
// use the error message sent from server, not default 500 msg
|
||||
if (err.response?.data) {
|
||||
let errorText: string;
|
||||
const data = err.response.data;
|
||||
// expect { error: string }, but otherwise dump
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
if (data.error && typeof data.error === "string") {
|
||||
errorText = data.error;
|
||||
} else if (typeof data === "string") {
|
||||
throw new Error(data);
|
||||
errorText = data;
|
||||
} else {
|
||||
throw new Error(JSON.stringify(data));
|
||||
errorText = JSON.stringify(data);
|
||||
}
|
||||
throw new Error(`${errorText} (HTTP ${err.response.status})`);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
@ -230,7 +234,7 @@ export class RestClient {
|
||||
}
|
||||
|
||||
public async get(path: string): Promise<RestClientResponse> {
|
||||
const { data } = await this.client.get(path).catch(parseAxios500error);
|
||||
const { data } = await this.client.get(path).catch(parseAxiosError);
|
||||
if (data === null) {
|
||||
throw new Error("Received null response from server");
|
||||
}
|
||||
@ -238,7 +242,7 @@ export class RestClient {
|
||||
}
|
||||
|
||||
public async post(path: string, params: PostTxsParams): Promise<RestClientResponse> {
|
||||
const { data } = await this.client.post(path, params).catch(parseAxios500error);
|
||||
const { data } = await this.client.post(path, params).catch(parseAxiosError);
|
||||
if (data === null) {
|
||||
throw new Error("Received null response from server");
|
||||
}
|
||||
@ -335,9 +339,8 @@ export class RestClient {
|
||||
// wasm rest queries are listed here: https://github.com/cosmwasm/wasmd/blob/master/x/wasm/client/rest/query.go#L19-L27
|
||||
public async listCodeInfo(): Promise<readonly CodeInfo[]> {
|
||||
const path = `/wasm/code`;
|
||||
const responseData = await this.get(path);
|
||||
// answer may be null (empty array)
|
||||
return parseWasmResponse(responseData as WasmResponse) || [];
|
||||
const responseData = (await this.get(path)) as WasmResponse<CodeInfo[]>;
|
||||
return unwrapWasmResponse(responseData);
|
||||
}
|
||||
|
||||
// this will download the original wasm bytecode by code id
|
||||
@ -349,32 +352,32 @@ export class RestClient {
|
||||
return fromBase64(code);
|
||||
}
|
||||
|
||||
public async listContractAddresses(): Promise<readonly string[]> {
|
||||
const path = `/wasm/contract`;
|
||||
const responseData = await this.get(path);
|
||||
// answer may be null (go's encoding of empty array)
|
||||
const addresses: string[] | null = parseWasmResponse(responseData as WasmResponse);
|
||||
return addresses || [];
|
||||
}
|
||||
|
||||
public async listContractsByCodeId(id: number): Promise<readonly ContractInfo[]> {
|
||||
const path = `/wasm/code/${id}/contracts`;
|
||||
const responseData = await this.get(path);
|
||||
// answer may be null (go's encoding of empty array)
|
||||
const contracts: ContractInfo[] | null = parseWasmResponse(responseData as WasmResponse);
|
||||
return contracts || [];
|
||||
const responseData = (await this.get(path)) as WasmResponse<ContractInfo[] | null>;
|
||||
return unwrapWasmResponse(responseData) || [];
|
||||
}
|
||||
|
||||
// throws error if no contract at this address
|
||||
public async getContractInfo(address: string): Promise<ContractInfo> {
|
||||
/**
|
||||
* Returns null when contract was not found at this address.
|
||||
*/
|
||||
public async getContractInfo(address: string): Promise<ContractDetails | null> {
|
||||
const path = `/wasm/contract/${address}`;
|
||||
const responseData = await this.get(path);
|
||||
// rest server returns null if no data for the address
|
||||
const info: ContractInfo | null = parseWasmResponse(responseData as WasmResponse);
|
||||
if (!info) {
|
||||
throw new Error(`No contract found at address "${address}"`);
|
||||
|
||||
try {
|
||||
const response = (await this.get(path)) as WasmResponse<ContractDetails>;
|
||||
return unwrapWasmResponse(response);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
if (error.message.startsWith("unknown address:")) {
|
||||
return null;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
// Returns all contract state.
|
||||
|
||||
@ -76,7 +76,8 @@ describe("SigningCosmWasmClient", () => {
|
||||
verifier: faucet.address,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
"Let's see",
|
||||
"My cool label",
|
||||
"Let's see if the memo is used",
|
||||
transferAmount,
|
||||
);
|
||||
|
||||
@ -91,14 +92,22 @@ describe("SigningCosmWasmClient", () => {
|
||||
const client = new SigningCosmWasmClient(httpUrl, faucet.address, signBytes => pen.sign(signBytes));
|
||||
const { codeId } = await client.upload(getRandomizedHackatom());
|
||||
|
||||
const contractAddress1 = await client.instantiate(codeId, {
|
||||
verifier: faucet.address,
|
||||
beneficiary: makeRandomAddress(),
|
||||
});
|
||||
const contractAddress2 = await client.instantiate(codeId, {
|
||||
verifier: faucet.address,
|
||||
beneficiary: makeRandomAddress(),
|
||||
});
|
||||
const contractAddress1 = await client.instantiate(
|
||||
codeId,
|
||||
{
|
||||
verifier: faucet.address,
|
||||
beneficiary: makeRandomAddress(),
|
||||
},
|
||||
"contract 1",
|
||||
);
|
||||
const contractAddress2 = await client.instantiate(
|
||||
codeId,
|
||||
{
|
||||
verifier: faucet.address,
|
||||
beneficiary: makeRandomAddress(),
|
||||
},
|
||||
"contract 2",
|
||||
);
|
||||
expect(contractAddress1).not.toEqual(contractAddress2);
|
||||
});
|
||||
});
|
||||
@ -128,14 +137,20 @@ describe("SigningCosmWasmClient", () => {
|
||||
verifier: faucet.address,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
"amazing random contract",
|
||||
undefined,
|
||||
transferAmount,
|
||||
);
|
||||
|
||||
// execute
|
||||
const result = await client.execute(contractAddress, {}, undefined);
|
||||
const [firstLog] = result.logs;
|
||||
expect(firstLog.log).toEqual(`released funds to ${beneficiaryAddress}`);
|
||||
const result = await client.execute(contractAddress, { release: {} }, undefined);
|
||||
const wasmEvent = result.logs.find(() => true)?.events.find(e => e.type === "wasm");
|
||||
assert(wasmEvent, "Event of type wasm expected");
|
||||
expect(wasmEvent.attributes).toContain({ key: "action", value: "release" });
|
||||
expect(wasmEvent.attributes).toContain({
|
||||
key: "destination",
|
||||
value: beneficiaryAddress,
|
||||
});
|
||||
|
||||
// Verify token transfer from contract to beneficiary
|
||||
const rest = new RestClient(httpUrl);
|
||||
|
||||
@ -134,6 +134,7 @@ export class SigningCosmWasmClient extends CosmWasmClient {
|
||||
public async instantiate(
|
||||
codeId: number,
|
||||
initMsg: object,
|
||||
label: string,
|
||||
memo = "",
|
||||
transferAmount?: readonly Coin[],
|
||||
): Promise<string> {
|
||||
@ -143,6 +144,7 @@ export class SigningCosmWasmClient extends CosmWasmClient {
|
||||
sender: this.senderAddress,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
code_id: codeId.toString(),
|
||||
label: label,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
init_msg: initMsg,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
|
||||
2
packages/sdk/src/testdata/contract.json
vendored
2
packages/sdk/src/testdata/contract.json
vendored
File diff suppressed because one or more lines are too long
@ -27,17 +27,18 @@ export function leb128Encode(uint: number): Uint8Array {
|
||||
|
||||
export function getRandomizedHackatom(): Uint8Array {
|
||||
const data = Encoding.fromBase64(hackatom.data);
|
||||
|
||||
// The return value of the export function cosmwasm_api_0_6 is unused and
|
||||
// can be randomized for testing.
|
||||
//
|
||||
// Find position of mutable bytes as follows:
|
||||
// $ wasm-objdump -d contract.wasm | grep -F "cosmwasm_api_0_6" -A 1
|
||||
// 00e67c func[149] <cosmwasm_api_0_6>:
|
||||
// 00e67d: 41 83 0c | i32.const 1539
|
||||
// 0136d2 func[198] <cosmwasm_api_0_6>:
|
||||
// 0136d3: 41 83 0c | i32.const 1539
|
||||
//
|
||||
// In the last line, the addresses 00e67d-00e67f hold a one byte instruction
|
||||
// (https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#constants-described-here)
|
||||
// and a two byte value (leb128 encoded 1539)
|
||||
// In the last line, the addresses [0136d3, 0136d3+1, 0136d3+2] hold a one byte instruction
|
||||
// and a two byte value (leb128 encoded 1539). See also
|
||||
// https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#constants-described-here.
|
||||
|
||||
// Any unsigned integer from 128 to 16383 is encoded to two leb128 bytes
|
||||
const min = 128;
|
||||
@ -45,8 +46,8 @@ export function getRandomizedHackatom(): Uint8Array {
|
||||
const random = Math.floor(Math.random() * (max - min)) + min;
|
||||
const bytes = leb128Encode(random);
|
||||
|
||||
data[0x00e67d + 1] = bytes[0];
|
||||
data[0x00e67d + 2] = bytes[1];
|
||||
data[0x0136d3 + 1] = bytes[0];
|
||||
data[0x0136d3 + 2] = bytes[1];
|
||||
|
||||
return data;
|
||||
}
|
||||
@ -59,6 +60,9 @@ export const tendermintIdMatcher = /^[0-9A-F]{64}$/;
|
||||
export const tendermintOptionalIdMatcher = /^([0-9A-F]{64}|)$/;
|
||||
export const tendermintAddressMatcher = /^[0-9A-F]{40}$/;
|
||||
|
||||
// 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 function wasmdEnabled(): boolean {
|
||||
return !!process.env.WASMD_ENABLED;
|
||||
}
|
||||
|
||||
@ -70,6 +70,8 @@ export interface MsgInstantiateContract extends MsgTemplate {
|
||||
readonly sender: string;
|
||||
/** ID of the Wasm code that was uploaded before */
|
||||
readonly code_id: string;
|
||||
/** Human-readable label for this contract */
|
||||
readonly label: string;
|
||||
/** Init message as JavaScript object */
|
||||
readonly init_msg: object;
|
||||
readonly init_funds: ReadonlyArray<Coin>;
|
||||
|
||||
13
packages/sdk/types/cosmwasmclient.d.ts
vendored
13
packages/sdk/types/cosmwasmclient.d.ts
vendored
@ -34,14 +34,18 @@ export interface Code {
|
||||
readonly source?: string;
|
||||
readonly builder?: string;
|
||||
}
|
||||
export interface CodeDetails {
|
||||
export interface CodeDetails extends Code {
|
||||
/** The original wasm bytes */
|
||||
readonly wasm: Uint8Array;
|
||||
readonly data: Uint8Array;
|
||||
}
|
||||
export interface Contract {
|
||||
readonly address: string;
|
||||
readonly codeId: number;
|
||||
/** Bech32 account address */
|
||||
readonly creator: string;
|
||||
readonly label: string;
|
||||
}
|
||||
export interface ContractDetails extends Contract {
|
||||
/** Argument passed on initialization of the contract */
|
||||
readonly initMsg: object;
|
||||
}
|
||||
@ -73,7 +77,10 @@ export declare class CosmWasmClient {
|
||||
getCodes(): Promise<readonly Code[]>;
|
||||
getCodeDetails(codeId: number): Promise<CodeDetails>;
|
||||
getContracts(codeId: number): Promise<readonly Contract[]>;
|
||||
getContract(address: string): Promise<Contract>;
|
||||
/**
|
||||
* Throws an error if no contract was found at the address
|
||||
*/
|
||||
getContract(address: string): Promise<ContractDetails>;
|
||||
/**
|
||||
* Returns the data at the key if present (raw contract dependent storage data)
|
||||
* or null if no data at this key.
|
||||
|
||||
4
packages/sdk/types/logs.d.ts
vendored
4
packages/sdk/types/logs.d.ts
vendored
@ -1,9 +1,11 @@
|
||||
export declare type SupportedEventType = "message" | "transfer" | "wasm";
|
||||
export declare function isSupportedEventType(data: any): data is SupportedEventType;
|
||||
export interface Attribute {
|
||||
readonly key: string;
|
||||
readonly value: string;
|
||||
}
|
||||
export interface Event {
|
||||
readonly type: "message" | "transfer";
|
||||
readonly type: SupportedEventType;
|
||||
readonly attributes: readonly Attribute[];
|
||||
}
|
||||
export interface Log {
|
||||
|
||||
13
packages/sdk/types/restclient.d.ts
vendored
13
packages/sdk/types/restclient.d.ts
vendored
@ -102,9 +102,13 @@ export interface CodeInfo {
|
||||
readonly builder?: string;
|
||||
}
|
||||
export interface ContractInfo {
|
||||
readonly address: string;
|
||||
readonly code_id: number;
|
||||
/** Bech32 account address */
|
||||
readonly creator: string;
|
||||
readonly label: string;
|
||||
}
|
||||
export interface ContractDetails extends ContractInfo {
|
||||
/** Argument passed on initialization of the contract */
|
||||
readonly init_msg: object;
|
||||
}
|
||||
@ -120,6 +124,9 @@ declare type RestClientResponse =
|
||||
| PostTxsResponse
|
||||
| EncodeTxResponse
|
||||
| WasmResponse<string>
|
||||
| WasmResponse<CodeInfo[]>
|
||||
| WasmResponse<ContractInfo[] | null>
|
||||
| WasmResponse<ContractDetails>
|
||||
| WasmResponse<GetCodeResult>;
|
||||
/**
|
||||
* The mode used to send transaction
|
||||
@ -158,9 +165,11 @@ export declare class RestClient {
|
||||
postTx(tx: StdTx): Promise<PostTxsResponse>;
|
||||
listCodeInfo(): Promise<readonly CodeInfo[]>;
|
||||
getCode(id: number): Promise<Uint8Array>;
|
||||
listContractAddresses(): Promise<readonly string[]>;
|
||||
listContractsByCodeId(id: number): Promise<readonly ContractInfo[]>;
|
||||
getContractInfo(address: string): Promise<ContractInfo>;
|
||||
/**
|
||||
* Returns null when contract was not found at this address.
|
||||
*/
|
||||
getContractInfo(address: string): Promise<ContractDetails | null>;
|
||||
getAllContractState(address: string): Promise<readonly Model[]>;
|
||||
queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null>;
|
||||
queryContractSmart(address: string, query: object): Promise<Uint8Array>;
|
||||
|
||||
@ -44,6 +44,7 @@ export declare class SigningCosmWasmClient extends CosmWasmClient {
|
||||
instantiate(
|
||||
codeId: number,
|
||||
initMsg: object,
|
||||
label: string,
|
||||
memo?: string,
|
||||
transferAmount?: readonly Coin[],
|
||||
): Promise<string>;
|
||||
|
||||
2
packages/sdk/types/types.d.ts
vendored
2
packages/sdk/types/types.d.ts
vendored
@ -55,6 +55,8 @@ export interface MsgInstantiateContract extends MsgTemplate {
|
||||
readonly sender: string;
|
||||
/** ID of the Wasm code that was uploaded before */
|
||||
readonly code_id: string;
|
||||
/** Human-readable label for this contract */
|
||||
readonly label: string;
|
||||
/** Init message as JavaScript object */
|
||||
readonly init_msg: object;
|
||||
readonly init_funds: ReadonlyArray<Coin>;
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
b26861a6aa9858585ed905a590272735bd4fe8177c708940236224e8c9ff73ca cw-erc20.wasm
|
||||
66128c5c0cd38e6b908b03d41eba8c9e9befb79b34683e334d7a8bbe74d5df41 cw-nameservice.wasm
|
||||
aff8c8873d79d2153a8b9066a0683fec3c903669267eb806ffa831dcd4b3daae cw-erc20.wasm
|
||||
8427fcbf9e6c15d0e0dea0afe75640d997a6854d03c3aa1c8bdf1a9787a35681 cw-nameservice.wasm
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -77,7 +77,7 @@ async function main() {
|
||||
|
||||
for (const initMsg of [initMsgHash, initMsgIsa, initMsgJade]) {
|
||||
const memo = `Create an ERC20 instance for ${initMsg.symbol}`;
|
||||
const contractAddress = await client.instantiate(uploadReceipt.codeId, initMsg, memo);
|
||||
const contractAddress = await client.instantiate(uploadReceipt.codeId, initMsg, initMsg.symbol, memo);
|
||||
console.info(`Contract instantiated for ${initMsg.symbol} at ${contractAddress}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ async function main() {
|
||||
|
||||
for (const initMsg of [initFree, initLuxury]) {
|
||||
const memo = `Create an nameservice instance for ${initMsg.name}`;
|
||||
const contractAddress = await client.instantiate(uploadReceipt.codeId, initMsg, memo);
|
||||
const contractAddress = await client.instantiate(uploadReceipt.codeId, initMsg, initMsg.name, memo);
|
||||
console.info(`Contract instantiated for ${initMsg.name} at ${contractAddress}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# Choose from https://hub.docker.com/r/cosmwasm/wasmd-demo/tags
|
||||
REPOSITORY="cosmwasm/wasmd-demo"
|
||||
VERSION="v0.6.1"
|
||||
VERSION="v0.7.0-rc1"
|
||||
|
||||
CONTAINER_NAME="wasmd"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user