UNPKG

scryptlib

Version:

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

654 lines 24.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.resolveGenericType = exports.parseGenericType = exports.isGenericType = exports.resolveType = exports.findStatic = exports.findConstStatic = exports.flatternArg = exports.deduceGenericLibrary = exports.deduceGenericStruct = exports.hasGeneric = exports.checkSupportedParamType = exports.subArrayType = exports.toLiteralArrayType = exports.arrayTypeAndSize = exports.arrayTypeAndSizeStr = exports.typeOfArg = void 0; const _1 = require("."); const internal_1 = require("./internal"); const scryptTypes_1 = require("./scryptTypes"); function typeOfArg(a) { if (typeof a === 'bigint') { return 'int'; } else if (typeof a === 'boolean') { return 'bool'; } else if (typeof a === 'string') { return 'bytes'; } else if (Array.isArray(a)) { return 'Array'; } else { return typeof a; } } exports.typeOfArg = typeOfArg; /** * return eg. int[N][N][4] => ['int', ["N","N","4"]] * @param arrayTypeName */ function arrayTypeAndSizeStr(arrayTypeName) { const arraySizes = []; if (arrayTypeName.indexOf('>') > -1) { const elemTypeName = arrayTypeName.substring(0, arrayTypeName.lastIndexOf('>') + 1); const sizeParts = arrayTypeName.substring(arrayTypeName.lastIndexOf('>') + 1); [...sizeParts.matchAll(/\[([\w.]+)\]+/g)].map(match => { arraySizes.push(match[1]); }); return [elemTypeName, arraySizes]; } [...arrayTypeName.matchAll(/\[([\w.]+)\]+/g)].map(match => { arraySizes.push(match[1]); }); const group = arrayTypeName.split('['); const elemTypeName = group[0]; return [elemTypeName, arraySizes]; } exports.arrayTypeAndSizeStr = arrayTypeAndSizeStr; /** * return eg. int[2][3][4] => ['int', [2,3,4]] * @param arrayTypeName eg. int[2][3][4] */ function arrayTypeAndSize(arrayTypeName) { const [elemTypeName, arraySizes] = arrayTypeAndSizeStr(arrayTypeName); return [elemTypeName, arraySizes.map(size => { const n = parseInt(size); if (isNaN(n)) { throw new Error(`arrayTypeAndSize error type ${arrayTypeName} with sub isNaN`); } return n; })]; } exports.arrayTypeAndSize = arrayTypeAndSize; function toLiteralArrayType(elemTypeName, sizes) { return [elemTypeName, sizes.map(size => `[${size}]`).join('')].join(''); } exports.toLiteralArrayType = toLiteralArrayType; /** * return eg. int[2][3][4] => int[3][4] * @param arrayTypeName eg. int[2][3][4] */ function subArrayType(arrayTypeName) { const [elemTypeName, sizes] = arrayTypeAndSize(arrayTypeName); return toLiteralArrayType(elemTypeName, sizes.slice(1)); } exports.subArrayType = subArrayType; function checkArrayParamType(args, param, resolver) { const typeInfo = resolver(param.type); const expectedType = typeInfo.finalType; const [elemTypeName, arraySizes] = arrayTypeAndSize(expectedType); if (!Array.isArray(args)) { return new Error(`The type of ${param.name} is wrong, expected ${expectedType} but got ${typeOfArg(args)}`); } if (args.length !== arraySizes[0]) { return new Error(`The type of ${param.name} is wrong, expected a array with length = ${arraySizes[0]} but got a array with length = ${args.length} `); } if (arraySizes.length == 1) { return args.map(arg => { return checkSupportedParamType(arg, { name: param.name, type: elemTypeName }, resolver); }).find(e => e instanceof Error); } else { return args.map(a => { return checkArrayParamType(a, { name: param.name, type: subArrayType(expectedType) }, resolver); }).find(e => e instanceof Error); } } function checkStructParamType(arg, param, resolver) { const typeInfo = resolver(param.type); if (!typeInfo.info) { return new Error(`The type of ${param.name} is wrong, no info found`); } let entity = typeInfo.info; if (typeInfo.generic) { const result = deduceGenericStruct(param, entity, resolver); if (result instanceof Error) { return result; } entity = result; } if (Array.isArray(arg)) { return new Error(`The type of ${param.name} is wrong, expected ${entity.name} but got a array`); } if (typeof arg !== 'object') { return new Error(`The type of ${param.name} is wrong, expected ${entity.name} but got a ${typeof arg}`); } const error = entity.params.map(p => { if (!Object.keys(arg).includes(p.name)) { return new Error(`The type of ${param.name} is wrong, expected ${entity.name} but missing member [${p.name}]`); } return checkSupportedParamType(arg[p.name], p, resolver); }).find(e => e instanceof Error); if (error) { return error; } const members = entity.params.map(p => p.name); return Object.keys(arg).map(key => { if (!members.includes(key)) { return new Error(`The type of ${param.name} is wrong, expected ${entity.name} but redundant member [${key}] appears`); } return undefined; }).find(e => e instanceof Error); } function checkLibraryParamType(args, param, resolver) { const typeInfo = resolver(param.type); if (!typeInfo.info || typeInfo.symbolType !== scryptTypes_1.SymbolType.Library) { return new Error(`The type of ${param.name} is wrong, no info found`); } let entity = typeInfo.info; if (typeInfo.generic) { const result = deduceGenericLibrary(param, entity, resolver); if (result instanceof Error) { return result; } entity = result; } if (Array.isArray(args)) { if (args.length !== entity.params.length) { return new Error(`The type of ${param.name} is wrong, expected a array with length = ${entity.params.length} but got a array with length = ${args.length} `); } return entity.params.map((p, index) => { if (typeof args[index] === 'undefined') { return new Error(`The type of ${param.name} is wrong, expected ${entity.name} but missing parameter [${p.name}]`); } return checkSupportedParamType(args[index], p, resolver); }).find(e => e instanceof Error); } else if (typeof args === 'object') { return entity.properties.map((p) => { if (typeof args[p.name] === 'undefined') { return new Error(`The type of ${param.name} is wrong, expected ${entity.name} but missing property [${p.name}]`); } return checkSupportedParamType(args[p.name], p, resolver); }).find(e => e instanceof Error); } else { return new Error(`The type of ${param.name} is wrong, expected a array or a object but got ${typeof args}`); } } function checkSupportedParamType(arg, param, resolver) { const typeInfo = resolver(param.type); const expectedType = typeInfo.finalType; if ((0, _1.isArrayType)(expectedType)) { return checkArrayParamType(arg, param, resolver); } else if (typeInfo.symbolType === scryptTypes_1.SymbolType.Struct) { return checkStructParamType(arg, param, resolver); } else if (typeInfo.symbolType === scryptTypes_1.SymbolType.Library) { return checkLibraryParamType(arg, param, resolver); } else if (typeInfo.symbolType === scryptTypes_1.SymbolType.ScryptType) { const error = new Error(`The type of ${param.name} is wrong, expected ${expectedType} but got ${typeOfArg(arg)}`); const t = typeOfArg(arg); if ((0, scryptTypes_1.isBytes)(expectedType)) { return t === scryptTypes_1.ScryptType.BYTES ? undefined : error; } else if (expectedType === scryptTypes_1.ScryptType.PRIVKEY) { return t === 'int' ? undefined : error; } else { return t == expectedType ? undefined : error; } } else { return new Error(`can't not resolve type: ${param.type}`); } } exports.checkSupportedParamType = checkSupportedParamType; function hasGeneric(entity) { return entity.genericTypes.length > 0; } exports.hasGeneric = hasGeneric; class GenericDeducer { constructor(entity, resolver) { this.entity = entity; this.resolver = resolver; this.inferred = {}; } resolve(type) { if (Object.keys(this.inferred).length > 0) { if (isGenericType(type)) { const [name, types] = parseGenericType(type); return (0, internal_1.toGenericType)(name, types.map(t => this.inferred[t] || t)); } if ((0, _1.isArrayType)(type)) { const [elem, sizes] = arrayTypeAndSizeStr(type); return toLiteralArrayType(elem, sizes.map(t => this.inferred[t] || t)); } return this.inferred[type] || type; } return type; } inferr(param) { const typeInfo = this.resolver(param.type); const [name, genericTypes] = parseGenericType(typeInfo.finalType); if (this.entity.name !== name) { return new Error(`Generic inference failed, expected ${name} but got ${this.entity.name}`); } if (this.entity.genericTypes.length !== genericTypes.length) { return new Error('Generic inference failed, genericTypes length not match'); } return this.entity.genericTypes.map((genericTyp, index) => { const realType = genericTypes[index]; return this.assert(genericTyp, realType); }).find(e => e instanceof Error); } getEntity() { if (Object.prototype.hasOwnProperty.call(this.entity, 'properties')) { const library = this.entity; return { name: library.name, params: library.params.map(p => ({ name: p.name, type: this.resolve(p.type) })), properties: library.properties.map(p => ({ name: p.name, type: this.resolve(p.type) })), genericTypes: (this.entity.genericTypes || []).map(t => this.resolve(t)) }; } return { name: this.entity.name, params: this.entity.params.map(p => ({ name: p.name, type: this.resolve(p.type) })), genericTypes: (this.entity.genericTypes || []).map(t => this.resolve(t)) }; } assert(genericType, realType) { if (this.inferred[genericType]) { if (this.inferred[genericType] !== realType) { return new Error(`Generic inference failed, generic ${genericType} cannot be both type ${this.inferred[genericType]} and type ${realType}`); } } else { this.inferred[genericType] = realType; } } } function deduceGenericStruct(param, entity, resolver) { if (!hasGeneric(entity)) { return entity; } const deducer = new GenericDeducer(entity, resolver); const error = deducer.inferr(param); if (error) { return error; } return deducer.getEntity(); } exports.deduceGenericStruct = deduceGenericStruct; function deduceGenericLibrary(param, entity, resolver) { if (!hasGeneric(entity)) { return entity; } const deducer = new GenericDeducer(entity, resolver); const error = deducer.inferr(param); if (error) { return error; } return deducer.getEntity(); } exports.deduceGenericLibrary = deduceGenericLibrary; function flatternArray(arg, param, resolver, options) { const [elemTypeName, arraySizes] = arrayTypeAndSize(param.type); const typeInfo = resolver(elemTypeName); if (!options.ignoreValue) { if (!Array.isArray(arg)) { throw new Error('flatternArray only work with array'); } if (arg.length != arraySizes[0]) { throw new Error(`Array length not match, expected ${arraySizes[0]} but got ${arg.length}`); } } return new Array(arraySizes[0]).fill(1).flatMap((_, index) => { const item = options.ignoreValue ? undefined : arg[index]; if (arraySizes.length > 1) { return flatternArg({ name: `${param.name}[${index}]`, type: subArrayType(param.type), value: item }, resolver, options); } else if (typeInfo.symbolType === scryptTypes_1.SymbolType.Struct) { return flatternArg({ name: `${param.name}[${index}]`, type: elemTypeName, value: item }, resolver, options); } else if (typeInfo.symbolType === scryptTypes_1.SymbolType.Library) { return flatternArg({ name: `${param.name}[${index}]`, type: elemTypeName, value: item }, resolver, options); } return { value: item, name: `${param.name}${(0, internal_1.subscript)(index, arraySizes)}`, type: elemTypeName }; }); } function flatternStruct(arg, param, resolver, options) { const typeInfo = resolver(param.type); if (!options.ignoreValue) { if (typeof arg !== 'object') { throw new Error('flatternStruct only work with object'); } } let entity = typeInfo.info; if (typeInfo.generic) { const deducer = new GenericDeducer(entity, resolver); const error = deducer.inferr(param); if (error) { throw error; } entity = deducer.getEntity(); } return entity.params.flatMap(p => { const paramTypeInfo = resolver(p.type); const member = options.ignoreValue ? undefined : arg[p.name]; if ((0, _1.isArrayType)(paramTypeInfo.finalType)) { return flatternArg({ name: `${param.name}.${p.name}`, type: p.type, value: member }, resolver, options); } else if (paramTypeInfo.symbolType === scryptTypes_1.SymbolType.Struct) { return flatternArg({ name: `${param.name}.${p.name}`, type: p.type, value: member }, resolver, options); } else { return { value: member, name: `${param.name}.${p.name}`, type: p.type }; } }); } function flatternLibrary(args, param, resolver, options) { const typeInfo = resolver(param.type); let entity = typeInfo.info; if (typeInfo.generic) { const deducer = new GenericDeducer(entity, resolver); const error = deducer.inferr(param); if (error) { throw error; } entity = deducer.getEntity(); } if (!options.ignoreValue) { if (options.state) { if (typeof args !== 'object') { throw new Error('only work with object when flat a libray as state'); } } else { if (!Array.isArray(args)) { throw new Error('only work with array when flat a library'); } if (entity.params.length != args.length) { throw new Error(`Array length not match, expected ${entity.params.length} but got ${args.length}`); } } } const toflat = options.state ? entity.properties : entity.params; return toflat.flatMap((p, index) => { const paramTypeInfo = resolver(p.type); let arg = options.ignoreValue ? undefined : (options.state ? args[p.name] : args[index]); if (!options.ignoreValue && typeof arg === 'undefined' && (entity.name === 'HashedSet' || entity.name === 'HashedMap')) { arg = args[0]; } if ((0, _1.isArrayType)(paramTypeInfo.finalType)) { return flatternArg({ name: `${param.name}.${p.name}`, type: p.type, value: arg }, resolver, options); } else if (paramTypeInfo.symbolType === scryptTypes_1.SymbolType.Struct) { return flatternArg({ name: `${param.name}.${p.name}`, type: p.type, value: arg }, resolver, options); } else if (paramTypeInfo.symbolType === scryptTypes_1.SymbolType.Library) { return flatternArg({ name: `${param.name}.${p.name}`, type: p.type, value: arg }, resolver, options); } else { return { value: arg, name: `${param.name}.${p.name}`, type: p.type }; } }); } function flatternArg(arg, resolver, options) { const args_ = []; const typeInfo = resolver(arg.type); if ((0, _1.isArrayType)(typeInfo.finalType)) { flatternArray(options.ignoreValue ? undefined : arg.value, { name: arg.name, type: typeInfo.finalType }, resolver, options).forEach(e => { args_.push({ name: e.name, type: resolver(e.type).finalType, value: e.value }); }); } else if (typeInfo.symbolType === scryptTypes_1.SymbolType.Struct) { flatternStruct(arg.value, { name: arg.name, type: typeInfo.finalType }, resolver, options).forEach(e => { args_.push({ name: e.name, type: resolver(e.type).finalType, value: e.value }); }); } else if (typeInfo.symbolType === scryptTypes_1.SymbolType.Library) { flatternLibrary(arg.value, { name: arg.name, type: typeInfo.finalType }, resolver, options).forEach(e => { args_.push({ name: e.name, type: resolver(e.type).finalType, value: e.value }); }); } else { args_.push({ name: arg.name, type: typeInfo.finalType, value: arg.value }); } return args_; } exports.flatternArg = flatternArg; function findConstStatic(statics, name) { return statics.find(s => { return s.const === true && s.name === name; }); } exports.findConstStatic = findConstStatic; function findStatic(statics, name) { return statics.find(s => { return s.name === name; }); } exports.findStatic = findStatic; function resolveAlias(alias, type) { const a = alias.find(a => { return a.name === type; }); if (a) { return resolveAlias(alias, a.type); } return type; } function resolveType(type, originTypes, contract, statics, alias, librarys) { type = resolveAlias(alias, type); if ((0, _1.isArrayType)(type)) { const [elemTypeName, sizes] = arrayTypeAndSizeStr(type); const elemTypeInfo = resolveType(elemTypeName, originTypes, contract, statics, alias, librarys); if ((0, _1.isArrayType)(elemTypeInfo.finalType)) { const [elemTypeName_, sizes_] = arrayTypeAndSizeStr(elemTypeInfo.finalType); const elemTypeInfo_ = resolveType(elemTypeName_, originTypes, contract, statics, alias, librarys); return { info: elemTypeInfo.info, generic: elemTypeInfo.generic, finalType: resolveConstStatic(contract, toLiteralArrayType(elemTypeInfo_.finalType, sizes.concat(sizes_)), statics), symbolType: elemTypeInfo.symbolType }; } return { info: elemTypeInfo.info, generic: elemTypeInfo.generic, finalType: resolveConstStatic(contract, toLiteralArrayType(elemTypeInfo.finalType, sizes), statics), symbolType: elemTypeInfo.symbolType }; } else if (isGenericType(type)) { const [name, genericTypes] = parseGenericType(type); const typeInfo = resolveType(name, originTypes, contract, statics, alias, librarys); const gts = genericTypes.map(t => resolveType(t, originTypes, contract, statics, alias, librarys).finalType); return { info: typeInfo.info, generic: true, finalType: (0, internal_1.toGenericType)(typeInfo.finalType, gts), symbolType: typeInfo.symbolType }; } if (originTypes[type]) { return originTypes[type]; } else if ((0, scryptTypes_1.isScryptType)(type)) { return { finalType: type, generic: false, symbolType: scryptTypes_1.SymbolType.ScryptType }; } else { return { finalType: type, generic: false, symbolType: scryptTypes_1.SymbolType.Unknown }; } } exports.resolveType = resolveType; function resolveConstStatic(contract, type, statics) { if ((0, _1.isArrayType)(type)) { const [elemTypeName, arraySizes] = arrayTypeAndSizeStr(type); const sizes = arraySizes.map(size => { if (/^(\d)+$/.test(size)) { return parseInt(size); } else { // size as a static const const size_ = (size.indexOf('.') > 0) ? size : `${contract}.${size}`; const value = findConstStatic(statics, size_); if (!value) { // Unable to solve when the subscript of the array is a function parameter, [CTC](https://scryptdoc.readthedocs.io/en/latest/ctc.html) return size; } return value.value; } }); return toLiteralArrayType(elemTypeName, sizes); } return type; } /** * check if a type is generic type * @param type * @returns */ function isGenericType(type) { return /^([\w]+)<([\w,[\]\s<>]+)>$/.test(type); } exports.isGenericType = isGenericType; /** * * @param type eg. HashedMap<int,int> * @param eg. ["HashedMap", ["int", "int"]}] An array generic types returned by @getGenericDeclaration * @returns {"K": "int", "V": "int"} */ function parseGenericType(type) { if (isGenericType(type)) { const m = type.match(/([\w]+)<([\w,[\]<>\s]+)>$/); if (m) { const library = m[1]; const realTypes = []; const brackets = []; let tmpType = ''; for (let i = 0; i < m[2].length; i++) { const ch = m[2].charAt(i); if (ch === '<' || ch === '[') { brackets.push(ch); } else if (ch === '>' || ch === ']') { brackets.pop(); } else if (ch === ',') { if (brackets.length === 0) { realTypes.push(tmpType.trim()); tmpType = ''; continue; } } tmpType += ch; } realTypes.push(tmpType.trim()); return [library, realTypes]; } } throw new Error(`"${type}" is not generic type`); } exports.parseGenericType = parseGenericType; function resolveGenericType(genericTypeMap, type) { if (Object.keys(genericTypeMap).length > 0) { if (isGenericType(type)) { const [name, types] = parseGenericType(type); return (0, internal_1.toGenericType)(name, types.map(t => genericTypeMap[t] || t)); } if ((0, _1.isArrayType)(type)) { const [elem, sizes] = arrayTypeAndSizeStr(type); return toLiteralArrayType(elem, sizes.map(t => genericTypeMap[t] || t)); } return genericTypeMap[type] || type; } return type; } exports.resolveGenericType = resolveGenericType; //# sourceMappingURL=typeCheck.js.map