PHP WebShell

Текущая директория: /usr/lib/node_modules/bitgo/node_modules/viem/actions/public

Просмотр файла: simulateCalls.ts

import type { AbiStateMutability, Address, Narrow } from 'abitype'
import * as AbiConstructor from 'ox/AbiConstructor'
import * as AbiFunction from 'ox/AbiFunction'

import { parseAccount } from '../../accounts/utils/parseAccount.js'
import type { Client } from '../../clients/createClient.js'
import type { Transport } from '../../clients/transports/createTransport.js'
import { ethAddress, zeroAddress } from '../../constants/address.js'
import { deploylessCallViaBytecodeBytecode } from '../../constants/contracts.js'
import { BaseError } from '../../errors/base.js'
import type { ErrorType } from '../../errors/utils.js'
import type { Account } from '../../types/account.js'
import type { Block } from '../../types/block.js'
import type { Call, Calls } from '../../types/calls.js'
import type { Chain } from '../../types/chain.js'
import type { Log } from '../../types/log.js'
import type { Hex } from '../../types/misc.js'
import type { MulticallResults } from '../../types/multicall.js'
import type { StateOverride } from '../../types/stateOverride.js'
import type { Mutable } from '../../types/utils.js'
import {
  type EncodeFunctionDataErrorType,
  encodeFunctionData,
} from '../../utils/abi/encodeFunctionData.js'
import { hexToBigInt } from '../../utils/index.js'
import {
  type CreateAccessListErrorType,
  createAccessList,
} from './createAccessList.js'
import {
  type SimulateBlocksErrorType,
  type SimulateBlocksParameters,
  simulateBlocks,
} from './simulateBlocks.js'

const getBalanceCode =
  '0x6080604052348015600e575f80fd5b5061016d8061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c8063f8b2cb4f1461002d575b5f80fd5b610047600480360381019061004291906100db565b61005d565b604051610054919061011e565b60405180910390f35b5f8173ffffffffffffffffffffffffffffffffffffffff16319050919050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100aa82610081565b9050919050565b6100ba816100a0565b81146100c4575f80fd5b50565b5f813590506100d5816100b1565b92915050565b5f602082840312156100f0576100ef61007d565b5b5f6100fd848285016100c7565b91505092915050565b5f819050919050565b61011881610106565b82525050565b5f6020820190506101315f83018461010f565b9291505056fea26469706673582212203b9fe929fe995c7cf9887f0bdba8a36dd78e8b73f149b17d2d9ad7cd09d2dc6264736f6c634300081a0033'

export type SimulateCallsParameters<
  calls extends readonly unknown[] = readonly unknown[],
  account extends Account | Address | undefined = Account | Address | undefined,
> = Omit<SimulateBlocksParameters, 'blocks' | 'returnFullTransactions'> & {
  /** Account attached to the calls (msg.sender). */
  account?: account | undefined
  /** Calls to simulate. */
  calls: Calls<Narrow<calls>>
  /** State overrides. */
  stateOverrides?: StateOverride | undefined
  /** Whether to trace asset changes. */
  traceAssetChanges?: boolean | undefined
}

export type SimulateCallsReturnType<
  calls extends readonly unknown[] = readonly unknown[],
> = {
  /** Asset changes. */
  assetChanges: readonly {
    token: {
      address: Address
      decimals?: number | undefined
      symbol?: string | undefined
    }
    value: { pre: bigint; post: bigint; diff: bigint }
  }[]
  /** Block results. */
  block: Block
  /** Call results. */
  results: MulticallResults<
    Narrow<calls>,
    true,
    {
      extraProperties: {
        data: Hex
        gasUsed: bigint
        logs?: Log[] | undefined
      }
      error: Error
      mutability: AbiStateMutability
    }
  >
}

export type SimulateCallsErrorType =
  | AbiFunction.encodeData.ErrorType
  | AbiFunction.from.ErrorType
  | CreateAccessListErrorType
  | EncodeFunctionDataErrorType
  | SimulateBlocksErrorType
  | ErrorType

