Create Wasm module for LCD
This commit is contained in:
parent
ab4bbb7d28
commit
b352e942ab
510
packages/cosmwasm/src/lcdapi/wasm.spec.ts
Normal file
510
packages/cosmwasm/src/lcdapi/wasm.spec.ts
Normal file
@ -0,0 +1,510 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
import { Sha256 } from "@cosmjs/crypto";
|
||||
import { Bech32, fromAscii, fromBase64, fromHex, toAscii, toBase64, toHex } from "@cosmjs/encoding";
|
||||
import {
|
||||
Coin,
|
||||
coin,
|
||||
coins,
|
||||
LcdClient,
|
||||
makeSignBytes,
|
||||
Pen,
|
||||
PostTxsResponse,
|
||||
Secp256k1Pen,
|
||||
StdFee,
|
||||
} from "@cosmjs/sdk38";
|
||||
import { assert } from "@cosmjs/utils";
|
||||
|
||||
import { findAttribute, parseLogs } from "../logs";
|
||||
import {
|
||||
isMsgInstantiateContract,
|
||||
isMsgStoreCode,
|
||||
MsgExecuteContract,
|
||||
MsgInstantiateContract,
|
||||
MsgStoreCode,
|
||||
} from "../msgs";
|
||||
import {
|
||||
alice,
|
||||
bech32AddressMatcher,
|
||||
ContractUploadInstructions,
|
||||
deployedErc20,
|
||||
fromOneElementArray,
|
||||
getHackatom,
|
||||
makeRandomAddress,
|
||||
makeSignedTx,
|
||||
pendingWithoutWasmd,
|
||||
wasmd,
|
||||
wasmdEnabled,
|
||||
} from "../testutils.spec";
|
||||
import { setupWasmModule, WasmModule } from "./wasm";
|
||||
|
||||
type WasmClient = LcdClient & WasmModule;
|
||||
|
||||
function makeWasmClient(apiUrl: string): WasmClient {
|
||||
return LcdClient.withModules({ apiUrl }, setupWasmModule);
|
||||
}
|
||||
|
||||
async function uploadContract(
|
||||
client: WasmClient,
|
||||
pen: Pen,
|
||||
contract: ContractUploadInstructions,
|
||||
): Promise<PostTxsResponse> {
|
||||
const memo = "My first contract on chain";
|
||||
const theMsg: MsgStoreCode = {
|
||||
type: "wasm/store-code",
|
||||
value: {
|
||||
sender: alice.address0,
|
||||
wasm_byte_code: toBase64(contract.data),
|
||||
source: contract.source || "",
|
||||
builder: contract.builder || "",
|
||||
},
|
||||
};
|
||||
const fee: StdFee = {
|
||||
amount: [
|
||||
{
|
||||
amount: "5000000",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
gas: "89000000",
|
||||
};
|
||||
|
||||
const { account_number, sequence } = (await client.authAccounts(alice.address0)).result.value;
|
||||
const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence);
|
||||
const signature = await pen.sign(signBytes);
|
||||
const signedTx = makeSignedTx(theMsg, fee, memo, signature);
|
||||
return client.postTx(signedTx);
|
||||
}
|
||||
|
||||
async function instantiateContract(
|
||||
client: WasmClient,
|
||||
pen: Pen,
|
||||
codeId: number,
|
||||
beneficiaryAddress: string,
|
||||
transferAmount?: readonly Coin[],
|
||||
): Promise<PostTxsResponse> {
|
||||
const memo = "Create an escrow instance";
|
||||
const theMsg: MsgInstantiateContract = {
|
||||
type: "wasm/instantiate",
|
||||
value: {
|
||||
sender: alice.address0,
|
||||
code_id: codeId.toString(),
|
||||
label: "my escrow",
|
||||
init_msg: {
|
||||
verifier: alice.address0,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
init_funds: transferAmount || [],
|
||||
},
|
||||
};
|
||||
const fee: StdFee = {
|
||||
amount: [
|
||||
{
|
||||
amount: "5000000",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
gas: "89000000",
|
||||
};
|
||||
|
||||
const { account_number, sequence } = (await client.authAccounts(alice.address0)).result.value;
|
||||
const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence);
|
||||
const signature = await pen.sign(signBytes);
|
||||
const signedTx = makeSignedTx(theMsg, fee, memo, signature);
|
||||
return client.postTx(signedTx);
|
||||
}
|
||||
|
||||
async function executeContract(
|
||||
client: WasmClient,
|
||||
pen: Pen,
|
||||
contractAddress: string,
|
||||
msg: object,
|
||||
): Promise<PostTxsResponse> {
|
||||
const memo = "Time for action";
|
||||
const theMsg: MsgExecuteContract = {
|
||||
type: "wasm/execute",
|
||||
value: {
|
||||
sender: alice.address0,
|
||||
contract: contractAddress,
|
||||
msg: msg,
|
||||
sent_funds: [],
|
||||
},
|
||||
};
|
||||
const fee: StdFee = {
|
||||
amount: coins(5000000, "ucosm"),
|
||||
gas: "89000000",
|
||||
};
|
||||
|
||||
const { account_number, sequence } = (await client.authAccounts(alice.address0)).result.value;
|
||||
const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence);
|
||||
const signature = await pen.sign(signBytes);
|
||||
const signedTx = makeSignedTx(theMsg, fee, memo, signature);
|
||||
return client.postTx(signedTx);
|
||||
}
|
||||
|
||||
describe("wasm", () => {
|
||||
it("can be constructed", () => {
|
||||
const client = makeWasmClient(wasmd.endpoint);
|
||||
expect(client).toBeTruthy();
|
||||
});
|
||||
|
||||
describe("txsQuery", () => {
|
||||
it("can query by tags (module + code_id)", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = makeWasmClient(wasmd.endpoint);
|
||||
const result = await client.txsQuery(`message.module=wasm&message.code_id=${deployedErc20.codeId}`);
|
||||
expect(parseInt(result.count, 10)).toBeGreaterThanOrEqual(4);
|
||||
|
||||
// Check first 4 results
|
||||
const [store, hash, isa, jade] = result.txs.map((tx) => fromOneElementArray(tx.tx.value.msg));
|
||||
assert(isMsgStoreCode(store));
|
||||
assert(isMsgInstantiateContract(hash));
|
||||
assert(isMsgInstantiateContract(isa));
|
||||
assert(isMsgInstantiateContract(jade));
|
||||
expect(store.value).toEqual(
|
||||
jasmine.objectContaining({
|
||||
sender: alice.address0,
|
||||
source: deployedErc20.source,
|
||||
builder: deployedErc20.builder,
|
||||
}),
|
||||
);
|
||||
expect(hash.value).toEqual({
|
||||
code_id: deployedErc20.codeId.toString(),
|
||||
init_funds: [],
|
||||
init_msg: jasmine.objectContaining({
|
||||
symbol: "HASH",
|
||||
}),
|
||||
label: "HASH",
|
||||
sender: alice.address0,
|
||||
});
|
||||
expect(isa.value).toEqual({
|
||||
code_id: deployedErc20.codeId.toString(),
|
||||
init_funds: [],
|
||||
init_msg: jasmine.objectContaining({ symbol: "ISA" }),
|
||||
label: "ISA",
|
||||
sender: alice.address0,
|
||||
});
|
||||
expect(jade.value).toEqual({
|
||||
code_id: deployedErc20.codeId.toString(),
|
||||
init_funds: [],
|
||||
init_msg: jasmine.objectContaining({ symbol: "JADE" }),
|
||||
label: "JADE",
|
||||
sender: alice.address0,
|
||||
admin: alice.address1,
|
||||
});
|
||||
});
|
||||
|
||||
// Like previous test but filtered by message.action=store-code and message.action=instantiate
|
||||
it("can query by tags (module + code_id + action)", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const client = makeWasmClient(wasmd.endpoint);
|
||||
|
||||
{
|
||||
const uploads = await client.txsQuery(
|
||||
`message.module=wasm&message.code_id=${deployedErc20.codeId}&message.action=store-code`,
|
||||
);
|
||||
expect(parseInt(uploads.count, 10)).toEqual(1);
|
||||
const store = fromOneElementArray(uploads.txs[0].tx.value.msg);
|
||||
assert(isMsgStoreCode(store));
|
||||
expect(store.value).toEqual(
|
||||
jasmine.objectContaining({
|
||||
sender: alice.address0,
|
||||
source: deployedErc20.source,
|
||||
builder: deployedErc20.builder,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const instantiations = await client.txsQuery(
|
||||
`message.module=wasm&message.code_id=${deployedErc20.codeId}&message.action=instantiate`,
|
||||
);
|
||||
expect(parseInt(instantiations.count, 10)).toBeGreaterThanOrEqual(3);
|
||||
const [hash, isa, jade] = instantiations.txs.map((tx) => fromOneElementArray(tx.tx.value.msg));
|
||||
assert(isMsgInstantiateContract(hash));
|
||||
assert(isMsgInstantiateContract(isa));
|
||||
assert(isMsgInstantiateContract(jade));
|
||||
expect(hash.value).toEqual({
|
||||
code_id: deployedErc20.codeId.toString(),
|
||||
init_funds: [],
|
||||
init_msg: jasmine.objectContaining({
|
||||
symbol: "HASH",
|
||||
}),
|
||||
label: "HASH",
|
||||
sender: alice.address0,
|
||||
});
|
||||
expect(isa.value).toEqual({
|
||||
code_id: deployedErc20.codeId.toString(),
|
||||
init_funds: [],
|
||||
init_msg: jasmine.objectContaining({ symbol: "ISA" }),
|
||||
label: "ISA",
|
||||
sender: alice.address0,
|
||||
});
|
||||
expect(jade.value).toEqual({
|
||||
code_id: deployedErc20.codeId.toString(),
|
||||
init_funds: [],
|
||||
init_msg: jasmine.objectContaining({ symbol: "JADE" }),
|
||||
label: "JADE",
|
||||
sender: alice.address0,
|
||||
admin: alice.address1,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("postTx", () => {
|
||||
it("can upload, instantiate and execute wasm", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic);
|
||||
const client = makeWasmClient(wasmd.endpoint);
|
||||
|
||||
const transferAmount = [coin(1234, "ucosm"), coin(321, "ustake")];
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
|
||||
let codeId: number;
|
||||
|
||||
// upload
|
||||
{
|
||||
// console.log("Raw log:", result.raw_log);
|
||||
const result = await uploadContract(client, pen, getHackatom());
|
||||
expect(result.code).toBeFalsy();
|
||||
const logs = parseLogs(result.logs);
|
||||
const codeIdAttr = findAttribute(logs, "message", "code_id");
|
||||
codeId = Number.parseInt(codeIdAttr.value, 10);
|
||||
expect(codeId).toBeGreaterThanOrEqual(1);
|
||||
expect(codeId).toBeLessThanOrEqual(200);
|
||||
expect(result.data).toEqual(toHex(toAscii(`${codeId}`)).toUpperCase());
|
||||
}
|
||||
|
||||
let contractAddress: string;
|
||||
|
||||
// instantiate
|
||||
{
|
||||
const result = await instantiateContract(client, pen, codeId, beneficiaryAddress, transferAmount);
|
||||
expect(result.code).toBeFalsy();
|
||||
// console.log("Raw log:", result.raw_log);
|
||||
const logs = parseLogs(result.logs);
|
||||
const contractAddressAttr = findAttribute(logs, "message", "contract_address");
|
||||
contractAddress = contractAddressAttr.value;
|
||||
const amountAttr = findAttribute(logs, "transfer", "amount");
|
||||
expect(amountAttr.value).toEqual("1234ucosm,321ustake");
|
||||
expect(result.data).toEqual(toHex(Bech32.decode(contractAddress).data).toUpperCase());
|
||||
|
||||
const balance = (await client.authAccounts(contractAddress)).result.value.coins;
|
||||
expect(balance).toEqual(transferAmount);
|
||||
}
|
||||
|
||||
// execute
|
||||
{
|
||||
const result = await executeContract(client, pen, contractAddress, { release: {} });
|
||||
expect(result.data).toEqual("F00BAA");
|
||||
expect(result.code).toBeFalsy();
|
||||
// console.log("Raw log:", result.logs);
|
||||
const logs = parseLogs(result.logs);
|
||||
const wasmEvent = logs.find(() => true)?.events.find((e) => e.type === "wasm");
|
||||
assert(wasmEvent, "Event of type wasm expected");
|
||||
expect(wasmEvent.attributes).toContain({ key: "action", value: "release" });
|
||||
expect(wasmEvent.attributes).toContain({
|
||||
key: "destination",
|
||||
value: beneficiaryAddress,
|
||||
});
|
||||
|
||||
// Verify token transfer from contract to beneficiary
|
||||
const beneficiaryBalance = (await client.authAccounts(beneficiaryAddress)).result.value.coins;
|
||||
expect(beneficiaryBalance).toEqual(transferAmount);
|
||||
const contractBalance = (await client.authAccounts(contractAddress)).result.value.coins;
|
||||
expect(contractBalance).toEqual([]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// The /wasm endpoints
|
||||
|
||||
describe("query", () => {
|
||||
it("can list upload code", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic);
|
||||
const client = makeWasmClient(wasmd.endpoint);
|
||||
|
||||
// check with contracts were here first to compare
|
||||
const existingInfos = await client.listCodeInfo();
|
||||
existingInfos.forEach((val, idx) => expect(val.id).toEqual(idx + 1));
|
||||
const numExisting = existingInfos.length;
|
||||
|
||||
// upload data
|
||||
const hackatom = getHackatom();
|
||||
const result = await uploadContract(client, pen, hackatom);
|
||||
expect(result.code).toBeFalsy();
|
||||
const logs = parseLogs(result.logs);
|
||||
const codeIdAttr = findAttribute(logs, "message", "code_id");
|
||||
const codeId = Number.parseInt(codeIdAttr.value, 10);
|
||||
|
||||
// ensure we were added to the end of the list
|
||||
const newInfos = await client.listCodeInfo();
|
||||
expect(newInfos.length).toEqual(numExisting + 1);
|
||||
const lastInfo = newInfos[newInfos.length - 1];
|
||||
expect(lastInfo.id).toEqual(codeId);
|
||||
expect(lastInfo.creator).toEqual(alice.address0);
|
||||
|
||||
// ensure metadata is present
|
||||
expect(lastInfo.source).toEqual(hackatom.source);
|
||||
expect(lastInfo.builder).toEqual(hackatom.builder);
|
||||
|
||||
// check code hash matches expectation
|
||||
const wasmHash = new Sha256(hackatom.data).digest();
|
||||
expect(lastInfo.data_hash.toLowerCase()).toEqual(toHex(wasmHash));
|
||||
|
||||
// download code and check against auto-gen
|
||||
const { data } = await client.getCode(codeId);
|
||||
expect(fromBase64(data)).toEqual(hackatom.data);
|
||||
});
|
||||
|
||||
it("can list contracts and get info", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic);
|
||||
const client = makeWasmClient(wasmd.endpoint);
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
const transferAmount: readonly Coin[] = [
|
||||
{
|
||||
amount: "707707",
|
||||
denom: "ucosm",
|
||||
},
|
||||
];
|
||||
|
||||
// reuse an existing contract, or upload if needed
|
||||
let codeId: number;
|
||||
const existingInfos = await client.listCodeInfo();
|
||||
if (existingInfos.length > 0) {
|
||||
codeId = existingInfos[existingInfos.length - 1].id;
|
||||
} else {
|
||||
const uploadResult = await uploadContract(client, pen, getHackatom());
|
||||
expect(uploadResult.code).toBeFalsy();
|
||||
const uploadLogs = parseLogs(uploadResult.logs);
|
||||
const codeIdAttr = findAttribute(uploadLogs, "message", "code_id");
|
||||
codeId = Number.parseInt(codeIdAttr.value, 10);
|
||||
}
|
||||
|
||||
// create new instance and compare before and after
|
||||
const existingContractsByCode = await client.listContractsByCodeId(codeId);
|
||||
for (const contract of existingContractsByCode) {
|
||||
expect(contract.address).toMatch(bech32AddressMatcher);
|
||||
expect(contract.code_id).toEqual(codeId);
|
||||
expect(contract.creator).toMatch(bech32AddressMatcher);
|
||||
expect(contract.label).toMatch(/^.+$/);
|
||||
}
|
||||
|
||||
const result = await instantiateContract(client, pen, codeId, beneficiaryAddress, transferAmount);
|
||||
expect(result.code).toBeFalsy();
|
||||
const logs = parseLogs(result.logs);
|
||||
const contractAddressAttr = findAttribute(logs, "message", "contract_address");
|
||||
const myAddress = contractAddressAttr.value;
|
||||
|
||||
const newContractsByCode = await client.listContractsByCodeId(codeId);
|
||||
expect(newContractsByCode.length).toEqual(existingContractsByCode.length + 1);
|
||||
const newContract = newContractsByCode[newContractsByCode.length - 1];
|
||||
expect(newContract).toEqual(
|
||||
jasmine.objectContaining({
|
||||
code_id: codeId,
|
||||
creator: alice.address0,
|
||||
label: "my escrow",
|
||||
}),
|
||||
);
|
||||
|
||||
// check out info
|
||||
const myInfo = await client.getContractInfo(myAddress);
|
||||
assert(myInfo);
|
||||
expect(myInfo).toEqual(
|
||||
jasmine.objectContaining({
|
||||
code_id: codeId,
|
||||
creator: alice.address0,
|
||||
init_msg: jasmine.objectContaining({
|
||||
beneficiary: beneficiaryAddress,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(myInfo.admin).toBeUndefined();
|
||||
|
||||
// make sure random addresses don't give useful info
|
||||
const nonExistentAddress = makeRandomAddress();
|
||||
expect(await client.getContractInfo(nonExistentAddress)).toBeNull();
|
||||
});
|
||||
|
||||
describe("contract state", () => {
|
||||
const client = makeWasmClient(wasmd.endpoint);
|
||||
const noContract = makeRandomAddress();
|
||||
const expectedKey = toAscii("config");
|
||||
let contractAddress: string | undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
if (wasmdEnabled()) {
|
||||
const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic);
|
||||
const uploadResult = await uploadContract(client, pen, getHackatom());
|
||||
assert(!uploadResult.code);
|
||||
const uploadLogs = parseLogs(uploadResult.logs);
|
||||
const codeId = Number.parseInt(findAttribute(uploadLogs, "message", "code_id").value, 10);
|
||||
const instantiateResult = await instantiateContract(client, pen, codeId, makeRandomAddress());
|
||||
assert(!instantiateResult.code);
|
||||
const instantiateLogs = parseLogs(instantiateResult.logs);
|
||||
const contractAddressAttr = findAttribute(instantiateLogs, "message", "contract_address");
|
||||
contractAddress = contractAddressAttr.value;
|
||||
}
|
||||
});
|
||||
|
||||
it("can get all state", async () => {
|
||||
pendingWithoutWasmd();
|
||||
|
||||
// get contract state
|
||||
const state = await client.getAllContractState(contractAddress!);
|
||||
expect(state.length).toEqual(1);
|
||||
const data = state[0];
|
||||
expect(data.key).toEqual(expectedKey);
|
||||
const value = JSON.parse(fromAscii(data.val));
|
||||
expect(value.verifier).toBeDefined();
|
||||
expect(value.beneficiary).toBeDefined();
|
||||
|
||||
// bad address is empty array
|
||||
const noContractState = await client.getAllContractState(noContract);
|
||||
expect(noContractState).toEqual([]);
|
||||
});
|
||||
|
||||
it("can query by key", async () => {
|
||||
pendingWithoutWasmd();
|
||||
|
||||
// query by one key
|
||||
const raw = await client.queryContractRaw(contractAddress!, expectedKey);
|
||||
assert(raw, "must get result");
|
||||
const model = JSON.parse(fromAscii(raw));
|
||||
expect(model.verifier).toBeDefined();
|
||||
expect(model.beneficiary).toBeDefined();
|
||||
|
||||
// missing key is null
|
||||
const missing = await client.queryContractRaw(contractAddress!, fromHex("cafe0dad"));
|
||||
expect(missing).toBeNull();
|
||||
|
||||
// bad address is null
|
||||
const noContractModel = await client.queryContractRaw(noContract, expectedKey);
|
||||
expect(noContractModel).toBeNull();
|
||||
});
|
||||
|
||||
it("can make smart queries", async () => {
|
||||
pendingWithoutWasmd();
|
||||
|
||||
// we can query the verifier properly
|
||||
const resultDocument = await client.queryContractSmart(contractAddress!, { verifier: {} });
|
||||
expect(resultDocument).toEqual({ verifier: alice.address0 });
|
||||
|
||||
// invalid query syntax throws an error
|
||||
await client.queryContractSmart(contractAddress!, { nosuchkey: {} }).then(
|
||||
() => fail("shouldn't succeed"),
|
||||
(error) =>
|
||||
expect(error).toMatch(/query wasm contract failed: parsing hackatom::contract::QueryMsg/),
|
||||
);
|
||||
|
||||
// invalid address throws an error
|
||||
await client.queryContractSmart(noContract, { verifier: {} }).then(
|
||||
() => fail("shouldn't succeed"),
|
||||
(error) => expect(error).toMatch("not found"),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
157
packages/cosmwasm/src/lcdapi/wasm.ts
Normal file
157
packages/cosmwasm/src/lcdapi/wasm.ts
Normal file
@ -0,0 +1,157 @@
|
||||
import { fromBase64, fromUtf8, toHex, toUtf8 } from "@cosmjs/encoding";
|
||||
import { LcdApiArray, LcdClient, LcdModule, normalizeLcdApiArray } from "@cosmjs/sdk38";
|
||||
|
||||
import { JsonObject, Model, parseWasmData, WasmData } from "../types";
|
||||
|
||||
type WasmResponse<T> = WasmSuccess<T> | WasmError;
|
||||
|
||||
interface WasmSuccess<T> {
|
||||
readonly height: string;
|
||||
readonly result: T;
|
||||
}
|
||||
|
||||
interface WasmError {
|
||||
readonly error: string;
|
||||
}
|
||||
|
||||
export interface CodeInfo {
|
||||
readonly id: number;
|
||||
/** Bech32 account address */
|
||||
readonly creator: string;
|
||||
/** Hex-encoded sha256 hash of the code stored here */
|
||||
readonly data_hash: string;
|
||||
/**
|
||||
* An URL to a .tar.gz archive of the source code of the contract, which can be used to reproducibly build the Wasm bytecode.
|
||||
*
|
||||
* @see https://github.com/CosmWasm/cosmwasm-verify
|
||||
*/
|
||||
readonly source?: string;
|
||||
/**
|
||||
* A docker image (including version) to reproducibly build the Wasm bytecode from the source code.
|
||||
*
|
||||
* @example ```cosmwasm/rust-optimizer:0.8.0```
|
||||
* @see https://github.com/CosmWasm/cosmwasm-verify
|
||||
*/
|
||||
readonly builder?: string;
|
||||
}
|
||||
|
||||
export interface CodeDetails extends CodeInfo {
|
||||
/** Base64 encoded raw wasm data */
|
||||
readonly data: string;
|
||||
}
|
||||
|
||||
// This is list view, without contract info
|
||||
export interface ContractInfo {
|
||||
readonly address: string;
|
||||
readonly code_id: number;
|
||||
/** Bech32 account address */
|
||||
readonly creator: string;
|
||||
/** Bech32-encoded admin address */
|
||||
readonly admin?: string;
|
||||
readonly label: string;
|
||||
}
|
||||
|
||||
export interface ContractDetails extends ContractInfo {
|
||||
/** Argument passed on initialization of the contract */
|
||||
readonly init_msg: object;
|
||||
}
|
||||
|
||||
interface SmartQueryResponse {
|
||||
// base64 encoded response
|
||||
readonly smart: string;
|
||||
}
|
||||
|
||||
function isWasmError<T>(resp: WasmResponse<T>): resp is WasmError {
|
||||
return (resp as WasmError).error !== undefined;
|
||||
}
|
||||
|
||||
function unwrapWasmResponse<T>(response: WasmResponse<T>): T {
|
||||
if (isWasmError(response)) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
return response.result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/cosmwasm/wasmd/blob/master/x/wasm/client/rest/query.go#L19-L27
|
||||
*/
|
||||
export interface WasmModule extends LcdModule {
|
||||
readonly listCodeInfo: () => Promise<readonly CodeInfo[]>;
|
||||
|
||||
/**
|
||||
* Downloads the original wasm bytecode by code ID.
|
||||
*
|
||||
* Throws an error if no code with this id
|
||||
*/
|
||||
readonly getCode: (id: number) => Promise<CodeDetails>;
|
||||
|
||||
readonly listContractsByCodeId: (id: number) => Promise<readonly ContractInfo[]>;
|
||||
|
||||
/**
|
||||
* Returns null when contract was not found at this address.
|
||||
*/
|
||||
readonly getContractInfo: (address: string) => Promise<ContractDetails | null>;
|
||||
|
||||
/**
|
||||
* Returns all contract state.
|
||||
* This is an empty array if no such contract, or contract has no data.
|
||||
*/
|
||||
readonly getAllContractState: (address: string) => Promise<readonly Model[]>;
|
||||
|
||||
/**
|
||||
* 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<Uint8Array | null>;
|
||||
|
||||
/**
|
||||
* Makes a smart query on the contract and parses the reponse as JSON.
|
||||
* Throws error if no such contract exists, the query format is invalid or the response is invalid.
|
||||
*/
|
||||
readonly queryContractSmart: (address: string, query: object) => Promise<JsonObject>;
|
||||
}
|
||||
|
||||
export function setupWasmModule(base: LcdClient): WasmModule {
|
||||
return {
|
||||
listCodeInfo: async () => {
|
||||
const path = `/wasm/code`;
|
||||
const responseData = (await base.get(path)) as WasmResponse<LcdApiArray<CodeInfo>>;
|
||||
return normalizeLcdApiArray(unwrapWasmResponse(responseData));
|
||||
},
|
||||
getCode: async (id: number) => {
|
||||
const path = `/wasm/code/${id}`;
|
||||
const responseData = (await base.get(path)) as WasmResponse<CodeDetails>;
|
||||
return unwrapWasmResponse(responseData);
|
||||
},
|
||||
listContractsByCodeId: async (id: number) => {
|
||||
const path = `/wasm/code/${id}/contracts`;
|
||||
const responseData = (await base.get(path)) as WasmResponse<LcdApiArray<ContractInfo>>;
|
||||
return normalizeLcdApiArray(unwrapWasmResponse(responseData));
|
||||
},
|
||||
getContractInfo: async (address: string) => {
|
||||
const path = `/wasm/contract/${address}`;
|
||||
const response = (await base.get(path)) as WasmResponse<ContractDetails | null>;
|
||||
return unwrapWasmResponse(response);
|
||||
},
|
||||
getAllContractState: async (address: string) => {
|
||||
const path = `/wasm/contract/${address}/state`;
|
||||
const responseData = (await base.get(path)) as WasmResponse<LcdApiArray<WasmData>>;
|
||||
return normalizeLcdApiArray(unwrapWasmResponse(responseData)).map(parseWasmData);
|
||||
},
|
||||
queryContractRaw: async (address: string, key: Uint8Array) => {
|
||||
const hexKey = toHex(key);
|
||||
const path = `/wasm/contract/${address}/raw/${hexKey}?encoding=hex`;
|
||||
const responseData = (await base.get(path)) as WasmResponse<WasmData[]>;
|
||||
const data = unwrapWasmResponse(responseData);
|
||||
return data.length === 0 ? null : fromBase64(data[0].val);
|
||||
},
|
||||
queryContractSmart: async (address: string, query: object) => {
|
||||
const encoded = toHex(toUtf8(JSON.stringify(query)));
|
||||
const path = `/wasm/contract/${address}/smart/${encoded}?encoding=hex`;
|
||||
const responseData = (await base.get(path)) as WasmResponse<SmartQueryResponse>;
|
||||
const result = unwrapWasmResponse(responseData);
|
||||
// By convention, smart queries must return a valid JSON document (see https://github.com/CosmWasm/cosmwasm/issues/144)
|
||||
return JSON.parse(fromUtf8(fromBase64(result.smart)));
|
||||
},
|
||||
};
|
||||
}
|
||||
72
packages/cosmwasm/types/lcdapi/wasm.d.ts
vendored
Normal file
72
packages/cosmwasm/types/lcdapi/wasm.d.ts
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
import { LcdClient, LcdModule } from "@cosmjs/sdk38";
|
||||
import { JsonObject, Model } from "../types";
|
||||
export interface CodeInfo {
|
||||
readonly id: number;
|
||||
/** Bech32 account address */
|
||||
readonly creator: string;
|
||||
/** Hex-encoded sha256 hash of the code stored here */
|
||||
readonly data_hash: string;
|
||||
/**
|
||||
* An URL to a .tar.gz archive of the source code of the contract, which can be used to reproducibly build the Wasm bytecode.
|
||||
*
|
||||
* @see https://github.com/CosmWasm/cosmwasm-verify
|
||||
*/
|
||||
readonly source?: string;
|
||||
/**
|
||||
* A docker image (including version) to reproducibly build the Wasm bytecode from the source code.
|
||||
*
|
||||
* @example ```cosmwasm/rust-optimizer:0.8.0```
|
||||
* @see https://github.com/CosmWasm/cosmwasm-verify
|
||||
*/
|
||||
readonly builder?: string;
|
||||
}
|
||||
export interface CodeDetails extends CodeInfo {
|
||||
/** Base64 encoded raw wasm data */
|
||||
readonly data: string;
|
||||
}
|
||||
export interface ContractInfo {
|
||||
readonly address: string;
|
||||
readonly code_id: number;
|
||||
/** Bech32 account address */
|
||||
readonly creator: string;
|
||||
/** Bech32-encoded admin address */
|
||||
readonly admin?: string;
|
||||
readonly label: string;
|
||||
}
|
||||
export interface ContractDetails extends ContractInfo {
|
||||
/** Argument passed on initialization of the contract */
|
||||
readonly init_msg: object;
|
||||
}
|
||||
/**
|
||||
* @see https://github.com/cosmwasm/wasmd/blob/master/x/wasm/client/rest/query.go#L19-L27
|
||||
*/
|
||||
export interface WasmModule extends LcdModule {
|
||||
readonly listCodeInfo: () => Promise<readonly CodeInfo[]>;
|
||||
/**
|
||||
* Downloads the original wasm bytecode by code ID.
|
||||
*
|
||||
* Throws an error if no code with this id
|
||||
*/
|
||||
readonly getCode: (id: number) => Promise<CodeDetails>;
|
||||
readonly listContractsByCodeId: (id: number) => Promise<readonly ContractInfo[]>;
|
||||
/**
|
||||
* Returns null when contract was not found at this address.
|
||||
*/
|
||||
readonly getContractInfo: (address: string) => Promise<ContractDetails | null>;
|
||||
/**
|
||||
* Returns all contract state.
|
||||
* This is an empty array if no such contract, or contract has no data.
|
||||
*/
|
||||
readonly getAllContractState: (address: string) => Promise<readonly Model[]>;
|
||||
/**
|
||||
* 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<Uint8Array | null>;
|
||||
/**
|
||||
* Makes a smart query on the contract and parses the reponse as JSON.
|
||||
* Throws error if no such contract exists, the query format is invalid or the response is invalid.
|
||||
*/
|
||||
readonly queryContractSmart: (address: string, query: object) => Promise<JsonObject>;
|
||||
}
|
||||
export declare function setupWasmModule(base: LcdClient): WasmModule;
|
||||
1
packages/cosmwasm/types/lcdapi/wasm.spec.d.ts
vendored
Normal file
1
packages/cosmwasm/types/lcdapi/wasm.spec.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export {};
|
||||
Loading…
Reference in New Issue
Block a user