autrace
Version:
Account Update analyser for MINA
171 lines • 7.63 kB
JavaScript
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