Skip to content

Commit

Permalink
BREAKING: refactor(agent,cw,scrt): saner upload method
Browse files Browse the repository at this point in the history
- common upload, abstract chain-specific doUpload
- type gymnastics on Batch
- can now pass string (file path), URL (file: or other),
  Uint8Array, or Uploadable to agent.upload
  • Loading branch information
egasimus committed Oct 24, 2023
1 parent 9aae1a3 commit 2bf7a40
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 22 deletions.
60 changes: 50 additions & 10 deletions agent/agent-chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,8 +486,44 @@ export abstract class Agent {
/** Send native tokens to multiple recipients. */
abstract sendMany (outputs: [Address, ICoin[]][], opts?: ExecOpts): Promise<void|unknown>

/** Upload code, generating a new code id/hash pair. */
abstract upload (data: Uint8Array, meta?: Partial<Uploadable>): Promise<Uploaded>
/** Upload a contract's code, generating a new code id/hash pair. */
async upload (uploadable: string|URL|Uint8Array|Partial<Uploadable>): Promise<Uploaded> {
const fromPath = async (path: string) => {
const { readFile } = await import('node:fs/promises')
return await readFile(path)
}
const fromURL = async (url: URL) => {
if (url.protocol === 'file:') {
const { fileURLToPath } = await import('node:url')
return await fromPath(fileURLToPath(url))
} else {
return new Uint8Array(await (await fetch(url)).arrayBuffer())
}
}
let data: Uint8Array
const t0 = + new Date()
if (typeof uploadable === 'string') {
data = await fromPath(uploadable)
} else if (uploadable instanceof URL) {
data = await fromURL(uploadable)
} else if (uploadable instanceof Uint8Array) {
data = uploadable
} else if (uploadable.artifact) {
uploadable = uploadable.artifact
if (typeof uploadable === 'string') {
data = await fromPath(uploadable)
} else if (uploadable instanceof URL) {
data = await fromURL(uploadable)
}
} else {
throw new Error('Invalid argument passed to Agent#upload')
}
const result = this.doUpload(data!)
this.log.debug(`Uploaded in ${t0}msec:`, result)
return result
}

protected abstract doUpload (data: Uint8Array): Promise<Uploaded>

/** Get an uploader instance which performs code uploads and optionally caches them. */
getUploader <U extends Uploader> ($U: UploaderClass<U>, options?: Partial<U>): U {
Expand Down Expand Up @@ -578,7 +614,7 @@ export class StubAgent extends Agent {
}

/** Stub implementation of code upload. */
upload (data: Uint8Array, meta?: Partial<Uploadable>): Promise<Uploaded> {
protected doUpload (data: Uint8Array): Promise<Uploaded> {
this.log.warn('Agent#upload: this function is stub; use a subclass of Agent')
return Promise.resolve({
chainId: this.chain!.id,
Expand Down Expand Up @@ -618,12 +654,13 @@ export function assertAgent <A extends Agent> (thing: { agent?: A|null } = {}):
/** A constructor for a Batch subclass. */
export interface BatchClass<B extends Batch> extends Class<B, ConstructorParameters<typeof Batch>>{}

/** Batch is an alternate executor that collects collects messages to broadcast
* as a single transaction in order to execute them simultaneously. For that, it
* uses the API of its parent Agent. You can use it in scripts with:
* await agent.batch().wrap(async batch=>{ client.as(batch).exec(...) })
* */
export abstract class Batch implements Agent {
type BatchAgent = Omit<Agent, 'doUpload'|'ready'> & { ready: Promise<Batch> }

/** Batch is an alternate executor that collects messages to broadcast
* as a single transaction in order to execute them simultaneously.
* For that, it uses the API of its parent Agent. You can use it in scripts with:
* await agent.batch().wrap(async batch=>{ client.as(batch).exec(...) }) */
export abstract class Batch implements BatchAgent {
/** Messages in this batch, unencrypted. */
msgs: any[] = []
/** Next message id. */
Expand Down Expand Up @@ -777,7 +814,10 @@ export abstract class Batch implements Agent {
/** Uploads are disallowed in the middle of a batch because
* it's easy to go over the max request size, and
* difficult to know what that is in advance. */
async upload (data: Uint8Array, meta?: Partial<Uploadable>): Promise<never> {
async upload (data: Uint8Array): Promise<never> {
throw new Error.Invalid.Batching("upload")
}
async doUpload (data: Uint8Array): Promise<never> {
throw new Error.Invalid.Batching("upload")
}
/** Uploads are disallowed in the middle of a batch because
Expand Down
3 changes: 1 addition & 2 deletions agent/agent-services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ export abstract class Builder {
* Builder implementations override this, though. */
abstract buildMany (sources: (string|Buildable)[], ...args: unknown[]): Promise<Built[]>
}

/** Uploader: uploads a `Template`'s `artifact` to a specific `Chain`,
* binding the `Template` to a particular `chainId` and `codeId`. */
export class Uploader {
Expand Down Expand Up @@ -180,7 +179,7 @@ export class Uploader {
const log = new Console(`${contract.codeHash} -> ${this.agent.chain?.id??'(unknown chain id)'}`)
log(`from ${bold(contract.artifact)}`)
log(`${bold(String(data.length))} bytes (uncompressed)`)
const result = await this.agent.upload(data, contract)
const result = await this.agent.upload(contract)
this.checkCodeHash(contract, result)
const { codeId, codeHash, uploadTx } = result
log(`done, code id`, codeId)
Expand Down
2 changes: 1 addition & 1 deletion connect/cw/cw-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ class CWAgent extends Agent {
throw new Error('not implemented')
}

async upload (data: Uint8Array, meta?: Partial<Uploadable>): Promise<Uploaded> {
protected async doUpload (data: Uint8Array): Promise<Uploaded> {
const { api } = await this.ready
if (!this.address) throw new Error.Missing.Address()
const result = await api.upload(
Expand Down
13 changes: 6 additions & 7 deletions connect/scrt/scrt-chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
import type {
AgentClass, Built, Uploaded, AgentFees, ChainClass, Uint128, BatchClass, Client,
ExecOpts, ICoin, Message, Name, AnyContract, Address, TxHash, ChainId, CodeId, CodeHash, Label,
Instantiated
Instantiated, Uploadable
} from '@fadroma/agent'

/** Represents a Secret Network API endpoint. */
Expand Down Expand Up @@ -141,10 +141,10 @@ class ScrtChain extends Chain {
...options||{},
}) as ScrtChain

/** Connect to a Secret Network devnet. */
static devnet = (options: Partial<ScrtChain> = {}): ScrtChain => super.devnet({
...options||{},
}) as ScrtChain
/** Connect to Secret Network in testnet mode. */
static devnet = (options: Partial<ScrtChain> = {}): ScrtChain => {
throw new Error('Devnet not installed. Import @hackbg/fadroma')
}

/** Connect to a Secret Network mocknet. */
static mocknet = (options: Partial<Mocknet.Chain> = {}): Mocknet.Chain => new Mocknet.Chain({
Expand Down Expand Up @@ -357,7 +357,7 @@ class ScrtAgent extends Agent {
}

/** Upload a WASM binary. */
async upload (data: Uint8Array): Promise<Uploaded> {
protected async doUpload (data: Uint8Array): Promise<Uploaded> {
const { api } = await this.ready
type Log = { type: string, key: string }
if (!this.address) throw new Error.Missing.Address()
Expand Down Expand Up @@ -385,7 +385,6 @@ class ScrtAgent extends Agent {
this.log.error(`Code id not found in result.`)
throw new Error.Failed.Upload({ ...result, noCodeId: true })
}
this.log.debug(`gas used for upload of ${data.length} bytes:`, result.gasUsed)
return {
chainId: assertChain(this).id,
codeId,
Expand Down
4 changes: 2 additions & 2 deletions connect/scrt/scrt-mocknet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,8 @@ class MocknetAgent extends Agent {
}

/** Upload a binary to the mocknet. */
async upload (wasm: Uint8Array, meta?: Partial<Uploadable>): Promise<Uploaded> {
return new Contract(await this.chain.upload(wasm, meta)) as unknown as Uploaded
protected async doUpload (wasm: Uint8Array): Promise<Uploaded> {
return new Contract(await this.chain.upload(wasm)) as unknown as Uploaded
}
/** Instantiate a contract on the mocknet. */
async instantiate <C extends Client> (instance: Contract<C>): Promise<Instantiated> {
Expand Down

0 comments on commit 2bf7a40

Please sign in to comment.