/**
 * Simulates execution of a batch of calls.
 *
 * @param client - Client to use
 * @param parameters - {@link SimulateCallsParameters}
 * @returns Results. {@link SimulateCallsReturnType}
 *
 * @example
 * ```ts
 * import { createPublicClient, http, parseEther } from 'viem'
 * import { mainnet } from 'viem/chains'
 * import { simulateCalls } from 'viem/actions'
 *
 * const client = createPublicClient({
 *   chain: mainnet,
 *   transport: http(),
 * })
 *
 * const result = await simulateCalls(client, {
 *   account: '0x5a0b54d5dc17e482fe8b0bdca5320161b95fb929',
 *   calls: [{
 *     {
 *       data: '0xdeadbeef',
 *       to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
 *     },
 *     {
 *       to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
 *       value: parseEther('1'),
 *     },
 *   ]
 * })
 * ```
 */
export async function simulateCalls<
  const calls extends readonly unknown[],
  chain extends Chain | undefined,
  account extends Account | Address | undefined = undefined,
>(
  client: Client<Transport, chain>,
  parameters: SimulateCallsParameters<calls, account>,
): Promise<SimulateCallsReturnType<calls>> {
  const {
    blockNumber,
    blockTag,
    calls,
    stateOverrides,
    traceAssetChanges,
    traceTransfers,
    validation,
  } = parameters

  const account = parameters.account
    ? parseAccount(parameters.account)
    : undefined

  if (traceAssetChanges && !account)
    throw new BaseError(
      '`account` is required when `traceAssetChanges` is true',
    )

  // Derive bytecode to extract ETH balance via a contract call.
  const getBalanceData = account
    ? AbiConstructor.encode(AbiConstructor.from('constructor(bytes, bytes)'), {
        bytecode: deploylessCallViaBytecodeBytecode,
        args: [
          getBalanceCode,
          AbiFunction.encodeData(
            AbiFunction.from('function getBalance(address)'),
            [account.address],
          ),
        ],
      })
    : undefined

  // Fetch ERC20/721 addresses that were "touched" from the calls.
  const assetAddresses = traceAssetChanges
    ? await Promise.all(
        parameters.calls.map(async (call: any) => {
          if (!call.data && !call.abi) return
          const { accessList } = await createAccessList(client, {
            account: account!.address,
            ...call,
            data: call.abi ? encodeFunctionData(call) : call.data,
          })
          return accessList.map(({ address, storageKeys }) =>
            storageKeys.length > 0 ? address : null,
          )
        }),
      ).then((x) => x.flat().filter(Boolean))
    : []

  const blocks = await simulateBlocks(client, {
    blockNumber,
    blockTag: blockTag as undefined,
    blocks: [
      ...(traceAssetChanges
        ? [
            // ETH pre balances
            {
              calls: [{ data: getBalanceData }],
              stateOverrides,
            },

            // Asset pre balances
            {
              calls: assetAddresses.map((address, i) => ({
                abi: [
                  AbiFunction.from(
                    'function balanceOf(address) returns (uint256)',
                  ),
                ],
                functionName: 'balanceOf',
                args: [account!.address],
                to: address,
                from: zeroAddress,
                nonce: i,
              })),
              stateOverrides: [
                {
                  address: zeroAddress,
                  nonce: 0,
                },
              ],
            },
          ]
        : []),

      {
        calls: [...calls, {}].map((call) => ({
          ...(call as Call),
          from: account?.address,
        })) as any,
        stateOverrides,
      },

      ...(traceAssetChanges
        ? [
            // ETH post balances
            {
              calls: [{ data: getBalanceData }],
            },

            // Asset post balances
            {
              calls: assetAddresses.map((address, i) => ({
                abi: [
                  AbiFunction.from(
                    'function balanceOf(address) returns (uint256)',
                  ),
                ],
                functionName: 'balanceOf',
                args: [account!.address],
                to: address,
                from: zeroAddress,
                nonce: i,
              })),
              stateOverrides: [
                {
                  address: zeroAddress,
                  nonce: 0,
                },
              ],
            },

            // Decimals
            {
              calls: assetAddresses.map((address, i) => ({
                to: address,
                abi: [
                  AbiFunction.from('function decimals() returns (uint256)'),
                ],
                functionName: 'decimals',
                from: zeroAddress,
                nonce: i,
              })),
              stateOverrides: [
                {
                  address: zeroAddress,
                  nonce: 0,
                },
              ],
            },

            // Token URI
            {
              calls: assetAddresses.map((address, i) => ({
                to: address,
                abi: [
                  AbiFunction.from(
                    'function tokenURI(uint256) returns (string)',
                  ),
                ],
                functionName: 'tokenURI',
                args: [0n],
                from: zeroAddress,
                nonce: i,
              })),
              stateOverrides: [
                {
                  address: zeroAddress,
                  nonce: 0,
                },
              ],
            },

            // Symbols
            {
              calls: assetAddresses.map((address, i) => ({
                to: address,
                abi: [AbiFunction.from('function symbol() returns (string)')],
                functionName: 'symbol',
                from: zeroAddress,
                nonce: i,
              })),
              stateOverrides: [
                {
                  address: zeroAddress,
                  nonce: 0,
                },
              ],
            },
          ]
        : []),
    ],
    traceTransfers,
    validation,
  })

  const block_results = traceAssetChanges ? blocks[2] : blocks[0]
  const [
    block_ethPre,
    block_assetsPre,
    ,
    block_ethPost,
    block_assetsPost,
    block_decimals,
    block_tokenURI,
    block_symbols,
  ] = traceAssetChanges ? blocks : []

  // Extract call results from the simulation.
  const { calls: block_calls, ...block } = block_results
  const results = block_calls.slice(0, -1) ?? []

  // Extract pre-execution ETH and asset balances.
  const ethPre = block_ethPre?.calls ?? []
  const assetsPre = block_assetsPre?.calls ?? []
  const balancesPre = [...ethPre, ...assetsPre].map((call) =>
    call.status === 'success' ? hexToBigInt(call.data) : null,
  )

  // Extract post-execution ETH and asset balances.
  const ethPost = block_ethPost?.calls ?? []
  const assetsPost = block_assetsPost?.calls ?? []
  const balancesPost = [...ethPost, ...assetsPost].map((call) =>
    call.status === 'success' ? hexToBigInt(call.data) : null,
  )

  // Extract asset symbols & decimals.
  const decimals = (block_decimals?.calls ?? []).map((x) =>
    x.status === 'success' ? x.result : null,
  ) as (number | null)[]
  const symbols = (block_symbols?.calls ?? []).map((x) =>
    x.status === 'success' ? x.result : null,
  ) as (string | null)[]
  const tokenURI = (block_tokenURI?.calls ?? []).map((x) =>
    x.status === 'success' ? x.result : null,
  ) as (string | null)[]

  const changes: Mutable<SimulateCallsReturnType<calls>['assetChanges']> = []
  for (const [i, balancePost] of balancesPost.entries()) {
    const balancePre = balancesPre[i]

    if (typeof balancePost !== 'bigint') continue
    if (typeof balancePre !== 'bigint') continue

    const decimals_ = decimals[i - 1]
    const symbol_ = symbols[i - 1]
    const tokenURI_ = tokenURI[i - 1]

    const token = (() => {
      if (i === 0)
        return {
          address: ethAddress,
          decimals: 18,
          symbol: 'ETH',
        }

      return {
        address: assetAddresses[i - 1]! as Address,
        decimals: tokenURI_ || decimals_ ? Number(decimals_ ?? 1) : undefined,
        symbol: symbol_ ?? undefined,
      }
    })()

    if (changes.some((change) => change.token.address === token.address))
      continue

    changes.push({
      token,
      value: {
        pre: balancePre,
        post: balancePost,
        diff: balancePost - balancePre,
      },
    })
  }

  return {
    assetChanges: changes,
    block,
    results,
  } as unknown as SimulateCallsReturnType<calls>
}

Выполнить команду


Для локальной разработки. Не используйте в интернете!