parkingsv-contract
Version:
ParkingSV library
164 lines (125 loc) • 4.68 kB
text/typescript
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();
}
}