/ src / client.ts
client.ts
  1  import { Environment, CompilationInputs, FunctionHashes } from './types';
  2  import { Client as MythXClient } from 'mythxjs';
  3  
  4  export interface Data {
  5    contractName: string;
  6    bytecode: string;
  7    sourceMap: any;
  8    deployedBytecode: string;
  9    deployedSourceMap: any;
 10    sourceList: string[];
 11    analysisMode: string;
 12    toolName: string;
 13    noCacheLookup: boolean;
 14    sources: Sources | CompilationInputs;
 15    mainSource: string;
 16    functionHashes?: FunctionHashes;
 17  }
 18  
 19  interface Sources {
 20    [key: string]: {
 21      ast: any;
 22      source: string;
 23    };
 24  }
 25  
 26  export default class Client {
 27    private mythXClient: MythXClient;
 28    constructor(env: Environment) {
 29      const { apiUrl, username, password, apiKey } = env;
 30      this.mythXClient = new MythXClient(
 31        username,
 32        password,
 33        undefined,
 34        apiUrl,
 35        apiKey
 36      );
 37    }
 38  
 39    public failAnalysis(reason: string, status: string) {
 40      throw new Error(
 41        reason +
 42          ' ' +
 43          'The analysis job state is ' +
 44          status.toLowerCase() +
 45          ' and the result may become available later.'
 46      );
 47    }
 48  
 49    public async awaitAnalysisFinish(
 50      uuid: string,
 51      initialDelay: number,
 52      timeout: number
 53    ) {
 54      const statuses = ['Error', 'Finished'];
 55  
 56      let state = await this.mythXClient.getAnalysisStatus(uuid);
 57  
 58      if (statuses.includes(state.status)) {
 59        return state;
 60      }
 61  
 62      const timer = (interval: number) =>
 63        new Promise(resolve => setTimeout(resolve, interval));
 64  
 65      const maxRequests = 10;
 66      const start = Date.now();
 67      const remaining = Math.max(timeout - initialDelay, 0);
 68      const inverted = Math.sqrt(remaining) / Math.sqrt(285);
 69  
 70      for (let r = 0; r < maxRequests; r++) {
 71        const idle = Math.min(
 72          r === 0 ? initialDelay : (inverted * r) ** 2,
 73          start + timeout - Date.now()
 74        );
 75  
 76        // eslint-disable-next-line no-await-in-loop
 77        await timer(idle);
 78  
 79        if (Date.now() - start >= timeout) {
 80          this.failAnalysis(
 81            `User or default timeout reached after ${timeout / 1000} sec(s).`,
 82            state.status
 83          );
 84        }
 85  
 86        // eslint-disable-next-line no-await-in-loop
 87        state = await this.mythXClient.getAnalysisStatus(uuid);
 88  
 89        if (statuses.includes(state.status)) {
 90          return state;
 91        }
 92      }
 93  
 94      this.failAnalysis(
 95        `Allowed number (${maxRequests}) of requests was reached.`,
 96        state.status
 97      );
 98    }
 99  
100    public async authenticate() {
101      return this.mythXClient.login();
102    }
103  
104    public async submitDataForAnalysis(data: Data) {
105      return this.mythXClient.analyze(data);
106    }
107  
108    public async getReport(uuid: string) {
109      return this.mythXClient.getDetectedIssues(uuid);
110    }
111  
112    public async getApiVersion() {
113      return this.mythXClient.getVersion();
114    }
115  
116    public async getAnalysesList() {
117      return this.mythXClient.getAnalysesList();
118    }
119  
120    public async getAnalysisStatus(uuid: string) {
121      return this.mythXClient.getAnalysisStatus(uuid);
122    }
123  }