From 462aafa39ebaf1d473cdb86421b2e548443aa7d5 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 28 Jul 2020 17:25:34 +0200 Subject: [PATCH 01/11] Start cleaning up WasmExtension tests --- packages/cosmwasm/src/lcdapi/wasm.spec.ts | 90 +++++++++++------------ 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/packages/cosmwasm/src/lcdapi/wasm.spec.ts b/packages/cosmwasm/src/lcdapi/wasm.spec.ts index eee9fd3dac..00d1f9555f 100644 --- a/packages/cosmwasm/src/lcdapi/wasm.spec.ts +++ b/packages/cosmwasm/src/lcdapi/wasm.spec.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { Sha256 } from "@cosmjs/crypto"; -import { Bech32, fromAscii, fromBase64, fromHex, toAscii, toBase64, toHex } from "@cosmjs/encoding"; +import { Bech32, fromAscii, fromHex, toAscii, toBase64, toHex } from "@cosmjs/encoding"; import { AuthExtension, Coin, @@ -143,10 +143,50 @@ async function executeContract( return client.postTx(signedTx); } -describe("wasm", () => { - it("can be constructed", () => { - const client = makeWasmClient(wasmd.endpoint); - expect(client).toBeTruthy(); +describe("WasmExtension", () => { + const hackatom = getHackatom(); + let hackatomCodeId: number | undefined; + + beforeAll(async () => { + if (wasmdEnabled()) { + const client = makeWasmClient(wasmd.endpoint); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const result = await uploadContract(client, wallet, hackatom); + assert(!result.code); + const logs = parseLogs(result.logs); + const codeIdAttr = findAttribute(logs, "message", "code_id"); + hackatomCodeId = Number.parseInt(codeIdAttr.value, 10); + } + }); + + describe("listCodeInfo", () => { + it("has recently uploaded contract as last entry", async () => { + pendingWithoutWasmd(); + assert(hackatomCodeId); + const client = makeWasmClient(wasmd.endpoint); + const codesList = await client.wasm.listCodeInfo(); + const lastCode = codesList[codesList.length - 1]; + expect(lastCode.id).toEqual(hackatomCodeId); + expect(lastCode.creator).toEqual(alice.address0); + expect(lastCode.source).toEqual(hackatom.source); + expect(lastCode.builder).toEqual(hackatom.builder); + expect(lastCode.data_hash.toLowerCase()).toEqual(toHex(new Sha256(hackatom.data).digest())); + }); + }); + + describe("getCode", () => { + it("contains fill code information", async () => { + pendingWithoutWasmd(); + assert(hackatomCodeId); + const client = makeWasmClient(wasmd.endpoint); + const code = await client.wasm.getCode(hackatomCodeId); + expect(code.id).toEqual(hackatomCodeId); + expect(code.creator).toEqual(alice.address0); + expect(code.source).toEqual(hackatom.source); + expect(code.builder).toEqual(hackatom.builder); + expect(code.data_hash.toLowerCase()).toEqual(toHex(new Sha256(hackatom.data).digest())); + expect(code.data).toEqual(toBase64(hackatom.data)); + }); }); describe("txsQuery", () => { @@ -319,47 +359,7 @@ describe("wasm", () => { }); }); - // The /wasm endpoints - describe("query", () => { - it("can list upload code", async () => { - pendingWithoutWasmd(); - const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); - const client = makeWasmClient(wasmd.endpoint); - - // check with contracts were here first to compare - const existingInfos = await client.wasm.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, wallet, hackatom); - assert(!result.code); - 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.wasm.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.wasm.getCode(codeId); - expect(fromBase64(data)).toEqual(hackatom.data); - }); - it("can list contracts and get info", async () => { pendingWithoutWasmd(); const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); From 104d82850932ea74097c47eeeb8a5f57f04038b2 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 28 Jul 2020 17:31:38 +0200 Subject: [PATCH 02/11] Cleanup getContractInfo tests --- packages/cosmwasm/src/lcdapi/wasm.spec.ts | 126 +++++++++++----------- 1 file changed, 64 insertions(+), 62 deletions(-) diff --git a/packages/cosmwasm/src/lcdapi/wasm.spec.ts b/packages/cosmwasm/src/lcdapi/wasm.spec.ts index 00d1f9555f..c610a4a26d 100644 --- a/packages/cosmwasm/src/lcdapi/wasm.spec.ts +++ b/packages/cosmwasm/src/lcdapi/wasm.spec.ts @@ -189,6 +189,70 @@ describe("WasmExtension", () => { }); }); + // TODO: move listContractsByCodeId tests out of here + describe("getContractInfo", () => { + it("works", async () => { + pendingWithoutWasmd(); + assert(hackatomCodeId); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const client = makeWasmClient(wasmd.endpoint); + const beneficiaryAddress = makeRandomAddress(); + const transferAmount = coins(707707, "ucosm"); + + // create new instance and compare before and after + const existingContractsByCode = await client.wasm.listContractsByCodeId(hackatomCodeId); + for (const contract of existingContractsByCode) { + expect(contract.address).toMatch(bech32AddressMatcher); + expect(contract.code_id).toEqual(hackatomCodeId); + expect(contract.creator).toMatch(bech32AddressMatcher); + expect(contract.label).toMatch(/^.+$/); + } + + const result = await instantiateContract( + client, + wallet, + hackatomCodeId, + beneficiaryAddress, + transferAmount, + ); + assert(!result.code); + const logs = parseLogs(result.logs); + const contractAddressAttr = findAttribute(logs, "message", "contract_address"); + const myAddress = contractAddressAttr.value; + + const newContractsByCode = await client.wasm.listContractsByCodeId(hackatomCodeId); + expect(newContractsByCode.length).toEqual(existingContractsByCode.length + 1); + const newContract = newContractsByCode[newContractsByCode.length - 1]; + expect(newContract).toEqual( + jasmine.objectContaining({ + code_id: hackatomCodeId, + creator: alice.address0, + label: "my escrow", + }), + ); + + const info = await client.wasm.getContractInfo(myAddress); + assert(info); + expect(info).toEqual( + jasmine.objectContaining({ + code_id: hackatomCodeId, + creator: alice.address0, + label: "my escrow", + }), + ); + expect(info.admin).toBeUndefined(); + }); + + it("returns null for non-existent address", async () => { + pendingWithoutWasmd(); + assert(hackatomCodeId); + const client = makeWasmClient(wasmd.endpoint); + const nonExistentAddress = makeRandomAddress(); + const info = await client.wasm.getContractInfo(nonExistentAddress); + expect(info).toBeNull(); + }); + }); + describe("txsQuery", () => { it("can query by tags (module + code_id)", async () => { pendingWithoutWasmd(); @@ -360,68 +424,6 @@ describe("WasmExtension", () => { }); describe("query", () => { - it("can list contracts and get info", async () => { - pendingWithoutWasmd(); - const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); - const client = makeWasmClient(wasmd.endpoint); - const beneficiaryAddress = makeRandomAddress(); - const transferAmount = coins(707707, "ucosm"); - - // reuse an existing contract, or upload if needed - let codeId: number; - const existingInfos = await client.wasm.listCodeInfo(); - if (existingInfos.length > 0) { - codeId = existingInfos[existingInfos.length - 1].id; - } else { - const uploadResult = await uploadContract(client, wallet, getHackatom()); - assert(!uploadResult.code); - 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.wasm.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, wallet, codeId, beneficiaryAddress, transferAmount); - assert(!result.code); - const logs = parseLogs(result.logs); - const contractAddressAttr = findAttribute(logs, "message", "contract_address"); - const myAddress = contractAddressAttr.value; - - const newContractsByCode = await client.wasm.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.wasm.getContractInfo(myAddress); - assert(myInfo); - expect(myInfo).toEqual( - jasmine.objectContaining({ - code_id: codeId, - creator: alice.address0, - }), - ); - expect(myInfo.admin).toBeUndefined(); - - // make sure random addresses don't give useful info - const nonExistentAddress = makeRandomAddress(); - expect(await client.wasm.getContractInfo(nonExistentAddress)).toBeNull(); - }); - it("can list contract history", async () => { pendingWithoutWasmd(); const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); From 888597fd0428bc47058bb0163ea51f717490c904 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 28 Jul 2020 17:34:34 +0200 Subject: [PATCH 03/11] Pull out getContractCodeHistory tests --- packages/cosmwasm/src/lcdapi/wasm.spec.ts | 87 ++++++++++++----------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/packages/cosmwasm/src/lcdapi/wasm.spec.ts b/packages/cosmwasm/src/lcdapi/wasm.spec.ts index c610a4a26d..954bbe20c6 100644 --- a/packages/cosmwasm/src/lcdapi/wasm.spec.ts +++ b/packages/cosmwasm/src/lcdapi/wasm.spec.ts @@ -253,6 +253,50 @@ describe("WasmExtension", () => { }); }); + describe("getContractCodeHistory", () => { + it("can list contract history", async () => { + pendingWithoutWasmd(); + assert(hackatomCodeId); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const client = makeWasmClient(wasmd.endpoint); + const beneficiaryAddress = makeRandomAddress(); + const transferAmount = coins(707707, "ucosm"); + + // create new instance and compare before and after + const result = await instantiateContract( + client, + wallet, + hackatomCodeId, + beneficiaryAddress, + transferAmount, + ); + assert(!result.code); + const logs = parseLogs(result.logs); + const contractAddressAttr = findAttribute(logs, "message", "contract_address"); + const myAddress = contractAddressAttr.value; + + const history = await client.wasm.getContractCodeHistory(myAddress); + assert(history); + expect(history).toContain({ + code_id: hackatomCodeId, + operation: "Init", + msg: { + verifier: alice.address0, + beneficiary: beneficiaryAddress, + }, + }); + }); + + it("returns null for non-existent address", async () => { + pendingWithoutWasmd(); + assert(hackatomCodeId); + const client = makeWasmClient(wasmd.endpoint); + const nonExistentAddress = makeRandomAddress(); + const history = await client.wasm.getContractCodeHistory(nonExistentAddress); + expect(history).toBeNull(); + }); + }); + describe("txsQuery", () => { it("can query by tags (module + code_id)", async () => { pendingWithoutWasmd(); @@ -424,49 +468,6 @@ describe("WasmExtension", () => { }); describe("query", () => { - it("can list contract history", async () => { - pendingWithoutWasmd(); - const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); - const client = makeWasmClient(wasmd.endpoint); - const beneficiaryAddress = makeRandomAddress(); - const transferAmount = coins(707707, "ucosm"); - - // reuse an existing contract, or upload if needed - let codeId: number; - const existingInfos = await client.wasm.listCodeInfo(); - if (existingInfos.length > 0) { - codeId = existingInfos[existingInfos.length - 1].id; - } else { - const uploadResult = await uploadContract(client, wallet, getHackatom()); - assert(!uploadResult.code); - 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 result = await instantiateContract(client, wallet, codeId, beneficiaryAddress, transferAmount); - assert(!result.code); - const logs = parseLogs(result.logs); - const contractAddressAttr = findAttribute(logs, "message", "contract_address"); - const myAddress = contractAddressAttr.value; - - // check out history - const myHistory = await client.wasm.getContractCodeHistory(myAddress); - assert(myHistory); - expect(myHistory).toContain({ - code_id: codeId, - operation: "Init", - msg: { - verifier: alice.address0, - beneficiary: beneficiaryAddress, - }, - }); - // make sure random addresses don't give useful info - const nonExistentAddress = makeRandomAddress(); - expect(await client.wasm.getContractCodeHistory(nonExistentAddress)).toBeNull(); - }); - describe("contract state", () => { const client = makeWasmClient(wasmd.endpoint); const noContract = makeRandomAddress(); From 5aa5623348d4f4af8c814dfa799dc756b10ea63d Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 28 Jul 2020 17:45:41 +0200 Subject: [PATCH 04/11] Use SigningCosmosClient for testing wasm messages --- packages/cosmwasm/src/lcdapi/wasm.spec.ts | 84 ++++++++--------------- 1 file changed, 29 insertions(+), 55 deletions(-) diff --git a/packages/cosmwasm/src/lcdapi/wasm.spec.ts b/packages/cosmwasm/src/lcdapi/wasm.spec.ts index 954bbe20c6..8bb42e2f3f 100644 --- a/packages/cosmwasm/src/lcdapi/wasm.spec.ts +++ b/packages/cosmwasm/src/lcdapi/wasm.spec.ts @@ -2,6 +2,7 @@ import { Sha256 } from "@cosmjs/crypto"; import { Bech32, fromAscii, fromHex, toAscii, toBase64, toHex } from "@cosmjs/encoding"; import { + assertIsPostTxSuccess, AuthExtension, Coin, coin, @@ -9,9 +10,11 @@ import { LcdClient, makeSignBytes, OfflineSigner, + PostTxResult, PostTxsResponse, Secp256k1Wallet, setupAuthExtension, + SigningCosmosClient, StdFee, } from "@cosmjs/launchpad"; import { assert } from "@cosmjs/utils"; @@ -46,10 +49,9 @@ function makeWasmClient(apiUrl: string): WasmClient { } async function uploadContract( - client: WasmClient, signer: OfflineSigner, contract: ContractUploadInstructions, -): Promise { +): Promise { const memo = "My first contract on chain"; const theMsg: MsgStoreCode = { type: "wasm/MsgStoreCode", @@ -61,29 +63,21 @@ async function uploadContract( }, }; const fee: StdFee = { - amount: [ - { - amount: "5000000", - denom: "ucosm", - }, - ], + amount: coins(5000000, "ucosm"), gas: "89000000", }; - const { account_number, sequence } = (await client.auth.account(alice.address0)).result.value; - const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); - const signature = await signer.sign(alice.address0, signBytes); - const signedTx = makeSignedTx(theMsg, fee, memo, signature); - return client.postTx(signedTx); + const firstAddress = (await signer.getAccounts())[0].address; + const client = new SigningCosmosClient(wasmd.endpoint, firstAddress, signer); + return client.signAndPost([theMsg], fee, memo); } async function instantiateContract( - client: WasmClient, signer: OfflineSigner, codeId: number, beneficiaryAddress: string, transferAmount?: readonly Coin[], -): Promise { +): Promise { const memo = "Create an escrow instance"; const theMsg: MsgInstantiateContract = { type: "wasm/MsgInstantiateContract", @@ -99,20 +93,13 @@ async function instantiateContract( }, }; const fee: StdFee = { - amount: [ - { - amount: "5000000", - denom: "ucosm", - }, - ], + amount: coins(5000000, "ucosm"), gas: "89000000", }; - const { account_number, sequence } = (await client.auth.account(alice.address0)).result.value; - const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); - const signature = await signer.sign(alice.address0, signBytes); - const signedTx = makeSignedTx(theMsg, fee, memo, signature); - return client.postTx(signedTx); + const firstAddress = (await signer.getAccounts())[0].address; + const client = new SigningCosmosClient(wasmd.endpoint, firstAddress, signer); + return client.signAndPost([theMsg], fee, memo); } async function executeContract( @@ -149,10 +136,9 @@ describe("WasmExtension", () => { beforeAll(async () => { if (wasmdEnabled()) { - const client = makeWasmClient(wasmd.endpoint); const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); - const result = await uploadContract(client, wallet, hackatom); - assert(!result.code); + const result = await uploadContract(wallet, hackatom); + assertIsPostTxSuccess(result); const logs = parseLogs(result.logs); const codeIdAttr = findAttribute(logs, "message", "code_id"); hackatomCodeId = Number.parseInt(codeIdAttr.value, 10); @@ -208,14 +194,8 @@ describe("WasmExtension", () => { expect(contract.label).toMatch(/^.+$/); } - const result = await instantiateContract( - client, - wallet, - hackatomCodeId, - beneficiaryAddress, - transferAmount, - ); - assert(!result.code); + const result = await instantiateContract(wallet, hackatomCodeId, beneficiaryAddress, transferAmount); + assertIsPostTxSuccess(result); const logs = parseLogs(result.logs); const contractAddressAttr = findAttribute(logs, "message", "contract_address"); const myAddress = contractAddressAttr.value; @@ -263,14 +243,8 @@ describe("WasmExtension", () => { const transferAmount = coins(707707, "ucosm"); // create new instance and compare before and after - const result = await instantiateContract( - client, - wallet, - hackatomCodeId, - beneficiaryAddress, - transferAmount, - ); - assert(!result.code); + const result = await instantiateContract(wallet, hackatomCodeId, beneficiaryAddress, transferAmount); + assertIsPostTxSuccess(result); const logs = parseLogs(result.logs); const contractAddressAttr = findAttribute(logs, "message", "contract_address"); const myAddress = contractAddressAttr.value; @@ -415,29 +389,29 @@ describe("WasmExtension", () => { // upload { // console.log("Raw log:", result.raw_log); - const result = await uploadContract(client, wallet, getHackatom()); - assert(!result.code); + const result = await uploadContract(wallet, getHackatom()); + assertIsPostTxSuccess(result); 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()); + expect(result.data).toEqual(toAscii(`${codeId}`)); } let contractAddress: string; // instantiate { - const result = await instantiateContract(client, wallet, codeId, beneficiaryAddress, transferAmount); - assert(!result.code); + const result = await instantiateContract(wallet, codeId, beneficiaryAddress, transferAmount); + assertIsPostTxSuccess(result); // 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()); + expect(result.data).toEqual(Bech32.decode(contractAddress).data); const balance = (await client.auth.account(contractAddress)).result.value.coins; expect(balance).toEqual(transferAmount); @@ -477,12 +451,12 @@ describe("WasmExtension", () => { beforeAll(async () => { if (wasmdEnabled()) { const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); - const uploadResult = await uploadContract(client, wallet, getHackatom()); - assert(!uploadResult.code); + const uploadResult = await uploadContract(wallet, getHackatom()); + assertIsPostTxSuccess(uploadResult); const uploadLogs = parseLogs(uploadResult.logs); const codeId = Number.parseInt(findAttribute(uploadLogs, "message", "code_id").value, 10); - const instantiateResult = await instantiateContract(client, wallet, codeId, makeRandomAddress()); - assert(!instantiateResult.code); + const instantiateResult = await instantiateContract(wallet, codeId, makeRandomAddress()); + assertIsPostTxSuccess(instantiateResult); const instantiateLogs = parseLogs(instantiateResult.logs); const contractAddressAttr = findAttribute(instantiateLogs, "message", "contract_address"); contractAddress = contractAddressAttr.value; From 116ab6496af9c36521cf5179dffd4940a0cefcfa Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 28 Jul 2020 17:49:13 +0200 Subject: [PATCH 05/11] Use a single hackatom instance --- packages/cosmwasm/src/lcdapi/wasm.spec.ts | 41 ++++++++++------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/packages/cosmwasm/src/lcdapi/wasm.spec.ts b/packages/cosmwasm/src/lcdapi/wasm.spec.ts index 8bb42e2f3f..d61b4f9063 100644 --- a/packages/cosmwasm/src/lcdapi/wasm.spec.ts +++ b/packages/cosmwasm/src/lcdapi/wasm.spec.ts @@ -133,6 +133,7 @@ async function executeContract( describe("WasmExtension", () => { const hackatom = getHackatom(); let hackatomCodeId: number | undefined; + let hackatomContractAddress: string | undefined; beforeAll(async () => { if (wasmdEnabled()) { @@ -142,6 +143,12 @@ describe("WasmExtension", () => { const logs = parseLogs(result.logs); const codeIdAttr = findAttribute(logs, "message", "code_id"); hackatomCodeId = Number.parseInt(codeIdAttr.value, 10); + + const instantiateResult = await instantiateContract(wallet, hackatomCodeId, makeRandomAddress()); + assertIsPostTxSuccess(instantiateResult); + const instantiateLogs = parseLogs(instantiateResult.logs); + const contractAddressAttr = findAttribute(instantiateLogs, "message", "contract_address"); + hackatomContractAddress = contractAddressAttr.value; } }); @@ -446,29 +453,13 @@ describe("WasmExtension", () => { const client = makeWasmClient(wasmd.endpoint); const noContract = makeRandomAddress(); const expectedKey = toAscii("config"); - let contractAddress: string | undefined; - - beforeAll(async () => { - if (wasmdEnabled()) { - const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); - const uploadResult = await uploadContract(wallet, getHackatom()); - assertIsPostTxSuccess(uploadResult); - const uploadLogs = parseLogs(uploadResult.logs); - const codeId = Number.parseInt(findAttribute(uploadLogs, "message", "code_id").value, 10); - const instantiateResult = await instantiateContract(wallet, codeId, makeRandomAddress()); - assertIsPostTxSuccess(instantiateResult); - const instantiateLogs = parseLogs(instantiateResult.logs); - const contractAddressAttr = findAttribute(instantiateLogs, "message", "contract_address"); - contractAddress = contractAddressAttr.value; - } - }); it("can get all state", async () => { pendingWithoutWasmd(); - assert(contractAddress); + assert(hackatomContractAddress); // get contract state - const state = await client.wasm.getAllContractState(contractAddress); + const state = await client.wasm.getAllContractState(hackatomContractAddress); expect(state.length).toEqual(1); const data = state[0]; expect(data.key).toEqual(expectedKey); @@ -483,17 +474,17 @@ describe("WasmExtension", () => { it("can query by key", async () => { pendingWithoutWasmd(); - assert(contractAddress); + assert(hackatomContractAddress); // query by one key - const raw = await client.wasm.queryContractRaw(contractAddress, expectedKey); + const raw = await client.wasm.queryContractRaw(hackatomContractAddress, 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.wasm.queryContractRaw(contractAddress, fromHex("cafe0dad")); + const missing = await client.wasm.queryContractRaw(hackatomContractAddress, fromHex("cafe0dad")); expect(missing).toBeNull(); // bad address is null @@ -503,14 +494,16 @@ describe("WasmExtension", () => { it("can make smart queries", async () => { pendingWithoutWasmd(); - assert(contractAddress); + assert(hackatomContractAddress); // we can query the verifier properly - const resultDocument = await client.wasm.queryContractSmart(contractAddress, { verifier: {} }); + const resultDocument = await client.wasm.queryContractSmart(hackatomContractAddress, { + verifier: {}, + }); expect(resultDocument).toEqual({ verifier: alice.address0 }); // invalid query syntax throws an error - await client.wasm.queryContractSmart(contractAddress, { nosuchkey: {} }).then( + await client.wasm.queryContractSmart(hackatomContractAddress, { nosuchkey: {} }).then( () => fail("shouldn't succeed"), (error) => expect(error).toMatch(/query wasm contract failed: parsing hackatom::contract::QueryMsg/), From 5ad837f9928f76668f8e968aeea7ec4540aa3067 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 28 Jul 2020 17:55:41 +0200 Subject: [PATCH 06/11] Move getContractCodeHistory after getContractInfo --- packages/cosmwasm/src/lcdapi/wasm.ts | 10 +++++----- packages/cosmwasm/types/lcdapi/wasm.d.ts | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/cosmwasm/src/lcdapi/wasm.ts b/packages/cosmwasm/src/lcdapi/wasm.ts index 3ab14075f6..330eddfa1f 100644 --- a/packages/cosmwasm/src/lcdapi/wasm.ts +++ b/packages/cosmwasm/src/lcdapi/wasm.ts @@ -97,6 +97,11 @@ export interface WasmExtension { */ readonly getContractInfo: (address: string) => Promise; + /** + * Returns null when contract history was not found for this address. + */ + readonly getContractCodeHistory: (address: string) => Promise; + /** * Returns all contract state. * This is an empty array if no such contract, or contract has no data. @@ -114,11 +119,6 @@ export interface WasmExtension { * Throws error if no such contract exists, the query format is invalid or the response is invalid. */ readonly queryContractSmart: (address: string, query: object) => Promise; - - /** - * Returns null when contract history was not found for this address. - */ - readonly getContractCodeHistory: (address: string) => Promise; }; } diff --git a/packages/cosmwasm/types/lcdapi/wasm.d.ts b/packages/cosmwasm/types/lcdapi/wasm.d.ts index c44ec4aa78..e01f0d4212 100644 --- a/packages/cosmwasm/types/lcdapi/wasm.d.ts +++ b/packages/cosmwasm/types/lcdapi/wasm.d.ts @@ -55,6 +55,10 @@ export interface WasmExtension { * Returns null when contract was not found at this address. */ readonly getContractInfo: (address: string) => Promise; + /** + * Returns null when contract history was not found for this address. + */ + readonly getContractCodeHistory: (address: string) => Promise; /** * Returns all contract state. * This is an empty array if no such contract, or contract has no data. @@ -70,10 +74,6 @@ export interface WasmExtension { * Throws error if no such contract exists, the query format is invalid or the response is invalid. */ readonly queryContractSmart: (address: string, query: object) => Promise; - /** - * Returns null when contract history was not found for this address. - */ - readonly getContractCodeHistory: (address: string) => Promise; }; } export declare function setupWasmExtension(base: LcdClient): WasmExtension; From ab2dd159df40cb4964186ad4d4b6c08d43afecbf Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 28 Jul 2020 17:57:57 +0200 Subject: [PATCH 07/11] Extract and improve getAllContractState tests --- packages/cosmwasm/src/lcdapi/wasm.spec.ts | 49 +++++++++++++---------- packages/cosmwasm/src/testutils.spec.ts | 3 +- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/packages/cosmwasm/src/lcdapi/wasm.spec.ts b/packages/cosmwasm/src/lcdapi/wasm.spec.ts index d61b4f9063..98c4cbcb0e 100644 --- a/packages/cosmwasm/src/lcdapi/wasm.spec.ts +++ b/packages/cosmwasm/src/lcdapi/wasm.spec.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { Sha256 } from "@cosmjs/crypto"; -import { Bech32, fromAscii, fromHex, toAscii, toBase64, toHex } from "@cosmjs/encoding"; +import { Bech32, fromAscii, fromHex, fromUtf8, toAscii, toBase64, toHex } from "@cosmjs/encoding"; import { assertIsPostTxSuccess, AuthExtension, @@ -29,6 +29,7 @@ import { } from "../msgs"; import { alice, + base64Matcher, bech32AddressMatcher, ContractUploadInstructions, deployedErc20, @@ -132,6 +133,7 @@ async function executeContract( describe("WasmExtension", () => { const hackatom = getHackatom(); + const hackatomConfigKey = toAscii("config"); let hackatomCodeId: number | undefined; let hackatomContractAddress: string | undefined; @@ -278,6 +280,28 @@ describe("WasmExtension", () => { }); }); + describe("getAllContractState", () => { + it("can get all state", async () => { + pendingWithoutWasmd(); + assert(hackatomContractAddress); + const client = makeWasmClient(wasmd.endpoint); + const state = await client.wasm.getAllContractState(hackatomContractAddress); + expect(state.length).toEqual(1); + const data = state[0]; + expect(data.key).toEqual(hackatomConfigKey); + const value = JSON.parse(fromUtf8(data.val)); + expect(value.verifier).toMatch(base64Matcher); + expect(value.beneficiary).toMatch(base64Matcher); + }); + + it("is empty for non-existent address", async () => { + const client = makeWasmClient(wasmd.endpoint); + const nonExistentAddress = makeRandomAddress(); + const state = await client.wasm.getAllContractState(nonExistentAddress); + expect(state).toEqual([]); + }); + }); + describe("txsQuery", () => { it("can query by tags (module + code_id)", async () => { pendingWithoutWasmd(); @@ -452,32 +476,13 @@ describe("WasmExtension", () => { describe("contract state", () => { const client = makeWasmClient(wasmd.endpoint); const noContract = makeRandomAddress(); - const expectedKey = toAscii("config"); - - it("can get all state", async () => { - pendingWithoutWasmd(); - assert(hackatomContractAddress); - - // get contract state - const state = await client.wasm.getAllContractState(hackatomContractAddress); - 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.wasm.getAllContractState(noContract); - expect(noContractState).toEqual([]); - }); it("can query by key", async () => { pendingWithoutWasmd(); assert(hackatomContractAddress); // query by one key - const raw = await client.wasm.queryContractRaw(hackatomContractAddress, expectedKey); + const raw = await client.wasm.queryContractRaw(hackatomContractAddress, hackatomConfigKey); assert(raw, "must get result"); const model = JSON.parse(fromAscii(raw)); expect(model.verifier).toBeDefined(); @@ -488,7 +493,7 @@ describe("WasmExtension", () => { expect(missing).toBeNull(); // bad address is null - const noContractModel = await client.wasm.queryContractRaw(noContract, expectedKey); + const noContractModel = await client.wasm.queryContractRaw(noContract, hackatomConfigKey); expect(noContractModel).toBeNull(); }); diff --git a/packages/cosmwasm/src/testutils.spec.ts b/packages/cosmwasm/src/testutils.spec.ts index ab0143139b..8c953ee6d2 100644 --- a/packages/cosmwasm/src/testutils.spec.ts +++ b/packages/cosmwasm/src/testutils.spec.ts @@ -25,7 +25,8 @@ export function makeRandomAddress(): string { } 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}$/; From 53acab4422942d1b48b51259809635ea8af8e193 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 28 Jul 2020 18:01:20 +0200 Subject: [PATCH 08/11] Pull out and improve queryContractRaw --- packages/cosmwasm/src/lcdapi/wasm.spec.ts | 49 ++++++++++++++--------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/packages/cosmwasm/src/lcdapi/wasm.spec.ts b/packages/cosmwasm/src/lcdapi/wasm.spec.ts index 98c4cbcb0e..526abfa172 100644 --- a/packages/cosmwasm/src/lcdapi/wasm.spec.ts +++ b/packages/cosmwasm/src/lcdapi/wasm.spec.ts @@ -302,6 +302,35 @@ describe("WasmExtension", () => { }); }); + describe("queryContractRaw", () => { + it("can query by key", async () => { + pendingWithoutWasmd(); + assert(hackatomContractAddress); + const client = makeWasmClient(wasmd.endpoint); + const raw = await client.wasm.queryContractRaw(hackatomContractAddress, hackatomConfigKey); + assert(raw, "must get result"); + const model = JSON.parse(fromAscii(raw)); + expect(model.verifier).toMatch(base64Matcher); + expect(model.beneficiary).toMatch(base64Matcher); + }); + + it("returns null for missing key", async () => { + pendingWithoutWasmd(); + assert(hackatomContractAddress); + const client = makeWasmClient(wasmd.endpoint); + const info = await client.wasm.queryContractRaw(hackatomContractAddress, fromHex("cafe0dad")); + expect(info).toBeNull(); + }); + + it("returns null for non-existent address", async () => { + pendingWithoutWasmd(); + const client = makeWasmClient(wasmd.endpoint); + const nonExistentAddress = makeRandomAddress(); + const info = await client.wasm.queryContractRaw(nonExistentAddress, hackatomConfigKey); + expect(info).toBeNull(); + }); + }); + describe("txsQuery", () => { it("can query by tags (module + code_id)", async () => { pendingWithoutWasmd(); @@ -477,26 +506,6 @@ describe("WasmExtension", () => { const client = makeWasmClient(wasmd.endpoint); const noContract = makeRandomAddress(); - it("can query by key", async () => { - pendingWithoutWasmd(); - assert(hackatomContractAddress); - - // query by one key - const raw = await client.wasm.queryContractRaw(hackatomContractAddress, hackatomConfigKey); - 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.wasm.queryContractRaw(hackatomContractAddress, fromHex("cafe0dad")); - expect(missing).toBeNull(); - - // bad address is null - const noContractModel = await client.wasm.queryContractRaw(noContract, hackatomConfigKey); - expect(noContractModel).toBeNull(); - }); - it("can make smart queries", async () => { pendingWithoutWasmd(); assert(hackatomContractAddress); From c023179ab4b1fb5b28f208adf65697b2594606f1 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 28 Jul 2020 18:05:26 +0200 Subject: [PATCH 09/11] Pull out and improve queryContractSmart tests --- packages/cosmwasm/src/lcdapi/wasm.spec.ts | 64 ++++++++++++----------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/packages/cosmwasm/src/lcdapi/wasm.spec.ts b/packages/cosmwasm/src/lcdapi/wasm.spec.ts index 526abfa172..0e0b720431 100644 --- a/packages/cosmwasm/src/lcdapi/wasm.spec.ts +++ b/packages/cosmwasm/src/lcdapi/wasm.spec.ts @@ -331,6 +331,39 @@ describe("WasmExtension", () => { }); }); + describe("queryContractSmart", () => { + it("can make smart queries", async () => { + pendingWithoutWasmd(); + assert(hackatomContractAddress); + const client = makeWasmClient(wasmd.endpoint); + const request = { verifier: {} }; + const response = await client.wasm.queryContractSmart(hackatomContractAddress, request); + expect(response).toEqual({ verifier: alice.address0 }); + }); + + it("throws for invalid query requests", async () => { + pendingWithoutWasmd(); + assert(hackatomContractAddress); + const client = makeWasmClient(wasmd.endpoint); + const request = { nosuchkey: {} }; + await client.wasm.queryContractSmart(hackatomContractAddress, request).then( + () => fail("shouldn't succeed"), + (error) => expect(error).toMatch(/query wasm contract failed: parsing hackatom::contract::QueryMsg/), + ); + }); + + it("throws for non-existent address", async () => { + pendingWithoutWasmd(); + const client = makeWasmClient(wasmd.endpoint); + const nonExistentAddress = makeRandomAddress(); + const request = { verifier: {} }; + await client.wasm.queryContractSmart(nonExistentAddress, request).then( + () => fail("shouldn't succeed"), + (error) => expect(error).toMatch("not found"), + ); + }); + }); + describe("txsQuery", () => { it("can query by tags (module + code_id)", async () => { pendingWithoutWasmd(); @@ -500,35 +533,4 @@ describe("WasmExtension", () => { } }); }); - - describe("query", () => { - describe("contract state", () => { - const client = makeWasmClient(wasmd.endpoint); - const noContract = makeRandomAddress(); - - it("can make smart queries", async () => { - pendingWithoutWasmd(); - assert(hackatomContractAddress); - - // we can query the verifier properly - const resultDocument = await client.wasm.queryContractSmart(hackatomContractAddress, { - verifier: {}, - }); - expect(resultDocument).toEqual({ verifier: alice.address0 }); - - // invalid query syntax throws an error - await client.wasm.queryContractSmart(hackatomContractAddress, { 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.wasm.queryContractSmart(noContract, { verifier: {} }).then( - () => fail("shouldn't succeed"), - (error) => expect(error).toMatch("not found"), - ); - }); - }); - }); }); From 7f7537bd59227dca2802f4d7e666593d077ca183 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 28 Jul 2020 18:11:18 +0200 Subject: [PATCH 10/11] Improve ContractCodeHistoryEntry.operation --- packages/cosmwasm/src/cosmwasmclient.ts | 3 ++- packages/cosmwasm/src/lcdapi/wasm.ts | 4 ++-- packages/cosmwasm/types/cosmwasmclient.d.ts | 3 ++- packages/cosmwasm/types/lcdapi/wasm.d.ts | 3 ++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/cosmwasm/src/cosmwasmclient.ts b/packages/cosmwasm/src/cosmwasmclient.ts index 1f356b1d07..358c0fda0b 100644 --- a/packages/cosmwasm/src/cosmwasmclient.ts +++ b/packages/cosmwasm/src/cosmwasmclient.ts @@ -118,7 +118,8 @@ export interface Contract { } export interface ContractCodeHistoryEntry { - readonly operation: string; + /** The source of this history entry */ + readonly operation: "Genesis" | "Init" | "Migrate"; readonly codeId: number; readonly msg: object; } diff --git a/packages/cosmwasm/src/lcdapi/wasm.ts b/packages/cosmwasm/src/lcdapi/wasm.ts index 330eddfa1f..22edd2b237 100644 --- a/packages/cosmwasm/src/lcdapi/wasm.ts +++ b/packages/cosmwasm/src/lcdapi/wasm.ts @@ -54,8 +54,8 @@ export interface ContractInfo { // An entry in the contracts code/ migration history export interface ContractCodeHistoryEntry { - // operation can be "Init", "Migrate", "Genesis" - readonly operation: string; + /** The source of this history entry */ + readonly operation: "Genesis" | "Init" | "Migrate"; readonly code_id: number; readonly msg: object; } diff --git a/packages/cosmwasm/types/cosmwasmclient.d.ts b/packages/cosmwasm/types/cosmwasmclient.d.ts index 65d2ea2fbf..a45dd0a512 100644 --- a/packages/cosmwasm/types/cosmwasmclient.d.ts +++ b/packages/cosmwasm/types/cosmwasmclient.d.ts @@ -85,7 +85,8 @@ export interface Contract { readonly label: string; } export interface ContractCodeHistoryEntry { - readonly operation: string; + /** The source of this history entry */ + readonly operation: "Genesis" | "Init" | "Migrate"; readonly codeId: number; readonly msg: object; } diff --git a/packages/cosmwasm/types/lcdapi/wasm.d.ts b/packages/cosmwasm/types/lcdapi/wasm.d.ts index e01f0d4212..621eac1bff 100644 --- a/packages/cosmwasm/types/lcdapi/wasm.d.ts +++ b/packages/cosmwasm/types/lcdapi/wasm.d.ts @@ -34,7 +34,8 @@ export interface ContractInfo { readonly label: string; } export interface ContractCodeHistoryEntry { - readonly operation: string; + /** The source of this history entry */ + readonly operation: "Genesis" | "Init" | "Migrate"; readonly code_id: number; readonly msg: object; } From e73ebfc46d7fb55d8a96d109dad238089cf41323 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 28 Jul 2020 18:11:51 +0200 Subject: [PATCH 11/11] Add CHANGELOG note on missing ContractDetails type --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92c48ccda9..f054fab157 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ and `.postTx`. - @cosmjs/cosmwasm: Use `*PostTx*` types and helpers from @cosmjs/sdk38. Remove exported `PostTxResult`. +- @cosmjs/cosmwasm: `ContractDetails` was removed in favour of just `Contract`. + The missing `init_msg` is now available via the contract's code history (see + `getContractCodeHistory`). - @cosmjs/sdk38: Rename `CosmosClient.getNonce` method to `.getSequence`. - @cosmjs/sdk38: Remove `RestClient` class in favour of new modular `LcdClient` class.