/ formatters / text.js
text.js
1 const separator = '-'.repeat(20); 2 const indent = ' '.repeat(4); 3 4 const roles = { 5 creator: 'CREATOR', 6 attacker: 'ATTACKER', 7 other: 'USER' 8 }; 9 10 const textFormatter = {}; 11 12 textFormatter.strToInt = str => parseInt(str, 10); 13 14 textFormatter.guessAccountRoleByAddress = address => { 15 const prefix = address.toLowerCase().substr(0, 10); 16 17 if (prefix === '0xaffeaffe') { 18 return roles.creator; 19 } else if (prefix === '0xdeadbeef') { 20 return roles.attacker; 21 } 22 23 return roles.other; 24 }; 25 26 textFormatter.stringifyValue = value => { 27 const type = typeof value; 28 29 if (type === 'number') { 30 return String(value); 31 } else if (type === 'string') { 32 return value; 33 } else if (value === null) { 34 return 'null'; 35 } 36 37 return JSON.stringify(value); 38 }; 39 40 textFormatter.formatTestCaseSteps = (steps, fnHashes = {}) => { 41 const output = []; 42 43 for (let s = 0, n = 0; s < steps.length; s++) { 44 const step = steps[s]; 45 46 /** 47 * Empty address means "contract creation" transaction. 48 * 49 * Skip it to not spam. 50 */ 51 if (step.address === '') { 52 continue; 53 } 54 55 n++; 56 57 const type = textFormatter.guessAccountRoleByAddress(step.origin); 58 59 const fnHash = step.input.substr(2, 8); 60 const fnName = fnHashes[fnHash] || step.name || '<N/A>'; 61 const fnDesc = `${fnName} [ ${fnHash} ]`; 62 63 output.push( 64 `Tx #${n}:`, 65 indent + `Origin: ${step.origin} [ ${type} ]`, 66 indent + `Function: ${textFormatter.stringifyValue(fnDesc)}`, 67 indent + `Calldata: ${textFormatter.stringifyValue(step.input)}` 68 ); 69 70 if ('decodedInput' in step) { 71 output.push(`${indent}Decoded Calldata: ${step.decodedInput}`); 72 } 73 74 output.push( 75 `${indent}Value: ${textFormatter.stringifyValue(step.value)}`, 76 '' 77 ); 78 } 79 80 return output.join('\n').trimRight(); 81 }; 82 83 textFormatter.formatTestCase = (testCase, fnHashes) => { 84 const output = []; 85 86 if (testCase.steps) { 87 const content = textFormatter.formatTestCaseSteps(testCase.steps, fnHashes); 88 89 if (content) { 90 output.push('Transaction Sequence:', '', content); 91 } 92 } 93 94 return output.length ? output.join('\n') : undefined; 95 }; 96 97 textFormatter.getCodeSample = (source, src) => { 98 const [start, length] = src.split(':').map(textFormatter.strToInt); 99 100 return source.substr(start, length); 101 }; 102 103 textFormatter.formatLocation = message => { 104 const start = `${message.line}:${message.column}`; 105 const finish = `${message.endLine}:{message.endCol}`; 106 107 return `from ${start} to ${finish}`; 108 }; 109 110 textFormatter.formatMessage = (message, filePath, sourceCode, fnHashes) => { 111 const { mythxIssue, mythxTextLocations } = message; 112 const output = []; 113 114 output.push( 115 `==== ${mythxIssue.swcTitle || 'N/A'} ====`, 116 `Severity: ${mythxIssue.severity}`, 117 `File: ${filePath}` 118 ); 119 120 if (message.ruleId !== 'N/A') { 121 output.push(`Link: ${message.ruleId}`); 122 } 123 124 output.push( 125 separator, 126 mythxIssue.description.head, 127 mythxIssue.description.tail 128 ); 129 130 const code = mythxTextLocations.length 131 ? textFormatter.getCodeSample(sourceCode, mythxTextLocations[0].sourceMap) 132 : undefined; 133 134 output.push( 135 separator, 136 `Location: ${textFormatter.formatLocation(message)}`, 137 '', 138 code || '<code not available>' 139 ); 140 141 const testCases = mythxIssue.extra && mythxIssue.extra.testCases; 142 143 if (testCases) { 144 for (const testCase of testCases) { 145 const content = textFormatter.formatTestCase(testCase, fnHashes); 146 147 if (content) { 148 output.push(separator, content); 149 } 150 } 151 } 152 153 return output.join('\n'); 154 }; 155 156 textFormatter.formatResult = result => { 157 const { filePath, sourceCode, functionHashes } = result; 158 159 return result.messages 160 .map(message => 161 textFormatter.formatMessage(message, filePath, sourceCode, functionHashes) 162 ) 163 .join('\n\n'); 164 }; 165 166 textFormatter.run = results => { 167 return results.map(result => textFormatter.formatResult(result)).join('\n\n'); 168 }; 169 170 module.exports = (results) => textFormatter.run(results);