diff --git a/packages/sdk/package.json b/packages/sdk/package.json index ff5ec0125d..81fc4ce588 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -41,8 +41,10 @@ "@iov/crypto": "^2.0.2", "@iov/encoding": "^2.0.2", "@iov/utils": "^2.0.2", + "@types/pako": "^1.0.1", "axios": "^0.19.0", - "bn.js": "^5.1.1" + "bn.js": "^5.1.1", + "pako": "^1.0.11" }, "devDependencies": { "@types/bn.js": "^4.11.6", diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts index d362eed9ce..0f68f2642c 100644 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ b/packages/sdk/src/cosmwasmclient.spec.ts @@ -321,7 +321,7 @@ describe("CosmWasmClient", () => { pendingWithoutWasmd(); const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); const client = new SigningCosmWasmClient(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); - const codeId = await client.upload(getRandomizedHackatom()); + const { codeId } = await client.upload(getRandomizedHackatom()); const initMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() }; const contractAddress = await client.instantiate(codeId, initMsg); contract = { initMsg: initMsg, address: contractAddress }; @@ -372,7 +372,7 @@ describe("CosmWasmClient", () => { pendingWithoutWasmd(); const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); const client = new SigningCosmWasmClient(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); - const codeId = await client.upload(getRandomizedHackatom()); + const { codeId } = await client.upload(getRandomizedHackatom()); const initMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() }; const contractAddress = await client.instantiate(codeId, initMsg); contract = { initMsg: initMsg, address: contractAddress }; diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index ca7ceb27e4..be21fbe57b 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -28,4 +28,9 @@ export { decodeSignature, makeSecp256k1SignatureFromFixedLength, } from "./signature"; -export { SigningCallback, SigningCosmWasmClient, ExecuteResult } from "./signingcosmwasmclient"; +export { + SigningCallback, + SigningCosmWasmClient, + ExecuteResult, + UploadReceipt, +} from "./signingcosmwasmclient"; diff --git a/packages/sdk/src/signingcosmwasmclient.spec.ts b/packages/sdk/src/signingcosmwasmclient.spec.ts index 295e7c6ec6..70da832d9f 100644 --- a/packages/sdk/src/signingcosmwasmclient.spec.ts +++ b/packages/sdk/src/signingcosmwasmclient.spec.ts @@ -1,3 +1,5 @@ +import { Sha256 } from "@iov/crypto"; +import { Encoding } from "@iov/encoding"; import { assert } from "@iov/utils"; import { Secp256k1Pen } from "./pen"; @@ -6,6 +8,8 @@ import { SigningCosmWasmClient } from "./signingcosmwasmclient"; import { getRandomizedHackatom, makeRandomAddress, pendingWithoutWasmd } from "./testutils.spec"; import { Coin } from "./types"; +const { toHex } = Encoding; + const httpUrl = "http://localhost:1317"; const faucet = { @@ -32,7 +36,18 @@ describe("SigningCosmWasmClient", () => { pendingWithoutWasmd(); const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); const client = new SigningCosmWasmClient(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); - const codeId = await client.upload(getRandomizedHackatom()); + const wasm = getRandomizedHackatom(); + const { + codeId, + originalChecksum, + originalSize, + compressedChecksum, + compressedSize, + } = await client.upload(wasm); + expect(originalChecksum).toEqual(toHex(new Sha256(wasm).digest())); + expect(originalSize).toEqual(wasm.length); + expect(compressedChecksum).toMatch(/^[0-9a-f]{64}$/); + expect(compressedSize).toBeLessThan(wasm.length * 0.5); expect(codeId).toBeGreaterThanOrEqual(1); }); }); @@ -42,7 +57,7 @@ describe("SigningCosmWasmClient", () => { pendingWithoutWasmd(); const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); const client = new SigningCosmWasmClient(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); - const codeId = await client.upload(getRandomizedHackatom()); + const { codeId } = await client.upload(getRandomizedHackatom()); const transferAmount: readonly Coin[] = [ { @@ -74,7 +89,7 @@ describe("SigningCosmWasmClient", () => { pendingWithoutWasmd(); const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); const client = new SigningCosmWasmClient(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); - const codeId = await client.upload(getRandomizedHackatom()); + const { codeId } = await client.upload(getRandomizedHackatom()); const contractAddress1 = await client.instantiate(codeId, { verifier: faucet.address, @@ -93,7 +108,7 @@ describe("SigningCosmWasmClient", () => { pendingWithoutWasmd(); const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); const client = new SigningCosmWasmClient(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); - const codeId = await client.upload(getRandomizedHackatom()); + const { codeId } = await client.upload(getRandomizedHackatom()); // instantiate const transferAmount: readonly Coin[] = [ diff --git a/packages/sdk/src/signingcosmwasmclient.ts b/packages/sdk/src/signingcosmwasmclient.ts index 3e2c888a73..c8a6999e59 100644 --- a/packages/sdk/src/signingcosmwasmclient.ts +++ b/packages/sdk/src/signingcosmwasmclient.ts @@ -1,4 +1,6 @@ +import { Sha256 } from "@iov/crypto"; import { Encoding } from "@iov/encoding"; +import pako from "pako"; import { CosmWasmClient, GetNonceResult, PostTxResult } from "./cosmwasmclient"; import { makeSignBytes, marshalTx } from "./encoding"; @@ -49,6 +51,19 @@ const defaultFees: FeeTable = { }, }; +export interface UploadReceipt { + /** Size of the original wasm code in bytes */ + readonly originalSize: number; + /** A hex encoded sha256 checksum of the original wasm code (that is stored on chain) */ + readonly originalChecksum: string; + /** Size of the compressed wasm code in bytes */ + readonly compressedSize: number; + /** A hex encoded sha256 checksum of the compressed wasm code (that stored in the transaction) */ + readonly compressedChecksum: string; + /** The ID of the code asigned by the chain */ + readonly codeId: number; +} + export interface ExecuteResult { readonly logs: readonly Log[]; } @@ -80,14 +95,15 @@ export class SigningCosmWasmClient extends CosmWasmClient { return super.getAccount(address || this.senderAddress); } - /** Uploads code and returns a code ID */ - public async upload(wasmCode: Uint8Array, memo = ""): Promise { + /** Uploads code and returns a receipt, including the code ID */ + public async upload(wasmCode: Uint8Array, memo = ""): Promise { + const compressed = pako.gzip(wasmCode, { level: 9 }); const storeCodeMsg: MsgStoreCode = { type: "wasm/store-code", value: { sender: this.senderAddress, // eslint-disable-next-line @typescript-eslint/camelcase - wasm_byte_code: Encoding.toBase64(wasmCode), + wasm_byte_code: Encoding.toBase64(compressed), source: "", builder: "", }, @@ -106,8 +122,13 @@ export class SigningCosmWasmClient extends CosmWasmClient { const result = await this.postTx(marshalTx(signedTx)); const codeIdAttr = findAttribute(result.logs, "message", "code_id"); - const codeId = Number.parseInt(codeIdAttr.value, 10); - return codeId; + return { + originalSize: wasmCode.length, + originalChecksum: Encoding.toHex(new Sha256(wasmCode).digest()), + compressedSize: compressed.length, + compressedChecksum: Encoding.toHex(new Sha256(compressed).digest()), + codeId: Number.parseInt(codeIdAttr.value, 10), + }; } public async instantiate( diff --git a/packages/sdk/types/index.d.ts b/packages/sdk/types/index.d.ts index dd2ad348f7..8ee2e6f914 100644 --- a/packages/sdk/types/index.d.ts +++ b/packages/sdk/types/index.d.ts @@ -27,4 +27,9 @@ export { decodeSignature, makeSecp256k1SignatureFromFixedLength, } from "./signature"; -export { SigningCallback, SigningCosmWasmClient, ExecuteResult } from "./signingcosmwasmclient"; +export { + SigningCallback, + SigningCosmWasmClient, + ExecuteResult, + UploadReceipt, +} from "./signingcosmwasmclient"; diff --git a/packages/sdk/types/signingcosmwasmclient.d.ts b/packages/sdk/types/signingcosmwasmclient.d.ts index 70a20ee674..4e9c352876 100644 --- a/packages/sdk/types/signingcosmwasmclient.d.ts +++ b/packages/sdk/types/signingcosmwasmclient.d.ts @@ -11,6 +11,18 @@ export interface FeeTable { readonly exec: StdFee; readonly send: StdFee; } +export interface UploadReceipt { + /** Size of the original wasm code in bytes */ + readonly originalSize: number; + /** A hex encoded sha256 checksum of the original wasm code (that is stored on chain) */ + readonly originalChecksum: string; + /** Size of the compressed wasm code in bytes */ + readonly compressedSize: number; + /** A hex encoded sha256 checksum of the compressed wasm code (that stored in the transaction) */ + readonly compressedChecksum: string; + /** The ID of the code asigned by the chain */ + readonly codeId: number; +} export interface ExecuteResult { readonly logs: readonly Log[]; } @@ -27,8 +39,8 @@ export declare class SigningCosmWasmClient extends CosmWasmClient { ); getNonce(address?: string): Promise; getAccount(address?: string): Promise; - /** Uploads code and returns a code ID */ - upload(wasmCode: Uint8Array, memo?: string): Promise; + /** Uploads code and returns a receipt, including the code ID */ + upload(wasmCode: Uint8Array, memo?: string): Promise; instantiate( codeId: number, initMsg: object, diff --git a/scripts/wasmd/deploy_erc20.js b/scripts/wasmd/deploy_erc20.js index 4a13cbef16..770f6e2ca8 100755 --- a/scripts/wasmd/deploy_erc20.js +++ b/scripts/wasmd/deploy_erc20.js @@ -72,12 +72,12 @@ async function main() { const client = new SigningCosmWasmClient(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); const wasm = fs.readFileSync(__dirname + "/contracts/cw-erc20.wasm"); - const codeId = await client.upload(wasm, "Upload ERC20 contract"); - console.info(`Upload succeeded. Code ID is ${codeId}`); + const uploadReceipt = await client.upload(wasm, "Upload ERC20 contract"); + console.info(`Upload succeeded. Receipt: ${JSON.stringify(uploadReceipt)}`); for (const initMsg of [initMsgHash, initMsgIsa, initMsgJade]) { const memo = `Create an ERC20 instance for ${initMsg.symbol}`; - const contractAddress = await client.instantiate(codeId, initMsg, memo); + const contractAddress = await client.instantiate(uploadReceipt.codeId, initMsg, memo); console.info(`Contract instantiated for ${initMsg.symbol} at ${contractAddress}`); } } diff --git a/yarn.lock b/yarn.lock index 3c1000d69b..39e36fe2ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1125,6 +1125,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.13.tgz#ccebcdb990bd6139cd16e84c39dc2fb1023ca90c" integrity sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg== +"@types/pako@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/pako/-/pako-1.0.1.tgz#33b237f3c9aff44d0f82fe63acffa4a365ef4a61" + integrity sha512-GdZbRSJ3Cv5fiwT6I0SQ3ckeN2PWNqxd26W9Z2fCK1tGrrasGy4puvNFtnddqH9UJFMQYXxEuuB7B8UK+LLwSg== + "@types/random-js@^1.0.31": version "1.0.31" resolved "https://registry.yarnpkg.com/@types/random-js/-/random-js-1.0.31.tgz#18a8bcc075afa504421e638fcbe021f27e802941" @@ -5964,6 +5969,11 @@ p-waterfall@^1.0.0: dependencies: p-reduce "^1.0.0" +pako@^1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + pako@~1.0.5: version "1.0.10" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732"