UNPKG

scryptlib

Version:

Javascript SDK for integration of Bitcoin SV Smart Contracts written in sCrypt language.

455 lines 19.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ABICoder = exports.FunctionCall = void 0; const builtins_1 = require("./builtins"); const compilerWrapper_1 = require("./compilerWrapper"); const contract_1 = require("./contract"); const deserializer_1 = require("./deserializer"); const launchConfig_1 = require("./launchConfig"); const scryptTypes_1 = require("./scryptTypes"); const serializer_1 = require("./serializer"); const stateful_1 = require("./stateful"); const typeCheck_1 = require("./typeCheck"); const utils_1 = require("./utils"); class FunctionCall { get unlockingScript() { return this._unlockingScript; } get lockingScript() { return this._lockingScript; } set lockingScript(s) { this._lockingScript = s; } constructor(methodName, binding) { this.methodName = methodName; this.args = []; if (binding.lockingScript === undefined && binding.unlockingScript === undefined) { throw new Error('param binding.lockingScript & binding.unlockingScript cannot both be empty'); } this.contract = binding.contract; this.args = binding.args; if (binding.lockingScript) { this._lockingScript = binding.lockingScript; } if (binding.unlockingScript) { this._unlockingScript = binding.unlockingScript; } } toASM() { return this.toScript().toASM(); } toString() { return this.toHex(); } toScript() { if (this.lockingScript) { return this.lockingScript; } else { return this.unlockingScript; } } toHex() { return this.toScript().toHex(); } genLaunchConfig(txContext) { const pubFunc = this.methodName; const name = `Debug ${this.contract.contractName}`; const program = `${this.contract.file}`; const asmArgs = this.contract.asmArgs || {}; const state = {}; if (contract_1.AbstractContract.isStateful(this.contract)) { Object.assign(state, { opReturnHex: this.contract.dataPart?.toHex() || '' }); } else if (this.contract.dataPart) { Object.assign(state, { opReturn: this.contract.dataPart.toASM() }); } const txCtx = Object.assign({}, this.contract.txContext || {}, txContext || {}, state); return (0, launchConfig_1.genLaunchConfigFile)(this.contract.resolver, this.contract.ctorArgs(), this.args, pubFunc, name, program, txCtx, asmArgs); } verify(txContext) { const result = this.contract.run_verify(this.unlockingScript, txContext); if (!result.success) { const debugUrl = this.genLaunchConfig(txContext); if (debugUrl) { result.error = result.error + `\t[Launch Debugger](${debugUrl.replace(/file:/i, 'scryptlaunch:')})\n`; } } return result; } } exports.FunctionCall = FunctionCall; class ABICoder { constructor(abi, resolver, contractName) { this.abi = abi; this.resolver = resolver; this.contractName = contractName; } encodeConstructorCall(contract, hexTemplate, ...args) { const constructorABI = this.abi.filter(entity => entity.type === compilerWrapper_1.ABIEntityType.CONSTRUCTOR)[0]; const cParams = constructorABI?.params || []; const args_ = contract.checkArgs('constructor', cParams, ...args); // handle array type const flatteredArgs = cParams.flatMap((p, index) => { const a = Object.assign({ ...p }, { value: args_[index] }); return (0, typeCheck_1.flatternArg)(a, this.resolver, { state: false, ignoreValue: false }); }); flatteredArgs.forEach(arg => { if (!hexTemplate.includes(`<${arg.name}>`)) { throw new Error(`abi constructor params mismatch with args provided: missing ${arg.name} in ASM tempalte`); } contract.hexTemplateArgs.set(`<${arg.name}>`, (0, serializer_1.toScriptHex)(arg.value, arg.type)); }); const hasCodePartTemplate = hexTemplate.match(/<__codePart__>/g) ? true : false; if (hasCodePartTemplate) { contract.hexTemplateArgs.set('<__codePart__>', '00'); } // Check if inline ASM var values are expected to be set. const templateMatches = hexTemplate.match(/<.*?>/g); const templateCount = templateMatches ? templateMatches.length : 0; contract.hasInlineASMVars = hasCodePartTemplate ? templateCount > contract.hexTemplateArgs.size + 1 : templateCount > contract.hexTemplateArgs.size; contract.statePropsArgs = stateful_1.default.buildDefaultStateArgs(contract); const lockingScript = (0, utils_1.buildContractCode)(contract.hexTemplateArgs, contract.hexTemplateInlineASM, hexTemplate); return new FunctionCall('constructor', { contract, lockingScript: lockingScript, args: cParams.map((param, index) => ({ name: param.name, type: param.type, value: args_[index] })) }); } encodeConstructorCallFromRawHex(contract, hexTemplate, raw) { const script = utils_1.bsv.Script.fromHex(raw); const constructorABI = this.abi.filter(entity => entity.type === compilerWrapper_1.ABIEntityType.CONSTRUCTOR)[0]; const cParams = constructorABI?.params || []; let offset = 0; let dataPartInHex = undefined; let codePartEndIndex = -1; const err = new Error(`the raw script cannot match the ASM template of contract ${contract.contractName}`); function checkOp(chunk) { const op = hexTemplate.substring(offset, offset + 2); if (parseInt(op, 16) != chunk.opcodenum) { throw err; } offset = offset + 2; } function checkPushByteLength(chunk) { const op = hexTemplate.substring(offset, offset + 2); if (parseInt(op, 16) != chunk.opcodenum) { throw err; } offset = offset + 2; const data = hexTemplate.substring(offset, offset + chunk.len * 2); if (chunk.buf.toString('hex') != data) { throw err; } offset = offset + chunk.len * 2; } function checkPushData1(chunk) { const op = hexTemplate.substring(offset, offset + 2); if (parseInt(op, 16) != chunk.opcodenum) { throw err; } offset = offset + 2; const next1Byte = hexTemplate.substring(offset, offset + 2); if (parseInt(next1Byte, 16) != chunk.len) { throw err; } offset = offset + 2; const data = hexTemplate.substring(offset, offset + chunk.len * 2); if (chunk.buf.toString('hex') != data) { throw err; } offset = offset + chunk.len * 2; } function checkPushData2(chunk) { const op = hexTemplate.substring(offset, offset + 2); if (parseInt(op, 16) != chunk.opcodenum) { throw err; } offset = offset + 2; const next2Byte = hexTemplate.substring(offset, offset + 4); if ((0, builtins_1.bin2num)(next2Byte) != BigInt(chunk.len)) { throw err; } offset = offset + 4; const data = hexTemplate.substring(offset, offset + chunk.len * 2); if (chunk.buf.toString('hex') != data) { throw err; } offset = offset + chunk.len * 2; } function checkPushData4(chunk) { const op = hexTemplate.substring(offset, offset + 2); if (parseInt(op, 16) != chunk.opcodenum) { throw err; } offset = offset + 2; const next4Byte = hexTemplate.substring(offset, offset + 8); if ((0, builtins_1.bin2num)(next4Byte) != BigInt(chunk.len)) { throw err; } offset = offset + 8; const data = hexTemplate.substring(offset, offset + chunk.len * 2); if (chunk.buf.toString('hex') != data) { throw err; } offset = offset + chunk.len * 2; } function findTemplateVariable() { if (hexTemplate.charAt(offset) == '<') { const start = offset; let found = false; while (!found && offset < hexTemplate.length) { offset++; if (hexTemplate.charAt(offset) == '>') { offset++; found = true; } } if (!found) { throw new Error('cannot found break >'); } return hexTemplate.substring(start, offset); } } function saveTemplateVariableValue(name, chunk) { const bw = new utils_1.bsv.encoding.BufferWriter(); bw.writeUInt8(chunk.opcodenum); if (chunk.buf) { if (chunk.opcodenum < utils_1.bsv.Opcode.OP_PUSHDATA1) { bw.write(chunk.buf); } else if (chunk.opcodenum === utils_1.bsv.Opcode.OP_PUSHDATA1) { bw.writeUInt8(chunk.len); bw.write(chunk.buf); } else if (chunk.opcodenum === utils_1.bsv.Opcode.OP_PUSHDATA2) { bw.writeUInt16LE(chunk.len); bw.write(chunk.buf); } else if (chunk.opcodenum === utils_1.bsv.Opcode.OP_PUSHDATA4) { bw.writeUInt32LE(chunk.len); bw.write(chunk.buf); } } if (name.startsWith(`<${contract.contractName}.`)) { //inline asm contract.hexTemplateInlineASM.set(name, bw.toBuffer().toString('hex')); } else { contract.hexTemplateArgs.set(name, bw.toBuffer().toString('hex')); } } for (let index = 0; index < script.chunks.length; index++) { const chunk = script.chunks[index]; let breakfor = false; switch (true) { case (chunk.opcodenum === 106): { if (offset >= hexTemplate.length) { const b = utils_1.bsv.Script.fromChunks(script.chunks.slice(index + 1)); dataPartInHex = b.toHex(); codePartEndIndex = index; breakfor = true; } else { checkOp(chunk); } break; } case (chunk.opcodenum === 0): { const variable = findTemplateVariable(); if (variable) { saveTemplateVariableValue(variable, chunk); } else { checkOp(chunk); } break; } case (chunk.opcodenum >= 1 && chunk.opcodenum <= 75): { const variable = findTemplateVariable(); if (variable) { saveTemplateVariableValue(variable, chunk); } else { checkPushByteLength(chunk); } break; } case (chunk.opcodenum >= 79 && chunk.opcodenum <= 96): { const variable = findTemplateVariable(); if (variable) { saveTemplateVariableValue(variable, chunk); } else { checkOp(chunk); } break; } case (chunk.opcodenum === 76): { const variable = findTemplateVariable(); if (variable) { saveTemplateVariableValue(variable, chunk); } else { checkPushData1(chunk); } break; } case (chunk.opcodenum === 77): { const variable = findTemplateVariable(); if (variable) { saveTemplateVariableValue(variable, chunk); } else { checkPushData2(chunk); } break; } case (chunk.opcodenum === 78): { const variable = findTemplateVariable(); if (variable) { saveTemplateVariableValue(variable, chunk); } else { checkPushData4(chunk); } break; } default: { checkOp(chunk); } } if (breakfor) { break; } } const ctorArgs = cParams.map(param => (0, deserializer_1.deserializeArgfromHex)(contract.resolver, Object.assign(param, { value: false // fake value }), contract.hexTemplateArgs, { state: false })); if (contract_1.AbstractContract.isStateful(contract) && dataPartInHex) { const scriptHex = dataPartInHex; const metaScript = dataPartInHex.substr(scriptHex.length - 10, 10); const version = (0, builtins_1.bin2num)(metaScript.substr(metaScript.length - 2, 2)); switch (version) { case (0, scryptTypes_1.Int)(0): { const [isGenesis, args] = stateful_1.default.parseStateHex(contract, scriptHex); contract.statePropsArgs = args; contract.isGenesis = isGenesis; } break; } } else if (dataPartInHex) { contract.setDataPartInHex(dataPartInHex); } return new FunctionCall('constructor', { contract, lockingScript: codePartEndIndex > -1 ? utils_1.bsv.Script.fromChunks(script.chunks.slice(0, codePartEndIndex)) : script, args: ctorArgs }); } encodePubFunctionCall(contract, name, args) { for (const entity of this.abi) { if (entity.name === name) { const args_ = contract.checkArgs(name, entity.params, ...args); const flatteredArgs = entity.params.flatMap((p, index) => { const a = Object.assign({ ...p }, { value: args_[index] }); return (0, typeCheck_1.flatternArg)(a, this.resolver, { state: false, ignoreValue: false }); }); let hex = flatteredArgs.map(a => (0, serializer_1.toScriptHex)(a.value, a.type)).join(''); if (this.abi.length > 2 && entity.index !== undefined) { // selector when there are multiple public functions const pubFuncIndex = entity.index; hex += `${utils_1.bsv.Script.fromASM((0, utils_1.int2Asm)(pubFuncIndex.toString())).toHex()}`; } return new FunctionCall(name, { contract, unlockingScript: utils_1.bsv.Script.fromHex(hex), args: entity.params.map((param, index) => ({ name: param.name, type: param.type, value: args_[index] })) }); } } throw new Error(`no public function named '${name}' found in contract '${contract.contractName}'`); } /** * build a FunctionCall by function name and unlocking script in hex. * @param contract * @param name name of public function * @param hex hex of unlocking script * @returns a FunctionCall which contains the function parameters that have been deserialized */ encodePubFunctionCallFromHex(contract, hex) { const callData = this.parseCallData(hex); return new FunctionCall(callData.methodName, { contract, unlockingScript: callData.unlockingScript, args: callData.args }); } /** * build a CallData by unlocking script in hex. * @param hex hex of unlocking script * @returns a CallData which contains the function parameters that have been deserialized */ parseCallData(hex) { const unlockingScript = utils_1.bsv.Script.fromHex(hex); const usASM = unlockingScript.toASM(); const pubFunAbis = this.abi.filter(entity => entity.type === 'function'); const pubFunCount = pubFunAbis.length; let entity = undefined; if (pubFunCount === 1) { entity = pubFunAbis[0]; } else { const pubFuncIndexASM = usASM.slice(usASM.lastIndexOf(' ') + 1); const pubFuncIndex = (0, utils_1.asm2int)(pubFuncIndexASM); entity = this.abi.find(entity => entity.index === pubFuncIndex); } if (!entity) { throw new Error(`the raw unlocking script cannot match the contract ${this.constructor.name}`); } const cParams = entity.params || []; const dummyArgs = cParams.map(p => { const dummyArg = Object.assign({}, p, { value: false }); return (0, typeCheck_1.flatternArg)(dummyArg, this.resolver, { state: true, ignoreValue: true }); }).flat(Infinity); let fArgsLen = dummyArgs.length; if (this.abi.length > 2 && entity.index !== undefined) { fArgsLen += 1; } const asmOpcodes = usASM.split(' '); if (fArgsLen != asmOpcodes.length) { throw new Error(`the raw unlockingScript cannot match the arguments of public function ${entity.name} of contract ${this.contractName}`); } const hexTemplateArgs = new Map(); dummyArgs.forEach((farg, index) => { hexTemplateArgs.set(`<${farg.name}>`, utils_1.bsv.Script.fromASM(asmOpcodes[index]).toHex()); }); const args = cParams.map(param => (0, deserializer_1.deserializeArgfromHex)(this.resolver, Object.assign(param, { value: false //fake value }), hexTemplateArgs, { state: false })); return { methodName: entity.name, args, unlockingScript }; } } exports.ABICoder = ABICoder; //# sourceMappingURL=abi.js.map