mirror of
https://github.com/cosmos/cosmjs.git
synced 2025-03-11 14:09:15 +00:00
Merge pull request #47 from confio/execute-contract
Implement contract execution message
This commit is contained in:
commit
a74310ab4c
@ -311,7 +311,11 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
let senderAddress: string;
|
||||
if (types.isMsgSend(firstMsg)) {
|
||||
senderAddress = firstMsg.value.from_address;
|
||||
} else if (types.isMsgStoreCode(firstMsg) || types.isMsgInstantiateContract(firstMsg)) {
|
||||
} else if (
|
||||
types.isMsgStoreCode(firstMsg) ||
|
||||
types.isMsgInstantiateContract(firstMsg) ||
|
||||
types.isMsgExecuteContract(firstMsg)
|
||||
) {
|
||||
senderAddress = firstMsg.value.sender;
|
||||
} else {
|
||||
throw new Error(`Got unsupported type of message: ${firstMsg.type}`);
|
||||
|
@ -1,17 +1,19 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
import { ChainId, PrehashType, SignableBytes } from "@iov/bcp";
|
||||
import { Encoding } from "@iov/encoding";
|
||||
import { Random } from "@iov/crypto";
|
||||
import { Bech32, Encoding } from "@iov/encoding";
|
||||
import { HdPaths, Secp256k1HdWallet } from "@iov/keycontrol";
|
||||
|
||||
import { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding";
|
||||
import { leb128Encode } from "./leb128.spec";
|
||||
import { Log, parseLogs } from "./logs";
|
||||
import { Attribute, Log, parseLogs } from "./logs";
|
||||
import { RestClient } from "./restclient";
|
||||
import contract from "./testdata/contract.json";
|
||||
import cosmoshub from "./testdata/cosmoshub.json";
|
||||
import {
|
||||
Coin,
|
||||
Msg,
|
||||
MsgExecuteContract,
|
||||
MsgInstantiateContract,
|
||||
MsgSend,
|
||||
MsgStoreCode,
|
||||
@ -76,6 +78,24 @@ function getRandomizedContract(): Uint8Array {
|
||||
return data;
|
||||
}
|
||||
|
||||
function makeRandomAddress(): string {
|
||||
return Bech32.encode("cosmos", Random.getBytes(20));
|
||||
}
|
||||
|
||||
/** Throws if the attribute was not found */
|
||||
function findAttribute(logs: readonly Log[], eventType: "message" | "transfer", attrKey: string): Attribute {
|
||||
const firstLogs = logs.find(() => true);
|
||||
const out = firstLogs?.events
|
||||
.find(event => event.type === eventType)
|
||||
?.attributes.find(attr => attr.key === attrKey);
|
||||
if (!out) {
|
||||
throw new Error(
|
||||
`Could not find attribute '${attrKey}' in first event of type '${eventType}' in first log.`,
|
||||
);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
describe("RestClient", () => {
|
||||
it("can be constructed", () => {
|
||||
const client = new RestClient(httpUrl);
|
||||
@ -154,12 +174,24 @@ describe("RestClient", () => {
|
||||
expect(result.code).toBeFalsy();
|
||||
});
|
||||
|
||||
it("can upload and instantiate wasm", async () => {
|
||||
it("can upload, instantiate and execute wasm", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const wallet = Secp256k1HdWallet.fromMnemonic(faucetMnemonic);
|
||||
const signer = await wallet.createIdentity("abc" as ChainId, faucetPath);
|
||||
const client = new RestClient(httpUrl);
|
||||
|
||||
const transferAmount: readonly Coin[] = [
|
||||
{
|
||||
amount: "1234",
|
||||
denom: "ucosm",
|
||||
},
|
||||
{
|
||||
amount: "321",
|
||||
denom: "ustake",
|
||||
},
|
||||
];
|
||||
const beneficiaryAddress = makeRandomAddress();
|
||||
|
||||
let codeId: number;
|
||||
|
||||
// upload
|
||||
@ -192,9 +224,8 @@ describe("RestClient", () => {
|
||||
const result = await client.postTx(marshalTx(signedTx));
|
||||
// console.log("Raw log:", result.raw_log);
|
||||
expect(result.code).toBeFalsy();
|
||||
const [firstLog] = parseSuccess(result.raw_log);
|
||||
const codeIdAttr = firstLog.events[0].attributes.find(attr => attr.key === "code_id");
|
||||
if (!codeIdAttr) throw new Error("Could not find code_id attribute");
|
||||
const logs = parseSuccess(result.raw_log);
|
||||
const codeIdAttr = findAttribute(logs, "message", "code_id");
|
||||
codeId = Number.parseInt(codeIdAttr.value, 10);
|
||||
expect(codeId).toBeGreaterThanOrEqual(1);
|
||||
expect(codeId).toBeLessThanOrEqual(200);
|
||||
@ -205,24 +236,14 @@ describe("RestClient", () => {
|
||||
// instantiate
|
||||
{
|
||||
const memo = "Create an escrow instance";
|
||||
const transferAmount: readonly Coin[] = [
|
||||
{
|
||||
amount: "1234",
|
||||
denom: "ucosm",
|
||||
},
|
||||
{
|
||||
amount: "321",
|
||||
denom: "ustake",
|
||||
},
|
||||
];
|
||||
const theMsg: MsgInstantiateContract = {
|
||||
type: "wasm/instantiate",
|
||||
value: {
|
||||
sender: faucetAddress,
|
||||
code_id: codeId.toString(),
|
||||
init_msg: {
|
||||
verifier: "cosmos1ltkhnmdcqemmd2tkhnx7qx66tq7e0wykw2j85k",
|
||||
beneficiary: "cosmos1ltkhnmdcqemmd2tkhnx7qx66tq7e0wykw2j85k",
|
||||
verifier: faucetAddress,
|
||||
beneficiary: beneficiaryAddress,
|
||||
},
|
||||
init_funds: transferAmount,
|
||||
},
|
||||
@ -245,23 +266,55 @@ describe("RestClient", () => {
|
||||
const result = await client.postTx(marshalTx(signedTx));
|
||||
expect(result.code).toBeFalsy();
|
||||
// console.log("Raw log:", result.raw_log);
|
||||
const [firstLog] = parseSuccess(result.raw_log);
|
||||
|
||||
const amountAttr = firstLog.events
|
||||
.find(event => event.type === "transfer")
|
||||
?.attributes.find(attr => attr.key === "amount");
|
||||
if (!amountAttr) throw new Error("Could not find amount attribute");
|
||||
const logs = parseSuccess(result.raw_log);
|
||||
const amountAttr = findAttribute(logs, "transfer", "amount");
|
||||
expect(amountAttr.value).toEqual("1234ucosm,321ustake");
|
||||
|
||||
const contractAddressAttr = firstLog.events
|
||||
.find(event => event.type === "message")
|
||||
?.attributes.find(attr => attr.key === "contract_address");
|
||||
if (!contractAddressAttr) throw new Error("Could not find contract_address attribute");
|
||||
const contractAddressAttr = findAttribute(logs, "message", "contract_address");
|
||||
contractAddress = contractAddressAttr.value;
|
||||
|
||||
const balance = (await client.authAccounts(contractAddress)).result.value.coins;
|
||||
expect(balance).toEqual(transferAmount);
|
||||
}
|
||||
});
|
||||
|
||||
// execute
|
||||
{
|
||||
const memo = "Time for action";
|
||||
const theMsg: MsgExecuteContract = {
|
||||
type: "wasm/execute",
|
||||
value: {
|
||||
sender: faucetAddress,
|
||||
contract: contractAddress,
|
||||
msg: {},
|
||||
sent_funds: [],
|
||||
},
|
||||
};
|
||||
const fee: StdFee = {
|
||||
amount: [
|
||||
{
|
||||
amount: "5000000",
|
||||
denom: "ucosm",
|
||||
},
|
||||
],
|
||||
gas: "89000000",
|
||||
};
|
||||
|
||||
const account = (await client.authAccounts(faucetAddress)).result.value;
|
||||
const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account) as SignableBytes;
|
||||
const rawSignature = await wallet.createTransactionSignature(signer, signBytes, PrehashType.Sha256);
|
||||
const signature = encodeSecp256k1Signature(signer.pubkey.data, rawSignature);
|
||||
const signedTx = makeSignedTx(theMsg, fee, memo, signature);
|
||||
const result = await client.postTx(marshalTx(signedTx));
|
||||
expect(result.code).toBeFalsy();
|
||||
// console.log("Raw log:", result.raw_log);
|
||||
const [firstLog] = parseSuccess(result.raw_log);
|
||||
expect(firstLog.log).toEqual(`released funds to ${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([]);
|
||||
}
|
||||
}, 30_000);
|
||||
});
|
||||
});
|
||||
|
@ -64,6 +64,12 @@ interface PostTxsResponse {
|
||||
readonly txhash: string;
|
||||
readonly code?: number;
|
||||
readonly raw_log?: string;
|
||||
/** The same as `raw_log` but deserialized? */
|
||||
readonly logs?: object;
|
||||
/** The gas limit as set by the user */
|
||||
readonly gas_wanted?: string;
|
||||
/** The gas used by the execution */
|
||||
readonly gas_used?: string;
|
||||
}
|
||||
|
||||
interface EncodeTxResponse {
|
||||
|
@ -27,20 +27,26 @@ interface MsgTemplate {
|
||||
readonly value: object;
|
||||
}
|
||||
|
||||
export interface ValueSend {
|
||||
/** A Cosmos SDK token transfer message */
|
||||
export interface MsgSend extends MsgTemplate {
|
||||
readonly type: "cosmos-sdk/MsgSend";
|
||||
readonly value: {
|
||||
/** Bech32 account address */
|
||||
readonly from_address: string;
|
||||
/** Bech32 account address */
|
||||
readonly to_address: string;
|
||||
readonly amount: ReadonlyArray<Coin>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MsgSend extends MsgTemplate {
|
||||
readonly type: "cosmos-sdk/MsgSend";
|
||||
readonly value: ValueSend;
|
||||
}
|
||||
|
||||
export interface ValueStoreCode {
|
||||
/**
|
||||
* Uploads Wam code to the chain
|
||||
*
|
||||
* @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L17
|
||||
*/
|
||||
export interface MsgStoreCode extends MsgTemplate {
|
||||
readonly type: "wasm/store-code";
|
||||
readonly value: {
|
||||
/** Bech32 account address */
|
||||
readonly sender: string;
|
||||
/** Base64 encoded Wasm */
|
||||
@ -49,14 +55,17 @@ export interface ValueStoreCode {
|
||||
readonly source?: string;
|
||||
/** A docker tag, optional */
|
||||
readonly builder?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MsgStoreCode extends MsgTemplate {
|
||||
readonly type: "wasm/store-code";
|
||||
readonly value: ValueStoreCode;
|
||||
}
|
||||
|
||||
export interface ValueInstantiateContract {
|
||||
/**
|
||||
* Creates an instance of contract that was uploaded before.
|
||||
*
|
||||
* @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L73
|
||||
*/
|
||||
export interface MsgInstantiateContract extends MsgTemplate {
|
||||
readonly type: "wasm/instantiate";
|
||||
readonly value: {
|
||||
/** Bech32 account address */
|
||||
readonly sender: string;
|
||||
/** ID of the Wasm code that was uploaded before */
|
||||
@ -64,14 +73,28 @@ export interface ValueInstantiateContract {
|
||||
/** Init message as JavaScript object */
|
||||
readonly init_msg: object;
|
||||
readonly init_funds: ReadonlyArray<Coin>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MsgInstantiateContract extends MsgTemplate {
|
||||
readonly type: "wasm/instantiate";
|
||||
readonly value: ValueInstantiateContract;
|
||||
/**
|
||||
* Creates an instance of contract that was uploaded before.
|
||||
*
|
||||
* @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L103
|
||||
*/
|
||||
export interface MsgExecuteContract extends MsgTemplate {
|
||||
readonly type: "wasm/execute";
|
||||
readonly value: {
|
||||
/** Bech32 account address */
|
||||
readonly sender: string;
|
||||
/** Bech32 account address */
|
||||
readonly contract: string;
|
||||
/** Handle message as JavaScript object */
|
||||
readonly msg: object;
|
||||
readonly sent_funds: ReadonlyArray<Coin>;
|
||||
};
|
||||
}
|
||||
|
||||
export type Msg = MsgSend | MsgStoreCode | MsgInstantiateContract | MsgTemplate;
|
||||
export type Msg = MsgSend | MsgStoreCode | MsgInstantiateContract | MsgExecuteContract | MsgTemplate;
|
||||
|
||||
export function isMsgSend(msg: Msg): msg is MsgSend {
|
||||
return (msg as MsgSend).type === "cosmos-sdk/MsgSend";
|
||||
@ -85,6 +108,10 @@ export function isMsgInstantiateContract(msg: Msg): msg is MsgInstantiateContrac
|
||||
return (msg as MsgInstantiateContract).type === "wasm/instantiate";
|
||||
}
|
||||
|
||||
export function isMsgExecuteContract(msg: Msg): msg is MsgExecuteContract {
|
||||
return (msg as MsgExecuteContract).type === "wasm/execute";
|
||||
}
|
||||
|
||||
export interface StdFee {
|
||||
readonly amount: ReadonlyArray<Coin>;
|
||||
readonly gas: string;
|
||||
|
6
packages/sdk/types/restclient.d.ts
vendored
6
packages/sdk/types/restclient.d.ts
vendored
@ -49,6 +49,12 @@ interface PostTxsResponse {
|
||||
readonly txhash: string;
|
||||
readonly code?: number;
|
||||
readonly raw_log?: string;
|
||||
/** The same as `raw_log` but deserialized? */
|
||||
readonly logs?: object;
|
||||
/** The gas limit as set by the user */
|
||||
readonly gas_wanted?: string;
|
||||
/** The gas used by the execution */
|
||||
readonly gas_used?: string;
|
||||
}
|
||||
interface EncodeTxResponse {
|
||||
readonly tx: string;
|
||||
|
56
packages/sdk/types/types.d.ts
vendored
56
packages/sdk/types/types.d.ts
vendored
@ -16,18 +16,25 @@ interface MsgTemplate {
|
||||
readonly type: string;
|
||||
readonly value: object;
|
||||
}
|
||||
export interface ValueSend {
|
||||
/** A Cosmos SDK token transfer message */
|
||||
export interface MsgSend extends MsgTemplate {
|
||||
readonly type: "cosmos-sdk/MsgSend";
|
||||
readonly value: {
|
||||
/** Bech32 account address */
|
||||
readonly from_address: string;
|
||||
/** Bech32 account address */
|
||||
readonly to_address: string;
|
||||
readonly amount: ReadonlyArray<Coin>;
|
||||
};
|
||||
}
|
||||
export interface MsgSend extends MsgTemplate {
|
||||
readonly type: "cosmos-sdk/MsgSend";
|
||||
readonly value: ValueSend;
|
||||
}
|
||||
export interface ValueStoreCode {
|
||||
/**
|
||||
* Uploads Wam code to the chain
|
||||
*
|
||||
* @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L17
|
||||
*/
|
||||
export interface MsgStoreCode extends MsgTemplate {
|
||||
readonly type: "wasm/store-code";
|
||||
readonly value: {
|
||||
/** Bech32 account address */
|
||||
readonly sender: string;
|
||||
/** Base64 encoded Wasm */
|
||||
@ -36,12 +43,16 @@ export interface ValueStoreCode {
|
||||
readonly source?: string;
|
||||
/** A docker tag, optional */
|
||||
readonly builder?: string;
|
||||
};
|
||||
}
|
||||
export interface MsgStoreCode extends MsgTemplate {
|
||||
readonly type: "wasm/store-code";
|
||||
readonly value: ValueStoreCode;
|
||||
}
|
||||
export interface ValueInstantiateContract {
|
||||
/**
|
||||
* Creates an instance of contract that was uploaded before.
|
||||
*
|
||||
* @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L73
|
||||
*/
|
||||
export interface MsgInstantiateContract extends MsgTemplate {
|
||||
readonly type: "wasm/instantiate";
|
||||
readonly value: {
|
||||
/** Bech32 account address */
|
||||
readonly sender: string;
|
||||
/** ID of the Wasm code that was uploaded before */
|
||||
@ -49,15 +60,30 @@ export interface ValueInstantiateContract {
|
||||
/** Init message as JavaScript object */
|
||||
readonly init_msg: object;
|
||||
readonly init_funds: ReadonlyArray<Coin>;
|
||||
};
|
||||
}
|
||||
export interface MsgInstantiateContract extends MsgTemplate {
|
||||
readonly type: "wasm/instantiate";
|
||||
readonly value: ValueInstantiateContract;
|
||||
/**
|
||||
* Creates an instance of contract that was uploaded before.
|
||||
*
|
||||
* @see https://github.com/cosmwasm/wasmd/blob/9842678d89/x/wasm/internal/types/msg.go#L103
|
||||
*/
|
||||
export interface MsgExecuteContract extends MsgTemplate {
|
||||
readonly type: "wasm/execute";
|
||||
readonly value: {
|
||||
/** Bech32 account address */
|
||||
readonly sender: string;
|
||||
/** Bech32 account address */
|
||||
readonly contract: string;
|
||||
/** Handle message as JavaScript object */
|
||||
readonly msg: object;
|
||||
readonly sent_funds: ReadonlyArray<Coin>;
|
||||
};
|
||||
}
|
||||
export declare type Msg = MsgSend | MsgStoreCode | MsgInstantiateContract | MsgTemplate;
|
||||
export declare type Msg = MsgSend | MsgStoreCode | MsgInstantiateContract | MsgExecuteContract | MsgTemplate;
|
||||
export declare function isMsgSend(msg: Msg): msg is MsgSend;
|
||||
export declare function isMsgStoreCode(msg: Msg): msg is MsgStoreCode;
|
||||
export declare function isMsgInstantiateContract(msg: Msg): msg is MsgInstantiateContract;
|
||||
export declare function isMsgExecuteContract(msg: Msg): msg is MsgExecuteContract;
|
||||
export interface StdFee {
|
||||
readonly amount: ReadonlyArray<Coin>;
|
||||
readonly gas: string;
|
||||
|
Loading…
x
Reference in New Issue
Block a user