scryptlib
Version:
Javascript SDK for integration of Bitcoin SV Smart Contracts written in sCrypt language.
455 lines • 19.4 kB
JavaScript
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
;