UNPKG

0xweb

Version:

Contract package manager and other web3 tools

602 lines (578 loc) 23.8 kB
import alot from 'alot'; import * as parser from '@solidity-parser/parser'; import type { TAbiItem, AbiType, TAbiInput } from '@dequanto/types/TAbi'; import { ArrayTypeName, AssemblyBlock, AssemblyCall, astNodeTypes, BaseASTNode, BinaryOperation, BooleanLiteral, ContractDefinition, DecimalNumber, ElementaryTypeName, EmitStatement, EnumDefinition, EventDefinition, Expression, FunctionCall, FunctionDefinition, FunctionTypeName, HexNumber, Identifier, ImportDirective, IndexAccess, Mapping, MemberAccess, ModifierDefinition, NumberLiteral, SourceUnit, StateVariableDeclaration, StringLiteral, StructDefinition, TypeName, UnaryOperation, UserDefinedTypeName, VariableDeclaration, VariableDeclarationStatement } from '@solidity-parser/parser/dist/src/ast-types'; import { $logger } from '@dequanto/utils/$logger'; import { $abiUtils } from '@dequanto/utils/$abiUtils'; import { SourceFile, TSourceFileContract } from './SourceFile'; import { $array } from '@dequanto/utils/$array'; import { $types } from '../utils/$types'; export namespace Ast { export function parse(code: string, opts?: { path: string; }): { ast: SourceUnit, version: string } { try { const ast = parser.parse(code); const version = /pragma[^\d]+(?<version>[\d\.]+)/.exec(code)?.groups?.version; return { ast, version }; } catch (error) { let path = opts?.path ?? `${code.substring(0, 500)}...`; $logger.error(`Parser error in ${path}`); throw error; } } export function getContract(ast: SourceUnit, contractName: string): ContractDefinition { let contracts = ast.children.filter(isContractDefinition) as ContractDefinition[]; if (contractName == null) { return contracts[contracts.length - 1]; } let contract = contracts.find(x => x.name === contractName); return contract; } export function getStruct(ast: SourceUnit, contractName: string): StructDefinition { let structs = ast.children.filter(isStructDefinition) as StructDefinition[]; let struct = structs.find(x => x.name === contractName); return struct; } export function getImports(ast: SourceUnit): ImportDirective[] { const imports = ast.children.filter(isImportDirective) as ImportDirective[]; return imports; } export function getVariableDeclarations(contract: ContractDefinition) { let declarations = contract.subNodes.filter(isStateVariableDeclaration) as StateVariableDeclaration[]; let vars = alot(declarations) .mapMany(x => x.variables) .filter(x => x != null) .toArray() as VariableDeclaration[]; return vars; } export function getFunctionDeclarations(contract: ContractDefinition, inheritanceChain?: ContractDefinition[]) { let fns = contract.subNodes.filter(isFunctionDefinition) as FunctionDefinition[]; if (inheritanceChain?.length > 0) { inheritanceChain.forEach(contract => { fns.push(...getFunctionDeclarations(contract)); }); } return alot(fns).distinctBy(x => x.name).toArray(); } export function getModifierDefinitions(contract: ContractDefinition, inheritanceChain?: ContractDefinition[]) { let fns = contract.subNodes.filter(isModifierDefinition) as ModifierDefinition[]; if (inheritanceChain?.length > 0) { inheritanceChain.forEach(contract => { fns.push(...getModifierDefinitions(contract)); }); } return alot(fns).distinctBy(x => x.name).toArray(); } export function getEventDefinitions(contract: ContractDefinition, inheritanceChain?: ContractDefinition[]) { let fns = contract.subNodes.filter(isEventDefinition) as EventDefinition[]; if (inheritanceChain?.length > 0) { inheritanceChain.forEach(contract => { fns.push(...getEventDefinitions(contract)); }); } return fns; } export function getUserDefinedType(node: ContractDefinition | StructDefinition | SourceUnit, name: string): (StructDefinition | ContractDefinition | EnumDefinition) & { parent?; } { let [key, ...nestings] = name.split('.'); let nodeFound = getUserDefinedTypeRaw(node, key); if (nodeFound == null) { let cursor = node as any; while (nodeFound == null && cursor.parent != null) { nodeFound = getUserDefinedTypeRaw(cursor.parent, key); cursor = cursor.parent; } } while (nestings.length > 0 && nodeFound != null) { key = nestings.shift(); nodeFound = getUserDefinedTypeRaw(nodeFound as any, key); } return nodeFound as any; } export function find<T extends BaseASTNode = BaseASTNode>( node: BaseASTNode | BaseASTNode[] , matcher: (node: BaseASTNode) => boolean ): { node: T, stack: BaseASTNode[] } { if (Array.isArray(node)) { let result = alot(node) .map(x => find(x, matcher)) .first(x => x != null); return result as { node: T, stack: BaseASTNode[] }; } let result = findMany(node, matcher, { single: true }); return result[0] as { node: T, stack: BaseASTNode[] } } export function findMany<T extends BaseASTNode = BaseASTNode>( node: BaseASTNode , matcher: (node: BaseASTNode) => boolean , opts?: { single?: boolean } , stack: BaseASTNode[] = [] ): { node: T, stack: BaseASTNode[] }[] { let foundMany = []; if (matcher(node)) { return [{ node: node as T, stack }]; } let $stack = [...stack, node]; for (let key in node) { let val = node[key]; if (val == null || typeof val !== 'object') { continue; } if (Array.isArray(val)) { val = val.filter(x => x != null); if (val.length === 0 || val[0].type == null) { continue; } for (let i = 0; i < val.length; i++) { let found = findMany(val[i], matcher, opts, $stack); if (found.length > 0) { foundMany.push(...found); if (opts?.single) { return foundMany; } } } continue; } if (val.type != null) { let found = findMany(val, matcher, opts, $stack); if (found.length > 0) { foundMany.push(...found); if (opts?.single) { return foundMany; } } continue; } } return foundMany; } export function isBinaryOperation(node: BaseASTNode): node is BinaryOperation { return node?.type === 'BinaryOperation'; } export function isUnaryOperation(node: BaseASTNode): node is UnaryOperation { return node?.type === 'UnaryOperation'; } export function isIndexAccess(node: BaseASTNode): node is IndexAccess { return node?.type === 'IndexAccess'; } export function isMemberAccess(node: BaseASTNode): node is MemberAccess { return node?.type === 'MemberAccess'; } export function isIdentifier(node: BaseASTNode): node is Identifier { return node?.type === 'Identifier'; } export function isEmitStatement(node: BaseASTNode): node is EmitStatement { return node?.type === 'EmitStatement'; } export function isAssemblyBlock(node: BaseASTNode): node is AssemblyBlock { return node?.type === 'AssemblyBlock'; } export function isAssemblyCall(node: BaseASTNode): node is AssemblyCall { return node?.type === 'AssemblyCall'; } export function isEventDefinition(node: BaseASTNode): node is EventDefinition { return node?.type === 'EventDefinition'; } export function isFunctionCall(node: BaseASTNode): node is FunctionCall { return node?.type === 'FunctionCall'; } export function isFunctionDefinition(node: BaseASTNode): node is FunctionDefinition { return node?.type === 'FunctionDefinition'; } export function isModifierDefinition(node: BaseASTNode): node is ModifierDefinition { return node?.type === 'ModifierDefinition'; } export function isStateVariableDeclaration(node: BaseASTNode): node is StateVariableDeclaration { return node?.type === 'StateVariableDeclaration'; } export function isImportDirective(node: BaseASTNode): node is ImportDirective { return node?.type === 'ImportDirective'; } export function isContractDefinition(node: BaseASTNode): node is ContractDefinition { return node?.type === 'ContractDefinition'; } export function isVariableDeclarationStatement(node: BaseASTNode): node is VariableDeclarationStatement { return node?.type === 'VariableDeclarationStatement'; } export function isVariableDeclaration(node: BaseASTNode): node is VariableDeclaration { return node?.type === 'VariableDeclaration'; } export function isElementaryTypeName(node: BaseASTNode): node is ElementaryTypeName { return node?.type === 'ElementaryTypeName'; } export function isArrayTypeName(node: BaseASTNode): node is ArrayTypeName { return node?.type === 'ArrayTypeName'; } export function isMapping(node: BaseASTNode): node is Mapping { return node?.type === 'Mapping'; } export function isUserDefinedTypeName(node: BaseASTNode): node is UserDefinedTypeName { return node?.type === 'UserDefinedTypeName'; } export function isDecimalNumber(node: BaseASTNode): node is DecimalNumber { return node?.type === 'DecimalNumber'; } export function isHexNumber(node: BaseASTNode): node is HexNumber { return node?.type === 'HexNumber'; } export function isNumberLiteral(node: BaseASTNode): node is NumberLiteral { return node?.type === 'NumberLiteral'; } export function isStringLiteral(node: BaseASTNode): node is StringLiteral { return node?.type === 'StringLiteral'; } export function isBooleanLiteral(node: BaseASTNode): node is BooleanLiteral { return node?.type === 'BooleanLiteral'; } export function isStructDefinition(node: BaseASTNode): node is StructDefinition { return node?.type === 'StructDefinition'; } export function isSourceUnit(node: BaseASTNode): node is SourceUnit { return node?.type === 'SourceUnit'; } export function getFunctionName(node: FunctionCall) { let expression = node.expression; if (Ast.isIdentifier(expression)) { return expression.name; } return null; } function getUserDefinedTypeRaw(node: ContractDefinition | StructDefinition | SourceUnit, name: string): StructDefinition | ContractDefinition | EnumDefinition { let arr: BaseASTNode[]; if (isContractDefinition(node)) { arr = node.subNodes } else if (isStructDefinition(node)) { arr = node.members; } else if (isSourceUnit(node)) { arr = node.children } else { throw new Error(`Unexpected node: ${ JSON.stringify(node) } to get UserDefinedType from`); } let nodeFound = arr .filter(x => x.type === 'StructDefinition' || x.type === 'ContractDefinition' || x.type === 'EnumDefinition') .find(x => (x as any).name === name) as StructDefinition | ContractDefinition | EnumDefinition; if (nodeFound) { return nodeFound; } return null; } export function serialize(node: Identifier | MemberAccess | FunctionCall | EventDefinition | ElementaryTypeName | Expression | AssemblyCall | DecimalNumber | HexNumber | NumberLiteral ): string { if (Ast.isIdentifier(node)) { return node.name; } if (Ast.isMemberAccess(node)) { return serialize(node.expression as Identifier | MemberAccess) + '.' + node.memberName; } if (Ast.isIndexAccess(node)) { return serialize(node.base as Identifier | MemberAccess) + '[' + serialize(node.index) + ']'; } if (Ast.isFunctionCall(node)) { let name = serialize(node.expression); let args = node.arguments.map(node => serialize(node)); return `${name}(${args.join(', ')})`; } if (Ast.isElementaryTypeName(node)) { let typeName = $abiUtils.fromAliasIfAny(node.name); return typeName; } if (Ast.isAssemblyCall(node)) { let str = node.functionName; if (node.arguments?.length > 0) { let args = node.arguments.map(serialize).join(', '); str = `${str}(${args})`; } return str; } if (isDecimalNumber(node) || isHexNumber(node)) { return node.value; } if (isNumberLiteral(node)) { return node.number; } if (isStringLiteral(node)) { return `"${node.value?.replace(/"/g, '\\"')}"`; } throw new Error(`Unknown node ${JSON.stringify(node)}`) } export async function serializeTypeName( name: string , typeName: TypeName | VariableDeclaration , source: TSourceFileContract , inheritance: TSourceFileContract[] ): Promise<TAbiInput> { if (isVariableDeclaration(typeName)) { return serializeTypeName(typeName.name, typeName.typeName, source, inheritance); } if (isElementaryTypeName(typeName)) { return { name: name, type: serialize(typeName) }; } if (isArrayTypeName(typeName)) { let baseTypeName = typeName.baseTypeName; if (isElementaryTypeName(baseTypeName)) { return { name: name, type: `${serialize(baseTypeName)}[]` } } let result = await serializeTypeName(name, typeName.baseTypeName, source, inheritance); result.type += `[]`; return result; } if (isUserDefinedTypeName(typeName)) { let $type = Ast.getUserDefinedType(source.contract, typeName.namePath); if ($type == null && inheritance?.length > 0) { $type = alot(inheritance) .map(x => Ast.getUserDefinedType(x.contract, typeName.namePath)) .filter(x => x != null) .first(); } if ($type == null) { $type = await source.file?.getUserDefinedType(typeName.namePath); } if ($type != null) { if (isStructDefinition($type)) { return { name: name, type: 'tuple', components: await alot($type.members).mapAsync(async member => { return await serializeTypeName(member.name, member.typeName, source, inheritance) }).toArrayAsync() }; } if (isContractDefinition($type)) { return { name: name, type: 'address' }; } } else { throw new Error(`Could not find type "${typeName.namePath}" by serializing ${name}`); } } // if (isMapping(typeName)) { // let baseType = typeName.valueType; // function toVariableDeclaration (identifier: Identifier, typeName: TypeName): VariableDeclaration { // return <VariableDeclaration> { // type: 'VariableDeclaration', // name: identifier?.name, // identifier, // typeName // }; // } // if (isMapping(baseType)) { // let baseTypeInner = baseType.valueType; // return serializeTypeName(name, <FunctionTypeName> { // type: 'FunctionTypeName', // parameterTypes: [ // toVariableDeclaration(typeName.keyName, typeName.keyType), // toVariableDeclaration(baseType.keyName, baseType.keyType), // ], // returnTypes: [ // toVariableDeclaration(baseType.valueName, baseTypeInner), // ] // }, source, inheritance) // } // return serializeTypeName(name, <FunctionTypeName> { // type: 'FunctionTypeName', // parameterTypes: [ toVariableDeclaration(typeName.keyName, typeName.keyType) ], // returnTypes: [ toVariableDeclaration(typeName.valueName, typeName.valueType) ] // }, source, inheritance) // } throw new Error(`@TODO implement complex type to abi serializer: ${name} = ${JSON.stringify(typeName)}`); } export async function getAbi( node: EventDefinition | FunctionDefinition | VariableDeclaration , source: TSourceFileContract , inheritance?: TSourceFileContract[] ): Promise<TAbiItem> { if (Ast.isEventDefinition(node)) { return { type: 'event', name: node.name, inputs: await alot(node.parameters).mapAsync(async param => { let item = await serializeTypeName(param.name, param.typeName, source, inheritance) return { ...item, indexed: param.isIndexed }; }).toArrayAsync() } } if (Ast.isFunctionDefinition(node)) { let type: AbiType = node.isConstructor ? 'constructor' : 'function'; let name = node.isConstructor ? 'constructor' : node.name; if (name == null) { if (node.isFallback) { type = 'fallback'; name = void 0; } else if (node.isReceiveEther) { type = 'receive'; name = void 0; } else { throw new Error(`Could not find name for function ${JSON.stringify(node)}`); } } return { type, name, inputs: await alot(node.parameters).mapAsync(async param => { return serializeTypeName(param.name, param.typeName, source, inheritance); }).toArrayAsync(), outputs: await alot(node.returnParameters ?? []).mapAsync(async param => { let abiItem = await serializeTypeName(param.name ?? void 0, param.typeName, source, inheritance); return abiItem; }).toArrayAsync(), stateMutability: node.stateMutability ?? 'nonpayable', }; } if (Ast.isVariableDeclaration(node)) { if (KeyBasedGetter.isKeyBasedVariable(node)) { let fnDef = KeyBasedGetter.createKeyBasedGetterFunction(node); return getAbi(fnDef, source, inheritance); } // Generate getter return getAbi(<FunctionDefinition> { type: 'FunctionDefinition', name: node.name, parameters: [], returnParameters: [ node ], stateMutability: 'view' }, source, inheritance); } throw new Error(`Unknown node to get the ABI from: ${(node as any)?.type}`) } export function evaluate<TResult extends bigint | string = bigint | string>(node: Expression): TResult { if (isNumberLiteral(node)) { return BigInt(node.number) as TResult; } if (isBinaryOperation(node)) { let a = evaluate<bigint>(node.left); let b = evaluate<bigint>(node.right); switch (node.operator) { case '+': return a + b as TResult; case '-': return a - b as TResult; case '/': return a / b as TResult; case '*': return a * b as TResult; case '**': return a ** b as TResult; case '%': return a % b as TResult; case '<<': return a << b as TResult; case '>>': return a >> b as TResult; } } if (isStringLiteral(node)) { return node.value as TResult; } console.error(`Skip unknown node ${JSON.stringify(node)}`); return null; } namespace KeyBasedGetter { // for mappings and dynamic arrays export function isKeyBasedVariable ($var: VariableDeclaration): boolean { return isMapping($var.typeName) || (isArrayTypeName($var.typeName)) } export function createKeyBasedGetterFunction ($var: VariableDeclaration): FunctionDefinition { let params = [] as VariableDeclaration[]; let returns = [] as VariableDeclaration[]; let cursor = $var.typeName; // use loop to get nested mappings/arrays, e.g. mapping(uint => mapping(address => uint)) while (true) { let extracted = getKeyParameter(cursor); if (extracted == null) { break; } params.push(extracted.param); cursor = extracted.base; } return <FunctionDefinition> { type: 'FunctionDefinition', name: $var.name, parameters: params, returnParameters: [ toVariableDeclaration(null, cursor) ], stateMutability: 'view' }; } function getKeyParameter ($type: TypeName) { if (isMapping($type)) { return { base: $type.valueType, param: toVariableDeclaration($type.keyName, $type.keyType) }; } if (isArrayTypeName($type)) { return { base: $type.baseTypeName, param: toVariableDeclaration({ name: 'index', type: 'Identifier' }, <ElementaryTypeName> { type: 'ElementaryTypeName', name: 'uint256' }) } } return null; } function toVariableDeclaration (identifier: Identifier, $type: TypeName): VariableDeclaration { return <VariableDeclaration> { type: 'VariableDeclaration', name: identifier?.name, identifier, typeName: $type }; } } }