UNPKG

0xweb

Version:

Contract package manager and other web3 tools

368 lines (352 loc) 14.3 kB
import { $is } from '@dequanto/utils/$is'; import { EvmBytecode } from '../EvmBytecode'; import Opcode from '../interfaces/IOpcode'; import stringify from '../utils/stringify'; const updateCallDataLoad = (item: any, types: any) => { for (const i in item) { if (item.hasOwnProperty(i)) { if ( typeof item[i] === 'object' && item[i].name === 'CALLDATALOAD' && $is.BigInt(item[i].location) ) { const argNumber = item[i].location .subtract(4) .divide(32) .toString(); item[i].type = types[argNumber]; } if (typeof item[i] === 'object') { updateCallDataLoad(item[i], types); } } } }; const findReturns = (item: any) => { const returns = []; for (const i in item) { if (item.hasOwnProperty(i)) { if ( typeof item[i] === 'object' && item[i].name === 'RETURN' && item[i].items && item[i].items.length > 0 ) { returns.push(item[i].items); } if (typeof item[i] === 'object') { const deepReturns: any = findReturns(item[i]); if (deepReturns.length > 0) { returns.push(...deepReturns); } } } } return returns; }; export class TopLevelFunction { readonly name: string; readonly type?: string; readonly hash: any; readonly gasUsed: number; readonly payable: boolean; readonly visibility: string; readonly constant: boolean; readonly items: any; readonly returns: any; constructor(state: EvmBytecode, items: any, hash: any, gasUsed: number) { this.name = 'Function'; this.hash = hash; this.gasUsed = gasUsed; this.items = items; this.payable = true; this.visibility = 'public'; this.constant = false; this.returns = []; if ( this.items.length > 0 && this.items[0] instanceof REQUIRE && this.items[0].condition.name === 'ISZERO' && this.items[0].condition.item.name === 'CALLVALUE' ) { this.payable = false; this.items.shift(); } if (this.items.length === 1 && this.items[0].name === 'RETURN') { this.constant = true; } if (this.hash in state.store.functionHashes) { const functionName = (state.store.functionHashes)[this.hash].split('(')[0]; const argumentTypes = (state.store.functionHashes)[this.hash] .replace(functionName, '') .substr(1) .slice(0, -1) .split(','); if ( argumentTypes.length > 1 || (argumentTypes.length === 1 && argumentTypes[0] !== '') ) { this.items.forEach((item: any) => updateCallDataLoad(item, argumentTypes)); } } const returns: any = []; this.items.forEach((item: any) => { const deepReturns = findReturns(item); if (deepReturns.length > 0) { returns.push(...deepReturns); } }); if ( returns.length > 0 && returns.every( (returnItem: any) => returnItem.length === returns[0].length && returnItem.map((item: any) => item.type).join('') === returns[0].map((item: any) => item.type).join('') ) ) { returns[0].forEach((item: any) => { if ($is.BigInt(item)) { this.returns.push('uint256'); } else if (item.type) { this.returns.push(item.type); } else { this.returns.push('unknown'); } }); } else if (returns.length > 0) { this.returns.push('<unknown>'); } } } export class Variable { readonly name: string; readonly label: string | false; readonly types: any; constructor(label: string | false, types: any) { this.name = 'Variable'; this.label = label; this.types = types; } } export class REQUIRE { readonly name: string; readonly type?: string; readonly wrapped: boolean; readonly condition: any; constructor(condition: any) { this.name = 'REQUIRE'; this.wrapped = true; this.condition = condition; } toString() { return 'require(' + stringify(this.condition) + ');'; } } export class JUMPI { readonly name: string; readonly type?: string; readonly wrapped: boolean; readonly condition: any; readonly location: any; readonly valid: boolean; readonly true?: any; readonly false?: any; readonly payable?: boolean; constructor(condition: any, location: any, ifTrue?: any, ifFalse?: any, skipped?: boolean) { this.name = 'JUMPI'; this.wrapped = true; this.condition = condition; this.location = location; if (skipped) { this.valid = true; } else if (ifTrue && ifFalse) { this.valid = true; this.true = ifTrue; this.false = ifFalse; if ( this.true.length >= 1 && this.true[0] instanceof REQUIRE && this.true[0].condition.name === 'ISZERO' && this.true[0].condition.item.name === 'CALLVALUE' ) { this.payable = false; this.true.shift(); } else { this.payable = true; } } else { this.valid = false; } } toString() { if (this.valid && this.true && this.false) { return stringify(this.condition); } else if (this.valid) { return 'if' + stringify(this.condition) + ' goto(' + stringify(this.location) + ');'; } else { return "revert(\"Bad jump destination\");"; } } } export default (opcode: Opcode, state: EvmBytecode): void => { const jumpLocation = state.stack.pop(); const jumpCondition = state.stack.pop(); const opcodes = state.getOpcodes(); if (!$is.BigInt(jumpLocation)) { state.halted = true; state.instructions.push(new JUMPI(jumpCondition, jumpLocation)); } else { const jumpLocationData = opcodes.find((o: any) => o.pc === Number(jumpLocation)); if (!jumpLocationData || jumpLocationData.name !== 'JUMPDEST') { state.halted = true; state.instructions.push(new JUMPI(jumpCondition, jumpLocation)); } else if ($is.BigInt(jumpCondition)) { const jumpIndex = opcodes.indexOf(jumpLocationData); if ( jumpIndex >= 0 && jumpCondition !== 0n && !(opcode.pc + ':' + Number(jumpLocation) in state.jumps) ) { state.jumps[opcode.pc + ':' + Number(jumpLocation)] = true; state.pc = jumpIndex; } } else if ( !(opcode.pc + ':' + Number(jumpLocation) in state.jumps) && jumpCondition.name === 'SIG' ) { const jumpIndex = opcodes.indexOf(jumpLocationData); if (jumpIndex >= 0) { const functionClone: any = state.clone(); functionClone.pc = jumpIndex; const functionCloneTree = functionClone.parse(); state.functions[jumpCondition.hash] = new TopLevelFunction( state, functionCloneTree, jumpCondition.hash, functionClone.gasUsed ); if ( jumpCondition.hash in state.store.functionHashes && functionCloneTree.length === 1 && functionCloneTree[0].name === 'RETURN' && functionCloneTree[0].items.every((item: any) => item.name === 'MappingLoad') ) { functionCloneTree[0].items.forEach((item: any) => { const fullFunction = (state.store.functionHashes)[jumpCondition.hash]; state.mappings[item.location].name = fullFunction.split('(')[0]; if ( item.structlocation && !state.mappings[item.location].structs.includes(item.structlocation) ) { state.mappings[item.location].structs.push(item.structlocation); } }); delete state.functions[jumpCondition.hash]; } else if ( jumpCondition.hash in state.store.functionHashes && state.functions[jumpCondition.hash].items.length === 1 && state.functions[jumpCondition.hash].items[0].name === 'RETURN' && state.functions[jumpCondition.hash].items[0].items.length === 1 && state.functions[jumpCondition.hash].items[0].items[0].name === 'SLOAD' && $is.BigInt( state.functions[jumpCondition.hash].items[0].items[0].location ) ) { if ( !( state.functions[jumpCondition.hash].items[0].items[0].location in state.variables ) ) { const fullFunction = (state.store.functionHashes)[jumpCondition.hash]; state.variables[ state.functions[jumpCondition.hash].items[0].items[0].location ] = new Variable(fullFunction.split('(')[0], []); delete state.functions[jumpCondition.hash]; } else { const fullFunction = (state.store.functionHashes)[jumpCondition.hash]; state.variables[ state.functions[jumpCondition.hash].items[0].items[0].location ].label = fullFunction.split('(')[0]; delete state.functions[jumpCondition.hash]; } } } } else if ( !(opcode.pc + ':' + Number(jumpLocation) in state.jumps) && ((jumpCondition.name === 'LT' && jumpCondition.left.name === 'CALLDATASIZE' && $is.BigInt(jumpCondition.right) && jumpCondition.right.equals(4)) || (jumpCondition.name === 'ISZERO' && jumpCondition.item.name === 'CALLDATASIZE')) ) { const jumpIndex = opcodes.indexOf(jumpLocationData); if (jumpIndex >= 0) { state.halted = true; const trueClone: any = state.clone(); trueClone.pc = jumpIndex; const trueCloneTree = trueClone.parse(); const falseClone = state.clone(); falseClone.pc = state.pc + 1; const falseCloneTree = falseClone.getInstructions(); if ( trueCloneTree.length > 0 && trueCloneTree.length === falseCloneTree.length && trueCloneTree[0].name !== 'REVERT' && trueCloneTree[0].name !== 'INVALID' && trueCloneTree.map((item: any) => stringify(item)).join('') === falseCloneTree.map((item: any) => stringify(item)).join('') ) { state.functions[''] = new TopLevelFunction( state, trueCloneTree, '', trueCloneTree.gasUsed ); } else if ( trueCloneTree.length > 0 && trueCloneTree[0].name !== 'REVERT' && trueCloneTree[0].name !== 'INVALID' ) { state.instructions.push( new JUMPI(jumpCondition, jumpLocation, trueCloneTree, falseCloneTree) ); } } else { state.instructions.push(new JUMPI(jumpCondition, jumpLocation)); } } else if (!(opcode.pc + ':' + Number(jumpLocation) in state.jumps)) { const jumpIndex = opcodes.indexOf(jumpLocationData); state.jumps[opcode.pc + ':' + Number(jumpLocation)] = true; if (jumpIndex >= 0) { state.halted = true; const trueClone: any = state.clone(); trueClone.pc = jumpIndex; const trueCloneTree = trueClone.parse(); const falseClone = state.clone(); falseClone.pc = state.pc + 1; const falseCloneTree: any = falseClone.getInstructions(); if ( (falseCloneTree.length === 1 && 'name' in falseCloneTree[0] && (falseCloneTree[0].name === 'REVERT' && falseCloneTree[0].items && falseCloneTree[0].items.length === 0)) || falseCloneTree[0].name === 'INVALID' ) { state.instructions.push(new REQUIRE(jumpCondition)); state.instructions.push(...trueCloneTree); } else { state.instructions.push( new JUMPI(jumpCondition, jumpLocation, trueCloneTree, falseCloneTree) ); } } else { state.instructions.push(new JUMPI(jumpCondition, jumpLocation)); } } else { state.instructions.push(new JUMPI(jumpCondition, jumpLocation, null, null, true)); } } };