diff --git a/.pnp.js b/.pnp.js index caf080db..c013dda2 100755 --- a/.pnp.js +++ b/.pnp.js @@ -3398,7 +3398,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageDependencies": [ ["@cosmjs/cosmwasm-stargate", "workspace:packages/cosmwasm-stargate"], ["@cosmjs/amino", "workspace:packages/amino"], - ["@cosmjs/cosmwasm-launchpad", "workspace:packages/cosmwasm-launchpad"], ["@cosmjs/crypto", "workspace:packages/crypto"], ["@cosmjs/encoding", "workspace:packages/encoding"], ["@cosmjs/math", "workspace:packages/math"], diff --git a/CHANGELOG.md b/CHANGELOG.md index 62517f86..bfcad33c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,11 @@ and this project adheres to available under `tendermint33`. - @cosmjs/proto-signing and @cosmjs/stargate: Create a Stargate-ready `parseCoins` that replaces the `parseCoins` re-export from `@cosmjs/amino`. +- @cosmjs/cosmwasm-stargate: Export `isValidBuilder`, which is a clone of + `isValidBuilder` from @cosmjs/cosmwasm-launchpad. +- @cosmjs/cosmwasm-stargate: Copy symbols `Code`, `CodeDetails`, `Contract`, + `ContractCodeHistoryEntry` and `JsonObject` from @cosmjs/cosmwasm-launchpad + and remove dependency on @cosmjs/cosmwasm-launchpad. ### Changed diff --git a/packages/cosmwasm-launchpad/src/cosmwasmclient.ts b/packages/cosmwasm-launchpad/src/cosmwasmclient.ts index 1f7324e1..3915971b 100644 --- a/packages/cosmwasm-launchpad/src/cosmwasmclient.ts +++ b/packages/cosmwasm-launchpad/src/cosmwasmclient.ts @@ -95,7 +95,7 @@ export interface Code { } export interface CodeDetails extends Code { - /** The original wasm bytes */ + /** The original Wasm bytes */ readonly data: Uint8Array; } diff --git a/packages/cosmwasm-stargate/package.json b/packages/cosmwasm-stargate/package.json index 8e21300e..3333a1b4 100644 --- a/packages/cosmwasm-stargate/package.json +++ b/packages/cosmwasm-stargate/package.json @@ -38,7 +38,6 @@ }, "dependencies": { "@cosmjs/amino": "workspace:packages/amino", - "@cosmjs/cosmwasm-launchpad": "workspace:packages/cosmwasm-launchpad", "@cosmjs/crypto": "workspace:packages/crypto", "@cosmjs/encoding": "workspace:packages/encoding", "@cosmjs/math": "workspace:packages/math", diff --git a/packages/cosmwasm-stargate/src/builder.spec.ts b/packages/cosmwasm-stargate/src/builder.spec.ts new file mode 100644 index 00000000..20926f4c --- /dev/null +++ b/packages/cosmwasm-stargate/src/builder.spec.ts @@ -0,0 +1,63 @@ +import { isValidBuilder } from "./builder"; + +describe("builder", () => { + describe("isValidBuilder", () => { + // Valid cases + + it("returns true for simple examples", () => { + expect(isValidBuilder("myorg/super-optimizer:0.1.2")).toEqual(true); + expect(isValidBuilder("myorg/super-optimizer:42")).toEqual(true); + }); + + it("supports images with multi level names", () => { + expect(isValidBuilder("myorg/department-x/office-y/technology-z/super-optimizer:0.1.2")).toEqual(true); + }); + + it("returns true for tags with lower and upper chars", () => { + expect(isValidBuilder("myorg/super-optimizer:0.1.2-alpha")).toEqual(true); + expect(isValidBuilder("myorg/super-optimizer:0.1.2-Alpha")).toEqual(true); + }); + + // Invalid cases + + it("returns false for missing or empty tag", () => { + expect(isValidBuilder("myorg/super-optimizer")).toEqual(false); + expect(isValidBuilder("myorg/super-optimizer:")).toEqual(false); + }); + + it("returns false for name components starting or ending with a separator", () => { + expect(isValidBuilder(".myorg/super-optimizer:42")).toEqual(false); + expect(isValidBuilder("-myorg/super-optimizer:42")).toEqual(false); + expect(isValidBuilder("_myorg/super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg./super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg-/super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg_/super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg/.super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg/-super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg/_super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg/super-optimizer.:42")).toEqual(false); + expect(isValidBuilder("myorg/super-optimizer-:42")).toEqual(false); + expect(isValidBuilder("myorg/super-optimizer_:42")).toEqual(false); + }); + + it("returns false for upper case character in name component", () => { + expect(isValidBuilder("mYorg/super-optimizer:42")).toEqual(false); + expect(isValidBuilder("myorg/super-Optimizer:42")).toEqual(false); + }); + + it("returns false for long images", () => { + expect( + isValidBuilder( + "myorgisnicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenicenice/super-optimizer:42", + ), + ).toEqual(false); + }); + + it("returns false for images with no organization", () => { + // Those are valid dockerhub images from https://hub.docker.com/_/ubuntu and https://hub.docker.com/_/rust + // but not valid in the context of CosmWasm Verify + expect(isValidBuilder("ubuntu:xenial-20200212")).toEqual(false); + expect(isValidBuilder("rust:1.40.0")).toEqual(false); + }); + }); +}); diff --git a/packages/cosmwasm-stargate/src/builder.ts b/packages/cosmwasm-stargate/src/builder.ts new file mode 100644 index 00000000..31a790c7 --- /dev/null +++ b/packages/cosmwasm-stargate/src/builder.ts @@ -0,0 +1,20 @@ +// A docker image regexp. We remove support for non-standard registries for simplicity. +// https://docs.docker.com/engine/reference/commandline/tag/#extended-description +// +// An image name is made up of slash-separated name components (optionally prefixed by a registry hostname). +// Name components may contain lowercase characters, digits and separators. +// A separator is defined as a period, one or two underscores, or one or more dashes. A name component may not start or end with a separator. +// +// A tag name must be valid ASCII and may contain lowercase and uppercase letters, digits, underscores, periods and dashes. +// A tag name may not start with a period or a dash and may contain a maximum of 128 characters. +const dockerImagePattern = new RegExp( + "^[a-z0-9][a-z0-9._-]*[a-z0-9](/[a-z0-9][a-z0-9._-]*[a-z0-9])+:[a-zA-Z0-9_][a-zA-Z0-9_.-]{0,127}$", +); + +/** Max length in bytes/characters (regexp enforces all ASCII, even if that is not required by the standard) */ +const builderMaxLength = 128; + +export function isValidBuilder(builder: string): boolean { + if (builder.length > builderMaxLength) return false; + return !!builder.match(dockerImagePattern); +} diff --git a/packages/cosmwasm-stargate/src/cosmwasmclient.spec.ts b/packages/cosmwasm-stargate/src/cosmwasmclient.spec.ts index 3092ded5..83647705 100644 --- a/packages/cosmwasm-stargate/src/cosmwasmclient.spec.ts +++ b/packages/cosmwasm-stargate/src/cosmwasmclient.spec.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { Code } from "@cosmjs/cosmwasm-launchpad"; import { sha256 } from "@cosmjs/crypto"; import { fromAscii, fromBase64, fromHex, toAscii } from "@cosmjs/encoding"; import { Int53 } from "@cosmjs/math"; @@ -16,7 +15,7 @@ import { assert, sleep } from "@cosmjs/utils"; import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx"; import { ReadonlyDate } from "readonly-date"; -import { CosmWasmClient, PrivateCosmWasmClient } from "./cosmwasmclient"; +import { Code, CosmWasmClient, PrivateCosmWasmClient } from "./cosmwasmclient"; import { SigningCosmWasmClient } from "./signingcosmwasmclient"; import { alice, diff --git a/packages/cosmwasm-stargate/src/cosmwasmclient.ts b/packages/cosmwasm-stargate/src/cosmwasmclient.ts index 147ead52..c0529b86 100644 --- a/packages/cosmwasm-stargate/src/cosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/cosmwasmclient.ts @@ -1,11 +1,4 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { - Code, - CodeDetails, - Contract, - ContractCodeHistoryEntry, - JsonObject, -} from "@cosmjs/cosmwasm-launchpad"; import { fromAscii, toHex } from "@cosmjs/encoding"; import { Uint53 } from "@cosmjs/math"; import { @@ -33,18 +26,56 @@ import { assert, sleep } from "@cosmjs/utils"; import { CodeInfoResponse } from "cosmjs-types/cosmwasm/wasm/v1beta1/query"; import { ContractCodeHistoryOperationType } from "cosmjs-types/cosmwasm/wasm/v1beta1/types"; -import { setupWasmExtension, WasmExtension } from "./queries"; +import { JsonObject, setupWasmExtension, WasmExtension } from "./queries"; -// Those types can be copied over to allow them to evolve independently of @cosmjs/cosmwasm-launchpad. -// For now just re-export them such that they can be imported via @cosmjs/cosmwasm-stargate. +// Re-exports that belong to public CosmWasmClient interfaces export { - Code, // returned by CosmWasmClient.getCode - CodeDetails, // returned by CosmWasmClient.getCodeDetails - Contract, // returned by CosmWasmClient.getContract - ContractCodeHistoryEntry, // returned by CosmWasmClient.getContractCodeHistory JsonObject, // returned by CosmWasmClient.queryContractSmart }; +export interface Code { + readonly id: number; + /** Bech32 account address */ + readonly creator: string; + /** Hex-encoded sha256 hash of the code stored here */ + readonly checksum: 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 Code { + /** The original Wasm bytes */ + readonly data: Uint8Array; +} + +export interface Contract { + readonly address: string; + readonly codeId: number; + /** Bech32 account address */ + readonly creator: string; + /** Bech32-encoded admin address */ + readonly admin: string | undefined; + readonly label: string; +} + +export interface ContractCodeHistoryEntry { + /** The source of this history entry */ + readonly operation: "Genesis" | "Init" | "Migrate"; + readonly codeId: number; + readonly msg: Record; +} + /** Use for testing only */ export interface PrivateCosmWasmClient { readonly tmClient: Tendermint34Client | undefined; diff --git a/packages/cosmwasm-stargate/src/index.ts b/packages/cosmwasm-stargate/src/index.ts index e90bbc57..c02bdae8 100644 --- a/packages/cosmwasm-stargate/src/index.ts +++ b/packages/cosmwasm-stargate/src/index.ts @@ -1,4 +1,5 @@ export { cosmWasmTypes } from "./aminotypes"; +export { isValidBuilder } from "./builder"; export { Code, CodeDetails, diff --git a/packages/cosmwasm-stargate/src/queries/index.ts b/packages/cosmwasm-stargate/src/queries/index.ts index c0692523..c33027c9 100644 --- a/packages/cosmwasm-stargate/src/queries/index.ts +++ b/packages/cosmwasm-stargate/src/queries/index.ts @@ -1 +1 @@ -export { setupWasmExtension, WasmExtension } from "./wasm"; +export { setupWasmExtension, JsonObject, WasmExtension } from "./wasm"; diff --git a/packages/cosmwasm-stargate/src/queries/wasm.ts b/packages/cosmwasm-stargate/src/queries/wasm.ts index 579ab1d0..229a0cb8 100644 --- a/packages/cosmwasm-stargate/src/queries/wasm.ts +++ b/packages/cosmwasm-stargate/src/queries/wasm.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { JsonObject } from "@cosmjs/cosmwasm-launchpad"; import { fromUtf8, toAscii } from "@cosmjs/encoding"; import { createPagination, createProtobufRpcClient, QueryClient } from "@cosmjs/stargate"; import { @@ -14,6 +13,14 @@ import { } from "cosmjs-types/cosmwasm/wasm/v1beta1/query"; import Long from "long"; +/** + * An object containing a parsed JSON document. The result of JSON.parse(). + * This doesn't provide any type safety over `any` but expresses intent in the code. + * + * This type is returned by `queryContractSmart`. + */ +export type JsonObject = any; + export interface WasmExtension { readonly wasm: { readonly listCodeInfo: (paginationKey?: Uint8Array) => Promise; diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts index c95632b3..f02a4f84 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { encodeSecp256k1Pubkey, makeSignDoc as makeSignDocAmino } from "@cosmjs/amino"; -import { isValidBuilder } from "@cosmjs/cosmwasm-launchpad"; import { sha256 } from "@cosmjs/crypto"; import { fromBase64, toHex, toUtf8 } from "@cosmjs/encoding"; import { Int53, Uint53 } from "@cosmjs/math"; @@ -47,6 +46,7 @@ import Long from "long"; import pako from "pako"; import { cosmWasmTypes } from "./aminotypes"; +import { isValidBuilder } from "./builder"; import { CosmWasmClient } from "./cosmwasmclient"; import { MsgClearAdminEncodeObject, @@ -57,9 +57,9 @@ import { MsgUpdateAdminEncodeObject, } from "./encodeobjects"; -function prepareBuilder(builder: string | undefined): string { - if (builder === undefined) { - return ""; // normalization needed by backend +function prepareBuilder(builder: string | undefined): string | undefined { + if (!builder) { + return undefined; } else { if (!isValidBuilder(builder)) throw new Error("The builder (Docker Hub image with tag) is not valid"); return builder; diff --git a/yarn.lock b/yarn.lock index a701535e..200e87d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -446,7 +446,6 @@ __metadata: resolution: "@cosmjs/cosmwasm-stargate@workspace:packages/cosmwasm-stargate" dependencies: "@cosmjs/amino": "workspace:packages/amino" - "@cosmjs/cosmwasm-launchpad": "workspace:packages/cosmwasm-launchpad" "@cosmjs/crypto": "workspace:packages/crypto" "@cosmjs/encoding": "workspace:packages/encoding" "@cosmjs/math": "workspace:packages/math"