UNPKG

parkingsv-contract

Version:

ParkingSV library

164 lines (125 loc) 4.68 kB
import { oc } from 'ts-optchain'; import { int2Asm, bsv } from "./utils"; import { AbstractContract, TxContext } from './contract'; import { ScryptType, Bool, Int } from './scryptTypes'; export enum ABIEntityType { FUNCTION = 'function', CONSTRUCTOR = 'constructor' } export interface ABIEntity { type: ABIEntityType; name: string; params: Array<{ name: string, type: string }>; index?: number; } export interface Script { toASM(): string; toHex(): string; } export type SupportedParamType = ScryptType | boolean | number | BigInt; export class FunctionCall { readonly contract: AbstractContract; readonly lockingScript?: Script; readonly unlockingScript?: Script; constructor( public methodName: string, public params: SupportedParamType[], binding: { contract: AbstractContract; lockingScriptASM?: string; unlockingScriptASM?: string; } ) { if (binding.lockingScriptASM === undefined && binding.unlockingScriptASM === undefined) { throw new Error(`param binding.lockingScriptASM & binding.unlockingScriptASM cannot both be empty`); } this.contract = binding.contract; if (binding.lockingScriptASM) { this.lockingScript = bsv.Script.fromASM(binding.lockingScriptASM); } if (binding.unlockingScriptASM) { this.unlockingScript = bsv.Script.fromASM(binding.unlockingScriptASM); } } toASM(): string { if (this.lockingScript) { return this.lockingScript.toASM(); } else { // @ts-ignore return this.unlockingScript.toASM(); } } toString(): string { return this.toHex(); } toScript(): Script { return bsv.Script.fromASM(this.toASM()); } toHex(): string { return this.toScript().toHex(); } verify(txContext?: TxContext): boolean { if (this.unlockingScript) { return this.contract.run_verify(this.unlockingScript.toASM(), txContext); } throw new Error("verification failed, missing unlockingScript"); } } export class ABICoder { constructor(public abi: ABIEntity[]) { } encodeConstructorCall(contract: AbstractContract, asmTemplate: string, ...args: SupportedParamType[]): FunctionCall { const constructorABI = this.abi.filter(entity => entity.type === ABIEntityType.CONSTRUCTOR)[0]; const cParams = oc(constructorABI).params([]); if (args.length !== cParams.length) { throw new Error(`wrong arguments length for #constructor, expected ${cParams.length} but got ${args.length}`); } let lsASM = asmTemplate; cParams.forEach((param, index) => { if (!asmTemplate.includes(`$${param.name}`)) { throw new Error(`abi constructor params mismatch with args provided: missing ${param.name} in asm tempalte`); } lsASM = lsASM.replace(`$${param.name}`, this.encodeParam(args[index], param.type)); }); return new FunctionCall('constructor', args, { contract, lockingScriptASM: lsASM }); } encodePubFunctionCall(contract: AbstractContract, name: string, args: SupportedParamType[]): FunctionCall { for (const entity of this.abi) { if (entity.name === name) { if (entity.params.length !== args.length) { throw new Error(`wrong arguments length for #${name}, expected ${entity.params.length} but got ${args.length}`); } let asm = this.encodeParams(args, entity.params.map(p => p.type)); if (this.abi.length > 2 && entity.index !== undefined) { const pubFuncIndex = entity.index + 1; asm += ` ${int2Asm(pubFuncIndex.toString())}`; } return new FunctionCall(name, args, { contract, unlockingScriptASM: asm }); } } throw new Error(`no function named '${name}' found in abi`); } encodeParams(args: SupportedParamType[], scryptTypeNames: string[]): string { if (args.length !== scryptTypeNames.length) { throw new Error(`wrong arguments length, expected ${scryptTypeNames.length} but got ${args.length}`); } return args.map((arg, i) => { return this.encodeParam(arg, scryptTypeNames[i]); }).join(' '); } encodeParam(arg: SupportedParamType, scryptTypeName: string): string { const typeofArg = typeof arg; if (typeofArg === 'boolean') { arg = new Bool(arg as boolean); } if (typeofArg === 'number') { arg = new Int(arg as number); } if (typeofArg === 'bigint') { arg = new Int(arg as BigInt); } if (arg.constructor.name.toLowerCase() !== scryptTypeName.toLowerCase()) { throw new Error(`wrong argument type, expected ${scryptTypeName} but got ${arg.constructor.name}`); } return (arg as ScryptType).toASM(); } }