diff --git a/packages/validation-manager/src/BundlerCollectorTracer.ts b/packages/validation-manager/src/BundlerCollectorTracer.ts index 99226e2e..cc8bf564 100644 --- a/packages/validation-manager/src/BundlerCollectorTracer.ts +++ b/packages/validation-manager/src/BundlerCollectorTracer.ts @@ -59,6 +59,7 @@ export interface TopLevelCallInfo { contractSize: { [addr: string]: ContractSizeInfo } extCodeAccessInfo: { [addr: string]: string } oog?: boolean + calls?: [] } /** diff --git a/packages/validation-manager/src/TracerResultParser.ts b/packages/validation-manager/src/TracerResultParser.ts index 54a3e3f2..766b9721 100644 --- a/packages/validation-manager/src/TracerResultParser.ts +++ b/packages/validation-manager/src/TracerResultParser.ts @@ -6,7 +6,7 @@ import { BigNumber, BigNumberish } from 'ethers' import { hexZeroPad, Interface, keccak256 } from 'ethers/lib/utils' import { inspect } from 'util' -import { BundlerTracerResult } from './BundlerCollectorTracer' +import { BundlerTracerResult, TopLevelCallInfo } from './BundlerCollectorTracer' import { IEntryPoint__factory, IPaymaster__factory, @@ -18,7 +18,7 @@ import { ValidationErrors, mapOf, requireCond, - toBytes32, AddressZero + toBytes32, AddressZero, UserOperation } from '@account-abstraction/utils' import { ValidationResult } from './IValidationManager' @@ -189,6 +189,11 @@ function getEntityTitle (userOp: OperationBase, entityAddress: string): string { } } +// opcodes from [OP-011] +const bannedOpCodes = new Set(['GASPRICE', 'GASLIMIT', 'DIFFICULTY', 'TIMESTAMP', 'BASEFEE', 'BLOCKHASH', 'NUMBER', 'ORIGIN', 'GAS', 'CREATE', 'COINBASE', 'SELFDESTRUCT', 'RANDOM', 'PREVRANDAO', 'INVALID']) +// opcodes allowed in staked entities [OP-080] +const opcodesOnlyInStakedEntities = new Set(['BALANCE', 'SELFBALANCE']) + /** * parse collected simulation traces and revert if they break our rules * @param userOp the userOperation that was used in this simulation @@ -206,10 +211,6 @@ export function tracerResultParser ( debug('=== simulation result:', inspect(tracerResults, true, 10, true)) // todo: block access to no-code addresses (might need update to tracer) - // opcodes from [OP-011] - const bannedOpCodes = new Set(['GASPRICE', 'GASLIMIT', 'DIFFICULTY', 'TIMESTAMP', 'BASEFEE', 'BLOCKHASH', 'NUMBER', 'ORIGIN', 'GAS', 'CREATE', 'COINBASE', 'SELFDESTRUCT', 'RANDOM', 'PREVRANDAO', 'INVALID']) - // opcodes allowed in staked entities [OP-080] - const opcodesOnlyInStakedEntities = new Set(['BALANCE', 'SELFBALANCE']) // eslint-disable-next-line @typescript-eslint/no-base-to-string if (Object.values(tracerResults.callsFromEntryPoint).length < 1) { throw new Error('Unexpected traceCall result: no calls from entrypoint.') @@ -255,228 +256,229 @@ export function tracerResultParser ( } const entitySlots: { [addr: string]: Set } = parseEntitySlots(stakeInfoEntities, tracerResults.keccak) - console.log('wtf is tracerResult callsFromEntryPoint') - console.dir(tracerResults.callsFromEntryPoint, { depth: null }) + console.log('wtf is tracerResult callsFromEntryPoint', tracerResults.callsFromEntryPoint) + // console.dir(tracerResults.callsFromEntryPoint, { depth: null }) // console.log('wtf ###############################################') // console.log('wtf is entitySlots', entitySlots) // console.log('wtf is stakeInfoEntities', stakeInfoEntities) // console.log('wtf is tracerResult calls', tracerResults.calls) Object.entries(stakeInfoEntities).forEach(([entityAddress, entStakes]) => { const entityTitle = getEntityTitle(userOp, entityAddress) - const currentNumLevels = tracerResults.callsFromEntryPoint.filter( - info => info.topLevelTargetAddress.toLowerCase() === entityAddress.toLowerCase()) - currentNumLevels.forEach((currentNumLevel) => { - if (currentNumLevel == null) { + const entityCallsFromEntryPoint = tracerResults.callsFromEntryPoint.filter( + info => info.topLevelTargetAddress != null && info.topLevelTargetAddress.toLowerCase() === entityAddress.toLowerCase()) + entityCallsFromEntryPoint.forEach((entityCall) => { + if (entityCall == null) { if (entityAddress.toLowerCase() === userOp.sender.toLowerCase()) { // should never happen... only factory, paymaster are optional. throw new RpcError('missing trace into account validation', ValidationErrors.InvalidFields) } return } - const opcodes = currentNumLevel.opcodes - const access = currentNumLevel.access - if (entityAddress.toLowerCase() === userOp.paymaster?.toLowerCase()) { - // console.log('wtf is paymaster access', access) - console.log('wtf is tracerResult paymaster currentNumLevel') - console.dir(currentNumLevel, { depth: null }) - // console.log('wtf is all tracerResults') - // console.dir(tracerResults, { depth: null }) - } + processEntityCall(entityCall, entityAddress, entityTitle, entStakes, entitySlots, userOp as UserOperation, stakeInfoEntities, entryPointAddress, tracerResults) + }) + }) + // return list of contract addresses by this UserOp. already known not to contain zero-sized addresses. + const addresses = tracerResults.callsFromEntryPoint.flatMap(level => Object.keys(level.contractSize)) + const storageMap: StorageMap = {} + tracerResults.callsFromEntryPoint.forEach(level => { + Object.keys(level.access).forEach(addr => { + storageMap[addr] = storageMap[addr] ?? level.access[addr].reads + }) + }) + return [addresses, storageMap] +} - // [OP-020] - requireCond(!(currentNumLevel.oog ?? false), - `${entityTitle} internally reverts on oog`, ValidationErrors.OpcodeValidation) - - // opcodes from [OP-011] - Object.keys(opcodes).forEach(opcode => { - requireCond(!bannedOpCodes.has(opcode), `${entityTitle} uses banned opcode: ${opcode}`, ValidationErrors.OpcodeValidation) - // [OP-080] - requireCond(!opcodesOnlyInStakedEntities.has(opcode) || isStaked(entStakes), - `unstaked ${entityTitle} uses banned opcode: ${opcode}`, ValidationErrors.OpcodeValidation) - }) - // [OP-031] - if (entityTitle === 'factory') { - requireCond((opcodes.CREATE2 ?? 0) <= 1, `${entityTitle} with too many CREATE2`, ValidationErrors.OpcodeValidation) - } else { - requireCond(opcodes.CREATE2 == null, `${entityTitle} uses banned opcode: CREATE2`, ValidationErrors.OpcodeValidation) - } - Object.entries(access).forEach(([addr, { - reads, - writes, - transientReads, - transientWrites - }]) => { - // testing read/write access on contract "addr" - if (addr.toLowerCase() === sender.toLowerCase()) { - // allowed to access sender's storage - // [STO-010] - return - } +function processEntityCall (entityCall: TopLevelCallInfo, entityAddress: string, entityTitle: string, entStakes: StakeInfo, entitySlots: { [addr: string]: Set }, userOp: UserOperation, stakeInfoEntities: {[addr: string]: StakeInfo}, entryPointAddress: string, tracerResults: BundlerTracerResult): void { + const opcodes = entityCall.opcodes + const access = entityCall.access + // if (entityAddress.toLowerCase() === userOp.paymaster?.toLowerCase()) { + // // console.log('wtf is paymaster access', access) + // console.log('wtf is tracerResult paymaster currentNumLevel') + // console.dir(entityCall, { depth: null }) + // // console.log('wtf is all tracerResults') + // // console.dir(tracerResults, { depth: null }) + // } + + // [OP-020] + requireCond(!(entityCall.oog ?? false), + `${entityTitle} internally reverts on oog`, ValidationErrors.OpcodeValidation) - if (addr.toLowerCase() === entryPointAddress.toLowerCase()) { - // ignore storage access on entryPoint (balance/deposit of entities. - // we block them on method calls: only allowed to deposit, never to read - return - } + // opcodes from [OP-011] + Object.keys(opcodes).forEach(opcode => { + requireCond(!bannedOpCodes.has(opcode), `${entityTitle} uses banned opcode: ${opcode}`, ValidationErrors.OpcodeValidation) + // [OP-080] + requireCond(!opcodesOnlyInStakedEntities.has(opcode) || isStaked(entStakes), + `unstaked ${entityTitle} uses banned opcode: ${opcode}`, ValidationErrors.OpcodeValidation) + }) + // [OP-031] + if (entityTitle === 'factory') { + requireCond((opcodes.CREATE2 ?? 0) <= 1, `${entityTitle} with too many CREATE2`, ValidationErrors.OpcodeValidation) + } else { + requireCond(opcodes.CREATE2 == null, `${entityTitle} uses banned opcode: CREATE2`, ValidationErrors.OpcodeValidation) + } + Object.entries(access).forEach(([addr, { + reads, + writes, + transientReads, + transientWrites + }]) => { + // testing read/write access on contract "addr" + if (addr.toLowerCase() === userOp.sender.toLowerCase()) { + // allowed to access sender's storage + // [STO-010] + return + } - // return true if the given slot is associated with the given address, given the known keccak operations: - // @param slot the SLOAD/SSTORE slot address we're testing - // @param addr - the address we try to check for association with - // @param reverseKeccak - a mapping we built for keccak values that contained the address - function associatedWith (slot: string, addr: string, entitySlots: { [addr: string]: Set }): boolean { - const addrPadded = hexZeroPad(addr, 32).toLowerCase() - if (slot === addrPadded) { - return true - } - const k = entitySlots[addr] - if (k == null) { - return false - } - const slotN = BigNumber.from(slot) - // scan all slot entries to check of the given slot is within a structure, starting at that offset. - // assume a maximum size on a (static) structure size. - for (const k1 of k.keys()) { - const kn = BigNumber.from(k1) - if (slotN.gte(kn) && slotN.lt(kn.add(128))) { - return true - } - } - return false - } + if (addr.toLowerCase() === entryPointAddress.toLowerCase()) { + // ignore storage access on entryPoint (balance/deposit of entities. + // we block them on method calls: only allowed to deposit, never to read + return + } - debug('dump keccak calculations and reads', { - entityTitle, - entityAddress, - k: mapOf(tracerResults.keccak, k => keccak256(k)), - reads - }) - - // [OP-070]: treat transient storage (TLOAD/TSTORE) just like storage. - // scan all slots. find a referenced slot - // at the end of the scan, we will check if the entity has stake, and report that slot if not. - let requireStakeSlot: string | undefined - [ - ...Object.keys(writes), - ...Object.keys(reads), - ...Object.keys(transientWrites ?? {}), - ...Object.keys(transientReads ?? {}) - ].forEach(slot => { - // slot associated with sender is allowed (e.g. token.balanceOf(sender) - // but during initial UserOp (where there is an initCode), it is allowed only for staked entity - // if (addr.toLowerCase() === userOp.paymaster?.toLowerCase()) { - // console.log('wtf is paymaster, slot ,entity', addr, slot, entityAddress.toLowerCase()) - // } - if (associatedWith(slot, sender, entitySlots)) { - if (userOp.factory != null && userOp.factory !== AddressZero) { - // special case: account.validateUserOp is allowed to use assoc storage if factory is staked. - // [STO-022], [STO-021] - if (!(entityAddress.toLowerCase() === sender.toLowerCase() && isStaked(stakeInfoEntities[userOp.factory.toLowerCase()]))) { - requireStakeSlot = slot - } - } - } else if (associatedWith(slot, entityAddress, entitySlots)) { - console.log('------------- wtf not assoc??????') - // [STO-032] - // accessing a slot associated with entityAddr (e.g. token.balanceOf(paymaster) - requireStakeSlot = slot - } else if (addr.toLowerCase() === entityAddress.toLowerCase()) { - // [STO-031] - // accessing storage member of entity itself requires stake. - console.log('wtf sto-031') - requireStakeSlot = slot - } else if (writes[slot] == null && transientWrites[slot] == null) { - // [STO-033]: staked entity have read-only access to any storage in non-entity contract. + debug('dump keccak calculations and reads', { + entityTitle, + entityAddress, + k: mapOf(tracerResults.keccak, k => keccak256(k)), + reads + }) + + // [OP-070]: treat transient storage (TLOAD/TSTORE) just like storage. + // scan all slots. find a referenced slot + // at the end of the scan, we will check if the entity has stake, and report that slot if not. + let requireStakeSlot: string | undefined + [ + ...Object.keys(writes), + ...Object.keys(reads), + ...Object.keys(transientWrites ?? {}), + ...Object.keys(transientReads ?? {}) + ].forEach(slot => { + // slot associated with sender is allowed (e.g. token.balanceOf(sender) + // but during initial UserOp (where there is an initCode), it is allowed only for staked entity + // if (addr.toLowerCase() === userOp.paymaster?.toLowerCase()) { + // console.log('wtf is paymaster, slot ,entity', addr, slot, entityAddress.toLowerCase()) + // } + if (associatedWith(slot, userOp.sender.toLowerCase(), entitySlots)) { + if (userOp.factory != null && userOp.factory !== AddressZero) { + // special case: account.validateUserOp is allowed to use assoc storage if factory is staked. + // [STO-022], [STO-021] + if (!(entityAddress.toLowerCase() === userOp.sender.toLowerCase() && isStaked(stakeInfoEntities[userOp.factory.toLowerCase()]))) { requireStakeSlot = slot - } else { - // accessing arbitrary storage of another contract is not allowed - const isWrite = Object.keys(writes).includes(slot) || Object.keys(transientWrites ?? {}).includes(slot) - const isTransient = Object.keys(transientReads ?? {}).includes(slot) || Object.keys(transientWrites ?? {}).includes(slot) - const readWrite = isWrite ? 'write to' : 'read from' - const transientStr = isTransient ? 'transient ' : '' - requireCond(false, - `${entityTitle} has forbidden ${readWrite} ${transientStr}${nameAddr(addr, entityTitle)} slot ${slot}`, - ValidationErrors.OpcodeValidation, { [entityTitle]: entStakes?.addr }) } - }) + } + } else if (associatedWith(slot, entityAddress, entitySlots)) { + // [STO-032] + // accessing a slot associated with entityAddr (e.g. token.balanceOf(paymaster) + requireStakeSlot = slot + } else if (addr.toLowerCase() === entityAddress.toLowerCase()) { + // [STO-031] + // accessing storage member of entity itself requires stake. + requireStakeSlot = slot + } else if (writes[slot] == null && transientWrites[slot] == null) { + // [STO-033]: staked entity have read-only access to any storage in non-entity contract. + requireStakeSlot = slot + } else { + // accessing arbitrary storage of another contract is not allowed + const isWrite = Object.keys(writes).includes(slot) || Object.keys(transientWrites ?? {}).includes(slot) + const isTransient = Object.keys(transientReads ?? {}).includes(slot) || Object.keys(transientWrites ?? {}).includes(slot) + const readWrite = isWrite ? 'write to' : 'read from' + const transientStr = isTransient ? 'transient ' : '' + requireCond(false, + `${entityTitle} has forbidden ${readWrite} ${transientStr}${nameAddr(addr, entityTitle)} slot ${slot}`, + ValidationErrors.OpcodeValidation, { [entityTitle]: entStakes?.addr }) + } + }) - // if addr is current account/paymaster/factory, then return that title - // otherwise, return addr as-is - function nameAddr (addr: string, currentEntity: string): string { - const [title] = Object.entries(stakeInfoEntities).find(([title, info]) => - info?.addr.toLowerCase() === addr.toLowerCase()) ?? [] + // if addr is current account/paymaster/factory, then return that title + // otherwise, return addr as-is + function nameAddr (addr: string, currentEntity: string): string { + const [title] = Object.entries(stakeInfoEntities).find(([title, info]) => + info?.addr.toLowerCase() === addr.toLowerCase()) ?? [] - return title ?? addr - } + return title ?? addr + } - requireCondAndStake(requireStakeSlot != null, entStakes, - `unstaked ${entityTitle} accessed ${nameAddr(addr, entityTitle)} slot ${requireStakeSlot}`) - }) + requireCondAndStake(requireStakeSlot != null, entStakes, + `unstaked ${entityTitle} accessed ${nameAddr(addr, entityTitle)} slot ${requireStakeSlot}`) + }) - // check if the given entity is staked - function isStaked (entStake?: StakeInfo): boolean { - return entStake != null && BigNumber.from(1).lte(entStake.stake) && BigNumber.from(1).lte(entStake.unstakeDelaySec) - } + // check if the given entity is staked + function isStaked (entStake?: StakeInfo): boolean { + return entStake != null && BigNumber.from(1).lte(entStake.stake) && BigNumber.from(1).lte(entStake.unstakeDelaySec) + } - // helper method: if condition is true, then entity must be staked. - function requireCondAndStake (cond: boolean, entStake: StakeInfo | undefined, failureMessage: string): void { - if (!cond) { - return - } - if (entStake == null) { - throw new Error(`internal: ${entityTitle} not in userOp, but has storage accesses in ${JSON.stringify(access)}`) - } - requireCond(isStaked(entStake), - failureMessage, ValidationErrors.OpcodeValidation, { [entityTitle]: entStakes?.addr }) + // helper method: if condition is true, then entity must be staked. + function requireCondAndStake (cond: boolean, entStake: StakeInfo | undefined, failureMessage: string): void { + if (!cond) { + return + } + if (entStake == null) { + throw new Error(`internal: ${entityTitle} not in userOp, but has storage accesses in ${JSON.stringify(access)}`) + } + requireCond(isStaked(entStake), + failureMessage, ValidationErrors.OpcodeValidation, { [entityTitle]: entStakes?.addr }) - // TODO: check real minimum stake values - } + // TODO: check real minimum stake values + } - // the only contract we allow to access before its deployment is the "sender" itself, which gets created. - let illegalZeroCodeAccess: any - for (const addr of Object.keys(currentNumLevel.contractSize)) { - // [OP-042] - if (addr !== sender && addr.toLowerCase() !== entryPointAddress.toLowerCase() && currentNumLevel.contractSize[addr].contractSize <= 2) { - illegalZeroCodeAccess = currentNumLevel.contractSize[addr] - illegalZeroCodeAccess.address = addr - break - } - } - // [OP-041] - requireCond( - illegalZeroCodeAccess == null, - `${entityTitle} accesses un-deployed contract address ${illegalZeroCodeAccess?.address as string} with opcode ${illegalZeroCodeAccess?.opcode as string}`, - ValidationErrors.OpcodeValidation) - - let illegalEntryPointCodeAccess - for (const addr of Object.keys(currentNumLevel.extCodeAccessInfo)) { - if (addr.toLowerCase() === entryPointAddress.toLowerCase()) { - illegalEntryPointCodeAccess = currentNumLevel.extCodeAccessInfo[addr] - break - } - } - requireCond( - illegalEntryPointCodeAccess == null, - `${entityTitle} accesses EntryPoint contract address ${entryPointAddress} with opcode ${illegalEntryPointCodeAccess}`, - ValidationErrors.OpcodeValidation) - }) - }) - // return list of contract addresses by this UserOp. already known not to contain zero-sized addresses. - const addresses = tracerResults.callsFromEntryPoint.flatMap(level => Object.keys(level.contractSize)) - const storageMap: StorageMap = {} - tracerResults.callsFromEntryPoint.forEach(level => { - Object.keys(level.access).forEach(addr => { - storageMap[addr] = storageMap[addr] ?? level.access[addr].reads + // the only contract we allow to access before its deployment is the "sender" itself, which gets created. + let illegalZeroCodeAccess: any + if (userOp.factory?.toLowerCase() === entityAddress.toLowerCase()) { + console.log('wtf contract size', entityCall.contractSize) + } + for (const addr of Object.keys(entityCall.contractSize)) { + // [OP-042] + if (addr !== userOp.sender.toLowerCase() && addr.toLowerCase() !== entryPointAddress.toLowerCase() && entityCall.contractSize[addr].contractSize <= 2) { + illegalZeroCodeAccess = entityCall.contractSize[addr] + illegalZeroCodeAccess.address = addr + break + } + } + // [OP-041] + requireCond( + illegalZeroCodeAccess == null, + `${entityTitle} accesses un-deployed contract address ${illegalZeroCodeAccess?.address as string} with opcode ${illegalZeroCodeAccess?.opcode as string}`, + ValidationErrors.OpcodeValidation) + + let illegalEntryPointCodeAccess + for (const addr of Object.keys(entityCall.extCodeAccessInfo)) { + if (addr.toLowerCase() === entryPointAddress.toLowerCase()) { + illegalEntryPointCodeAccess = entityCall.extCodeAccessInfo[addr] + break + } + } + requireCond( + illegalEntryPointCodeAccess == null, + `${entityTitle} accesses EntryPoint contract address ${entryPointAddress} with opcode ${illegalEntryPointCodeAccess}`, + ValidationErrors.OpcodeValidation) + + if (entityCall.calls != null) { + entityCall.calls.forEach((call: any) => { + processEntityCall(call, entityAddress, entityTitle, entStakes, entitySlots, userOp, stakeInfoEntities, entryPointAddress, tracerResults) }) - }) - return [addresses, storageMap] + } } -export function flattenCalls (calls: any[]): any[] { - return calls.reduce((acc: any, call: any) => { - acc.push(call) // Add the current call to the accumulator - if (call.calls != null) { - acc.push(...flattenCalls(call.calls)) // Recursively flatten the nested calls +// return true if the given slot is associated with the given address, given the known keccak operations: +// @param slot the SLOAD/SSTORE slot address we're testing +// @param addr - the address we try to check for association with +// @param reverseKeccak - a mapping we built for keccak values that contained the address +function associatedWith (slot: string, addr: string, entitySlots: { [addr: string]: Set }): boolean { + const addrPadded = hexZeroPad(addr, 32).toLowerCase() + if (slot === addrPadded) { + return true + } + const k = entitySlots[addr] + if (k == null) { + return false + } + const slotN = BigNumber.from(slot) + // scan all slot entries to check of the given slot is within a structure, starting at that offset. + // assume a maximum size on a (static) structure size. + for (const k1 of k.keys()) { + const kn = BigNumber.from(k1) + if (slotN.gte(kn) && slotN.lt(kn.add(128))) { + return true } - return acc - }, []) + } + return false } diff --git a/packages/validation-manager/src/ValidationManager.ts b/packages/validation-manager/src/ValidationManager.ts index a8121d98..5d246e64 100644 --- a/packages/validation-manager/src/ValidationManager.ts +++ b/packages/validation-manager/src/ValidationManager.ts @@ -226,7 +226,9 @@ export class ValidationManager implements IValidationManager { }) console.log('wtf validation res', res) // todo fix - this.convertTracerResult(tracerResult) + // console.log('wtf tracer res') + // console.dir(tracerResult, { depth: null }) + this.convertTracerResult(tracerResult, userOp) let contractAddresses: string[] [contractAddresses, storageMap] = tracerResultParser(userOp, tracerResult, res, this.entryPoint.address) // if no previous contract hashes, then calculate hashes of contracts @@ -337,10 +339,20 @@ export class ValidationManager implements IValidationManager { } // todo fix rest of the code to work with the new tracer result instead of adjusting it here - convertTracerResult (tracerResult: any): BundlerTracerResult { + convertTracerResult (tracerResult: any, userOp: UserOperation): BundlerTracerResult { + const SENDER_CREATOR = '0xefc2c1444ebcc4db75e7613d20c6a62ff67a167c'.toLowerCase() this.addFirstCall(tracerResult) + // Before flattening we add top level addresses for calls from EntryPoint and from SENDER_CREATOR + tracerResult.calls.forEach((call: {calls: any, to: any, topLevelTargetAddress: any}) => { + call.topLevelTargetAddress = call.to + if (call.to.toLowerCase() === SENDER_CREATOR && call.calls != null) { + call.calls.forEach((subcall: any) => { + subcall.topLevelTargetAddress = subcall.to + }) + } + }) tracerResult.calls = this.flattenCalls(tracerResult.calls) - tracerResult.calls.forEach((call: { opcodes: any, usedOpcodes: any, topLevelTargetAddress: any, to: any, access: any, accessedSlots: any, extCodeAccessInfo: any, outOfGas: any, oog: any }) => { + tracerResult.calls.forEach((call: { to: any, from: any, opcodes: any, usedOpcodes: any, access: any, accessedSlots: any, extCodeAccessInfo: any, outOfGas: any, oog: any }) => { // console.log('wtf is call.usedOpcodes', call.usedOpcodes) call.opcodes = {} if (call.usedOpcodes != null) { @@ -349,7 +361,7 @@ export class ValidationManager implements IValidationManager { }) } // console.log('wtf is call.opcodes', call.opcodes) - call.topLevelTargetAddress = call.to + // call.topLevelTargetAddress = call.to if (call.access == null) { call.access = {} @@ -370,12 +382,19 @@ export class ValidationManager implements IValidationManager { if (call.extCodeAccessInfo == null) { call.extCodeAccessInfo = {} } + const newExtCode: any = {} + if (Array.isArray(call.extCodeAccessInfo)) { + call.extCodeAccessInfo.forEach((addr: any) => { + newExtCode[addr] = 1 + }) + } + call.extCodeAccessInfo = newExtCode call.oog = call.outOfGas }) // TODO: This is a hardcoded address of SenderCreator immutable member in EntryPoint. Any change in EntryPoint's code // requires a change of this address. // TODO remove this BS - tracerResult.callsFromEntryPoint = tracerResult.calls // .filter((call: { from: string }) => call.from.toLowerCase() === this.entryPoint.address.toLowerCase() || call.from.toLowerCase() === '0xefc2c1444ebcc4db75e7613d20c6a62ff67a167c') + tracerResult.callsFromEntryPoint = tracerResult.calls.filter((call: { from: string }) => call.from.toLowerCase() === this.entryPoint.address.toLowerCase() || call.from.toLowerCase() === SENDER_CREATOR) return tracerResult }