UNPKG

autrace

Version:
171 lines 7.63 kB
export class SmartContractAnalyzer { constructor() { this.analyzeContractInstance = (instance) => { const contractClass = Object.getPrototypeOf(instance).constructor; const contractName = contractClass.name; // Analyze state fields const stateFields = this.extractStateFields(contractClass); // Analyze methods and their relationships const methods = this.extractMethods(contractClass); // Analyze permissions const permissions = this.extractPermissions(contractClass); this.contracts.set(contractName, { name: contractName, stateFields, methods, permissions }); }; this.extractStateFields = (contractClass) => { const stateFields = []; let stateIndex = 0; Object.getOwnPropertyNames(contractClass.prototype).forEach(prop => { //console.log(prop) const descriptor = Object.getOwnPropertyDescriptor(contractClass.prototype, prop); //console.log('Descriptor: ', descriptor); if (descriptor?.get && prop !== 'address') { stateFields.push({ name: prop, index: stateIndex++ }); } }); //console.log('StateFields: ', stateFields) return stateFields; }; this.extractMethods = (contractClass) => { const methods = []; Object.getOwnPropertyNames(contractClass.prototype).forEach(prop => { const descriptor = Object.getOwnPropertyDescriptor(contractClass.prototype, prop); if (descriptor?.value instanceof Function) { const methodStr = descriptor.value.toString(); methods.push({ name: prop, childCalls: this.extractChildCalls(methodStr), stateChanges: this.extractStateChanges(methodStr), authorization: { requiresProof: methodStr.includes('@method'), requiresSignature: methodStr.includes('requireSignature') } }); } }); return methods; }; this.extractChildCalls = (methodStr) => { const childCalls = []; // Detect internal method calls const internalCallMatch = methodStr.match(/await this\.(\w+)\(/g); if (internalCallMatch) { childCalls.push({ internalMethod: internalCallMatch[0].replace('await this.', '').replace('(', '') }); } // Detect contract instantiations and calls const contractCallMatch = methodStr.match(/const\s+(\w+)\s*=\s*new\s+(\w+)\([^)]*\).*?await\s+\1\.(\w+)\(/s); if (contractCallMatch) { childCalls.push({ contractMethod: `${contractCallMatch[2]}.${contractCallMatch[3]}` }); } return childCalls; }; this.extractStateChanges = (methodStr) => { const stateChanges = []; // Match state get operations const getMatches = methodStr.match(/this\.(\w+)\.get(?:AndRequireEquals)?\(\)/g); if (getMatches) { getMatches.forEach(match => { const field = match.split('.')[1]; stateChanges.push({ field, operation: 'get' }); }); } // Match state set operations const setMatches = methodStr.match(/this\.(\w+)\.set\([^)]+\)/g); if (setMatches) { setMatches.forEach(match => { const field = match.split('.')[1]; stateChanges.push({ field, operation: 'set' }); }); } return stateChanges; }; this.extractPermissions = (contractClass) => { const deployMethod = contractClass.prototype.deploy; if (!deployMethod) return []; const deployStr = deployMethod.toString(); const permissions = []; // Extract permission settings const permissionMatches = deployStr.match(/Permissions\.(\w+)\(\)/g); if (permissionMatches) { permissions.push(...permissionMatches.map((p) => p.replace('Permissions.', '').replace('()', ''))); } return permissions; }; this.buildRelationshipGraph = () => { const relationships = new Map(); this.contracts.forEach((contract, contractName) => { const contractRelations = { parents: [], children: [], stateAccess: [], onChainStates: contract.stateFields.map(field => field.name).join(', ') }; contract.methods.forEach(method => { // Add child relationships method.childCalls.forEach(call => { if (call.contractMethod) { const [childContract, childMethod] = call.contractMethod.split('.'); contractRelations.children.push({ contract: childContract, method: childMethod }); } else if (call.internalMethod) { contractRelations.children.push({ method: call.internalMethod }); } }); // Track state access method.stateChanges.forEach(change => { const existing = contractRelations.stateAccess.find(s => s.field === change.field); if (existing) { if (!existing.operations.includes(change.operation)) { existing.operations.push(change.operation); } } else { contractRelations.stateAccess.push({ field: change.field, operations: [change.operation] }); } }); }); relationships.set(contractName, contractRelations); }); // Second pass to fill in parent relationships relationships.forEach((relations, contractName) => { relations.children.forEach((child) => { if (child.contract) { const childRelations = relationships.get(child.contract); if (childRelations && !childRelations.parents.includes(contractName)) { childRelations.parents.push(contractName); } } }); }); return relationships; }; this.contracts = new Map(); } getContracts() { return this.contracts; } getContract(contractName) { return this.contracts.get(contractName); } } //# sourceMappingURL=ContractAnalyser.js.map