private UserOperationSimulationResult SimulateValidation(
            Transaction transaction,
            UserOperation userOperation,
            BlockHeader parent,
            ITransactionProcessor transactionProcessor)
        {
            bool paymasterWhitelisted      = _whitelistedPaymasters.Contains(userOperation.Paymaster);
            UserOperationTxTracer txTracer = new(
                transaction,
                paymasterWhitelisted,
                userOperation.InitCode != Bytes.Empty, userOperation.Sender,
                userOperation.Paymaster,
                _entryPointContractAddress,
                _logger
                );

            transactionProcessor.Trace(transaction, parent, txTracer);

            FailedOp?failedOp = _userOperationTxBuilder.DecodeEntryPointOutputError(txTracer.Output);

            string?error = null;

            if (!txTracer.Success)
            {
                if (failedOp is not null)
                {
                    error = failedOp.ToString() !;
                }
                else
                {
                    error = txTracer.Error;
                }
                return(UserOperationSimulationResult.Failed(error));
            }

            UserOperationAccessList userOperationAccessList = new(txTracer.AccessedStorage);

            IDictionary <Address, Keccak> addressesToCodeHashes = new Dictionary <Address, Keccak>();

            foreach (Address accessedAddress in txTracer.AccessedAddresses)
            {
                addressesToCodeHashes[accessedAddress] = _stateProvider.GetCodeHash(accessedAddress);
            }

            return(new UserOperationSimulationResult()
            {
                Success = true,
                AccessList = userOperationAccessList,
                AddressesToCodeHashes = addressesToCodeHashes,
                Error = error
            });
        }
        public ResultWrapper <Keccak> Simulate(UserOperation userOperation,
                                               BlockHeader parent,
                                               UInt256?timestamp = null,
                                               CancellationToken cancellationToken = default)
        {
            if (userOperation.AlreadySimulated)
            {
                // codehash of all accessed addresses should not change between calls
                foreach (KeyValuePair <Address, Keccak> kv in userOperation.AddressesToCodeHashes)
                {
                    (Address address, Keccak expectedCodeHash) = kv;
                    Keccak codeHash = _stateProvider.GetCodeHash(address);
                    if (codeHash != expectedCodeHash)
                    {
                        return(ResultWrapper <Keccak> .Fail($"codehash of address {address} changed since initial simulation"));
                    }
                }
            }

            IReleaseSpec            currentSpec          = _specProvider.GetSpec(parent.Number + 1);
            ReadOnlyTxProcessingEnv txProcessingEnv      = _readOnlyTxProcessingEnvFactory.Create();
            ITransactionProcessor   transactionProcessor = txProcessingEnv.Build(_stateProvider.StateRoot);

            // wrap userOp into a tx calling the simulateWallet function off-chain from zero-address (look at EntryPoint.sol for more context)
            Transaction simulateValidationTransaction =
                BuildSimulateValidationTransaction(userOperation, parent, currentSpec);

            UserOperationSimulationResult simulationResult = SimulateValidation(simulateValidationTransaction, userOperation, parent, transactionProcessor);

            if (!simulationResult.Success)
            {
                return(ResultWrapper <Keccak> .Fail(simulationResult.Error ?? "unknown simulation failure"));
            }

            if (userOperation.AlreadySimulated)
            {
                // if previously simulated we must make sure it doesn't access any more than it did on the first round
                if (!userOperation.AccessList.AccessListContains(simulationResult.AccessList.Data))
                {
                    return(ResultWrapper <Keccak> .Fail("access list exceeded"));
                }
            }
            else
            {
                userOperation.AccessList            = simulationResult.AccessList;
                userOperation.AddressesToCodeHashes = simulationResult.AddressesToCodeHashes;
                userOperation.AlreadySimulated      = true;
            }

            return(ResultWrapper <Keccak> .Success(userOperation.RequestId !));
        }