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