diff --git a/packages/bcp/src/cosmwasmconnection.ts b/packages/bcp/src/cosmwasmconnection.ts index 8e02310b..fe4c9f2d 100644 --- a/packages/bcp/src/cosmwasmconnection.ts +++ b/packages/bcp/src/cosmwasmconnection.ts @@ -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}`); diff --git a/packages/sdk/src/restclient.spec.ts b/packages/sdk/src/restclient.spec.ts index 348e1fb5..bfd71c9a 100644 --- a/packages/sdk/src/restclient.spec.ts +++ b/packages/sdk/src/restclient.spec.ts @@ -1,6 +1,7 @@ /* 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"; @@ -12,6 +13,7 @@ import cosmoshub from "./testdata/cosmoshub.json"; import { Coin, Msg, + MsgExecuteContract, MsgInstantiateContract, MsgSend, MsgStoreCode, @@ -76,6 +78,10 @@ function getRandomizedContract(): Uint8Array { return data; } +function makeRandomAddress(): string { + return Bech32.encode("cosmos", Random.getBytes(20)); +} + describe("RestClient", () => { it("can be constructed", () => { const client = new RestClient(httpUrl); @@ -154,12 +160,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 @@ -207,24 +225,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, }, @@ -264,6 +272,46 @@ describe("RestClient", () => { 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); }); }); diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index 23fdca3b..560a2fdd 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -76,7 +76,25 @@ export interface MsgInstantiateContract extends MsgTemplate { }; } -export type Msg = MsgSend | MsgStoreCode | MsgInstantiateContract | MsgTemplate; +/** + * 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; + }; +} + +export type Msg = MsgSend | MsgStoreCode | MsgInstantiateContract | MsgExecuteContract | MsgTemplate; export function isMsgSend(msg: Msg): msg is MsgSend { return (msg as MsgSend).type === "cosmos-sdk/MsgSend"; @@ -90,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; readonly gas: string; diff --git a/packages/sdk/types/types.d.ts b/packages/sdk/types/types.d.ts index cbb86dec..723db8f6 100644 --- a/packages/sdk/types/types.d.ts +++ b/packages/sdk/types/types.d.ts @@ -62,10 +62,28 @@ export interface MsgInstantiateContract extends MsgTemplate { readonly init_funds: ReadonlyArray; }; } -export declare type Msg = MsgSend | MsgStoreCode | MsgInstantiateContract | MsgTemplate; +/** + * 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; + }; +} +export declare type Msg = MsgSend | MsgStoreCode | MsgInstantiateContract | MsgExecuteContract | MsgTemplate; export declare function isMsgSend(msg: Msg): msg is MsgSend; export declare function isMsgStoreCode(msg: Msg): msg is MsgStoreCode; export declare function isMsgInstantiateContract(msg: Msg): msg is MsgInstantiateContract; +export declare function isMsgExecuteContract(msg: Msg): msg is MsgExecuteContract; export interface StdFee { readonly amount: ReadonlyArray; readonly gas: string;