- Consolidated duplicate UndoManagers to single instance - Fixed connection promise to only resolve on 'connected' status - Fixed WebSocketProvider import (WebsocketProvider) - Added proper doc.destroy() cleanup - Renamed isPresenceInitialized property to avoid conflict Co-Authored-By: Paperclip <noreply@paperclip.ing>
300 lines
8.7 KiB
TypeScript
300 lines
8.7 KiB
TypeScript
import type { Abi, AbiStateMutability, Address, Narrow } from 'abitype'
|
|
import * as BlockOverrides from 'ox/BlockOverrides'
|
|
|
|
import {
|
|
type ParseAccountErrorType,
|
|
parseAccount,
|
|
} from '../../accounts/utils/parseAccount.js'
|
|
import type { Client } from '../../clients/createClient.js'
|
|
import type { Transport } from '../../clients/transports/createTransport.js'
|
|
import { AbiDecodingZeroDataError } from '../../errors/abi.js'
|
|
import type { BaseError } from '../../errors/base.js'
|
|
import { RawContractError } from '../../errors/contract.js'
|
|
import { UnknownNodeError } from '../../errors/node.js'
|
|
import type { ErrorType } from '../../errors/utils.js'
|
|
import type { Account } from '../../types/account.js'
|
|
import type { Block, BlockTag } 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 { TransactionRequest } from '../../types/transaction.js'
|
|
import type { ExactPartial, UnionOmit } from '../../types/utils.js'
|
|
import {
|
|
type DecodeFunctionResultErrorType,
|
|
decodeFunctionResult,
|
|
} from '../../utils/abi/decodeFunctionResult.js'
|
|
import {
|
|
type EncodeFunctionDataErrorType,
|
|
encodeFunctionData,
|
|
} from '../../utils/abi/encodeFunctionData.js'
|
|
import { concat } from '../../utils/data/concat.js'
|
|
import {
|
|
type NumberToHexErrorType,
|
|
numberToHex,
|
|
} from '../../utils/encoding/toHex.js'
|
|
import { getContractError } from '../../utils/errors/getContractError.js'
|
|
import {
|
|
type GetNodeErrorReturnType,
|
|
getNodeError,
|
|
} from '../../utils/errors/getNodeError.js'
|
|
import {
|
|
type FormatBlockErrorType,
|
|
formatBlock,
|
|
} from '../../utils/formatters/block.js'
|
|
import { formatLog } from '../../utils/formatters/log.js'
|
|
import {
|
|
type FormatTransactionRequestErrorType,
|
|
formatTransactionRequest,
|
|
} from '../../utils/formatters/transactionRequest.js'
|
|
import {
|
|
type SerializeStateOverrideErrorType,
|
|
serializeStateOverride,
|
|
} from '../../utils/stateOverride.js'
|
|
import {
|
|
type AssertRequestErrorType,
|
|
assertRequest,
|
|
} from '../../utils/transaction/assertRequest.js'
|
|
|
|
type CallExtraProperties = ExactPartial<
|
|
UnionOmit<
|
|
TransactionRequest,
|
|
'blobs' | 'data' | 'kzg' | 'to' | 'sidecars' | 'value'
|
|
>
|
|
> & {
|
|
/** Account attached to the call (msg.sender). */
|
|
account?: Account | Address | undefined
|
|
/** Recipient. `null` if contract deployment. */
|
|
to?: Address | null | undefined
|
|
}
|
|
|
|
export type SimulateBlocksParameters<
|
|
calls extends readonly unknown[] = readonly unknown[],
|
|
> = {
|
|
/** Blocks to simulate. */
|
|
blocks: readonly {
|
|
/** Block overrides. */
|
|
blockOverrides?: BlockOverrides.BlockOverrides | undefined
|
|
/** Calls to execute. */
|
|
calls: Calls<Narrow<calls>, CallExtraProperties>
|
|
/** State overrides. */
|
|
stateOverrides?: StateOverride | undefined
|
|
}[]
|
|
/** Whether to return the full transactions. */
|
|
returnFullTransactions?: boolean | undefined
|
|
/** Whether to trace transfers. */
|
|
traceTransfers?: boolean | undefined
|
|
/** Whether to enable validation mode. */
|
|
validation?: boolean | undefined
|
|
} & (
|
|
| {
|
|
/** The balance of the account at a block number. */
|
|
blockNumber?: bigint | undefined
|
|
blockTag?: undefined
|
|
}
|
|
| {
|
|
blockNumber?: undefined
|
|
/**
|
|
* The balance of the account at a block tag.
|
|
* @default 'latest'
|
|
*/
|
|
blockTag?: BlockTag | undefined
|
|
}
|
|
)
|
|
|
|
export type SimulateBlocksReturnType<
|
|
calls extends readonly unknown[] = readonly unknown[],
|
|
> = readonly (Block & {
|
|
calls: MulticallResults<
|
|
Narrow<calls>,
|
|
true,
|
|
{
|
|
extraProperties: {
|
|
data: Hex
|
|
gasUsed: bigint
|
|
logs?: Log[] | undefined
|
|
}
|
|
error: Error
|
|
mutability: AbiStateMutability
|
|
}
|
|
>
|
|
})[]
|
|
|
|
export type SimulateBlocksErrorType =
|
|
| AssertRequestErrorType
|
|
| DecodeFunctionResultErrorType
|
|
| EncodeFunctionDataErrorType
|
|
| FormatBlockErrorType
|
|
| FormatTransactionRequestErrorType
|
|
| GetNodeErrorReturnType
|
|
| ParseAccountErrorType
|
|
| SerializeStateOverrideErrorType
|
|
| NumberToHexErrorType
|
|
| ErrorType
|
|
|
|
/**
|
|
* Simulates a set of calls on block(s) with optional block and state overrides.
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* import { createClient, http, parseEther } from 'viem'
|
|
* import { simulate } from 'viem/actions'
|
|
* import { mainnet } from 'viem/chains'
|
|
*
|
|
* const client = createClient({
|
|
* chain: mainnet,
|
|
* transport: http(),
|
|
* })
|
|
*
|
|
* const result = await simulate(client, {
|
|
* blocks: [{
|
|
* blockOverrides: {
|
|
* number: 69420n,
|
|
* },
|
|
* calls: [{
|
|
* {
|
|
* account: '0x5a0b54d5dc17e482fe8b0bdca5320161b95fb929',
|
|
* data: '0xdeadbeef',
|
|
* to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
|
|
* },
|
|
* {
|
|
* account: '0x5a0b54d5dc17e482fe8b0bdca5320161b95fb929',
|
|
* to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
|
|
* value: parseEther('1'),
|
|
* },
|
|
* }],
|
|
* stateOverrides: [{
|
|
* address: '0x5a0b54d5dc17e482fe8b0bdca5320161b95fb929',
|
|
* balance: parseEther('10'),
|
|
* }],
|
|
* }]
|
|
* })
|
|
* ```
|
|
*
|
|
* @param client - Client to use.
|
|
* @param parameters - {@link SimulateBlocksParameters}
|
|
* @returns Simulated blocks. {@link SimulateBlocksReturnType}
|
|
*/
|
|
export async function simulateBlocks<
|
|
chain extends Chain | undefined,
|
|
const calls extends readonly unknown[],
|
|
>(
|
|
client: Client<Transport, chain>,
|
|
parameters: SimulateBlocksParameters<calls>,
|
|
): Promise<SimulateBlocksReturnType<calls>> {
|
|
const {
|
|
blockNumber,
|
|
blockTag = client.experimental_blockTag ?? 'latest',
|
|
blocks,
|
|
returnFullTransactions,
|
|
traceTransfers,
|
|
validation,
|
|
} = parameters
|
|
|
|
try {
|
|
const blockStateCalls = []
|
|
for (const block of blocks) {
|
|
const blockOverrides = block.blockOverrides
|
|
? BlockOverrides.toRpc(block.blockOverrides)
|
|
: undefined
|
|
const calls = block.calls.map((call_) => {
|
|
const call = call_ as Call<unknown, CallExtraProperties>
|
|
const account = call.account ? parseAccount(call.account) : undefined
|
|
const data = call.abi ? encodeFunctionData(call) : call.data
|
|
const request = {
|
|
...call,
|
|
account,
|
|
data: call.dataSuffix
|
|
? concat([data || '0x', call.dataSuffix])
|
|
: data,
|
|
from: call.from ?? account?.address,
|
|
} as const
|
|
assertRequest(request)
|
|
return formatTransactionRequest(request)
|
|
})
|
|
const stateOverrides = block.stateOverrides
|
|
? serializeStateOverride(block.stateOverrides)
|
|
: undefined
|
|
|
|
blockStateCalls.push({
|
|
blockOverrides,
|
|
calls,
|
|
stateOverrides,
|
|
})
|
|
}
|
|
|
|
const blockNumberHex =
|
|
typeof blockNumber === 'bigint' ? numberToHex(blockNumber) : undefined
|
|
const block = blockNumberHex || blockTag
|
|
|
|
const result = await client.request({
|
|
method: 'eth_simulateV1',
|
|
params: [
|
|
{ blockStateCalls, returnFullTransactions, traceTransfers, validation },
|
|
block,
|
|
],
|
|
})
|
|
|
|
return result.map((block, i) => ({
|
|
...formatBlock(block),
|
|
calls: block.calls.map((call, j) => {
|
|
const { abi, args, functionName, to } = blocks[i].calls[j] as Call<
|
|
unknown,
|
|
CallExtraProperties
|
|
>
|
|
|
|
const data = call.error?.data ?? call.returnData
|
|
const gasUsed = BigInt(call.gasUsed)
|
|
const logs = call.logs?.map((log) => formatLog(log))
|
|
const status = call.status === '0x1' ? 'success' : 'failure'
|
|
|
|
const result =
|
|
abi && status === 'success' && data !== '0x'
|
|
? decodeFunctionResult({
|
|
abi,
|
|
data,
|
|
functionName,
|
|
})
|
|
: null
|
|
|
|
const error = (() => {
|
|
if (status === 'success') return undefined
|
|
|
|
let error: Error | undefined
|
|
if (data === '0x') error = new AbiDecodingZeroDataError()
|
|
else if (data) error = new RawContractError({ data })
|
|
|
|
if (!error) return undefined
|
|
return getContractError(error, {
|
|
abi: (abi ?? []) as Abi,
|
|
address: to ?? '0x',
|
|
args,
|
|
functionName: functionName ?? '<unknown>',
|
|
})
|
|
})()
|
|
|
|
return {
|
|
data,
|
|
gasUsed,
|
|
logs,
|
|
status,
|
|
...(status === 'success'
|
|
? {
|
|
result,
|
|
}
|
|
: {
|
|
error,
|
|
}),
|
|
}
|
|
}),
|
|
})) as unknown as SimulateBlocksReturnType<calls>
|
|
} catch (e) {
|
|
const cause = e as BaseError
|
|
const error = getNodeError(cause, {})
|
|
if (error instanceof UnknownNodeError) throw cause
|
|
throw error
|
|
}
|
|
}
|