import "../../../_dnt.polyfills.js"; import { $, ArrayRune, Chain, ChainRune, CodecRune, ExtrinsicRune, hex, is, Rune, RunicArgs, Ss58Rune, transformTys, ValueRune, } from "../../../mod.js" import { $contractsApiInstantiateArgs, $contractsApiInstantiateResult, Weight } from "./codecs.js" import { Callable, InkMetadata, parse } from "./InkMetadata.js" import { InkRune } from "./InkRune.js" // TODO: `onInstantiated` export interface InstantiateProps { code: Uint8Array sender: Uint8Array value?: bigint ctor?: string args?: unknown[] gasLimit?: Weight storageDepositLimit?: bigint salt?: Uint8Array } export class InkMetadataRune extends Rune { static fromMetadataText(...[text]: RunicArgs) { return Rune.resolve(text).map(parse).into(InkMetadataRune) } codecs = this .into(ValueRune) .access("types") .map(transformTys) .access("ids") $event = Rune .tuple([ this.into(ValueRune).access("spec", "events"), this.codecs, ]) .map(([events, codecs]) => $.taggedUnion( "type", events.map((event) => $.variant( event.label, $.object( ...event.args.map((arg) => $.field(arg.label, codecs[arg.type.type]!)) as any, ) as any, ) ), ) ) .into(CodecRune) salt() { return Rune.constant(crypto.getRandomValues(new Uint8Array(4))) } // TODO: protected? encodeData(...args_: RunicArgs) { const [metadata, args] = RunicArgs.resolve(args_) const selector = metadata.access("selector").map(hex.decode) const selectorLength = selector.access("length") const argCodecs = metadata .access("args") .into(ArrayRune) .mapArray((arg) => { return this.codecs.access(arg.access("type", "type")) }) return Rune .tuple([selectorLength, argCodecs]) .map(([selectorLength, argCodecs]) => $.tuple($.sizedUint8Array(selectorLength), ...argCodecs) ) .into(CodecRune) .encoded( Rune .tuple([selector, args]) .map(([selector, args]) => [selector, ...args ?? []]) .unsafeAs(), ) } instantiation( chain: ChainRune, props: RunicArgs, ) { const { code } = props const salt = Rune .resolve(props.salt) .handle(is(undefined), this.salt) const storageDepositLimit = Rune.resolve(props.storageDepositLimit) const value = Rune .resolve(props.value) .handle(is(undefined), () => Rune.constant(0n)) const ctorMetadata = Rune.tuple([ this .into(ValueRune) .access("spec", "constructors"), Rune .resolve(props.ctor) .handle(is(undefined), () => Rune.resolve("new")), ]) .map(([ctors, label]) => ctors.find((ctor) => ctor.label === label)) .unhandle(is(undefined)) .rehandle(is(undefined), () => Rune.constant(new CtorNotFoundError())) .unhandle(is(CtorNotFoundError)) const data = this.encodeData(ctorMetadata, props.args) const instantiateArgs = Rune .constant($contractsApiInstantiateArgs) .into(CodecRune) .encoded(Rune.tuple([ props.sender, value, undefined, storageDepositLimit, Rune.object({ type: "Upload" as const, value: code }), data, salt, ])) const gasLimit = Rune .resolve(props.gasLimit) .handle(is(undefined), () => { const args = instantiateArgs.map(hex.encode) return chain.connection .call("state_call", "ContractsApi_instantiate", args) .map((result) => $contractsApiInstantiateResult.decode(hex.decode(result))) .access("gasRequired") }) return Rune .object({ type: "Contracts", value: Rune.object({ type: "instantiateWithCode", value, gasLimit, storageDepositLimit, code, data, salt, }), }) .unsafeAs>() .into(ExtrinsicRune, chain) } instanceFromAccountId( chain: ChainRune, ...[accountId]: RunicArgs ) { return Rune.resolve(accountId).into(InkRune, chain, this.as(InkMetadataRune)) } instanceFromSs58( chain: ChainRune, ...[ss58]: RunicArgs ) { return this.instanceFromAccountId( chain, Rune.resolve(ss58).into(Ss58Rune, chain).accountId(), ) } } export class CtorNotFoundError extends Error { override readonly name = "CtorNotFoundError" }