From b47fdc2e752c60a9d0ed7ae4d9bb20b188cec5b6 Mon Sep 17 00:00:00 2001 From: Garvit Khatri Date: Mon, 3 Jun 2024 19:02:40 +0530 Subject: [PATCH 1/2] Add simulation-functions --- .../src/ep-0.6/pimlicoActions.test.ts | 27 + packages/permissionless/actions/pimlico.ts | 7 + .../pimlico/buildSimulateUserOperationCall.ts | 513 ++++++++++++++++++ .../pimlico/sendCompressedUserOperation.ts | 2 +- .../clients/decorators/pimlico.ts | 44 +- packages/permissionless/clients/pimlico.ts | 2 +- .../getEntryPointPaymasterDepositOverrides.ts | 41 ++ .../utils/getEntryPointVersion.ts | 12 + 8 files changed, 642 insertions(+), 6 deletions(-) create mode 100644 packages/permissionless/actions/pimlico/buildSimulateUserOperationCall.ts create mode 100644 packages/permissionless/utils/getEntryPointPaymasterDepositOverrides.ts diff --git a/packages/permissionless-test/src/ep-0.6/pimlicoActions.test.ts b/packages/permissionless-test/src/ep-0.6/pimlicoActions.test.ts index bda1718a..9ab27a48 100644 --- a/packages/permissionless-test/src/ep-0.6/pimlicoActions.test.ts +++ b/packages/permissionless-test/src/ep-0.6/pimlicoActions.test.ts @@ -206,4 +206,31 @@ describe("Pimlico Actions tests", () => { expect(Array.isArray(validateSponsorshipPolicies)).toBe(true) expect(validateSponsorshipPolicies.length).toBe(1) }, 100000) + + test("create simulateHandleOp callData", async () => { + const simpleAccountClient = await getSimpleAccountClient({ + entryPoint: ENTRYPOINT_ADDRESS_V06, + paymasterClient: getPimlicoPaymasterClient(ENTRYPOINT_ADDRESS_V06) + }) + + const userOperation = + await simpleAccountClient.prepareUserOperationRequest({ + userOperation: { + callData: await simpleAccountClient.account.encodeCallData({ + to: "0x5af0d9827e0c53e4799bb226655a1de152a425a5", + data: "0x", + value: 0n + }) + } + }) + + const result = pimlicoBundlerClient.buildSimulateUserOperationCall({ + userOperation + }) + + expect(result.to).toBe(ENTRYPOINT_ADDRESS_V06) + expect(result.data).toBe( + "0xd6383f940000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004400000000000000000000000009438cc89a7b4ef056f3d861fa8e1b4a1a74b1ae70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000001388000000000000000000000000000000000000000000000000000000000000783580000000000000000000000000000000000000000000000000000000000010178000000000000000000000000000000000000000000000000000000003b9aca08000000000000000000000000000000000000000000000000000000003b9aca0000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000000589406Cc6185a346906296840746125a0E449764545fbfb9cf000000000000000000000000bc8f57ea0ed1ae23ea4646ac7a132f5c5140508b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084b61d27f60000000000000000000000005af0d9827e0c53e4799bb226655a1de152a425a500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009528ec0633192d0cBd9E1156CE05D5FdACAcB9394700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000be9dc396306ab3b177c83f35e27c1c3d1a9e02d8d4c2a466fc468a856e80d1cf2b671e0ccd67885e775ac11e2f06384336650a8de2ae9cdc212e89d62cbdf6aa1c00000000000000000000000000000000000000000000000000000000000000000000000000000000000041fffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ) + }) }) diff --git a/packages/permissionless/actions/pimlico.ts b/packages/permissionless/actions/pimlico.ts index 77e500b9..70090b6d 100644 --- a/packages/permissionless/actions/pimlico.ts +++ b/packages/permissionless/actions/pimlico.ts @@ -17,6 +17,11 @@ import { sponsorUserOperation } from "./pimlico/sponsorUserOperation" +import { + type BuildSimulateUserOperationCallParams, + buildSimulateUserOperationCall +} from "./pimlico/buildSimulateUserOperationCall" + import type { PimlicoBundlerActions, PimlicoPaymasterClientActions @@ -42,6 +47,7 @@ export type { SendCompressedUserOperationParameters, SponsorUserOperationReturnType, ValidateSponsorshipPolicies, + BuildSimulateUserOperationCallParams, ValidateSponsorshipPoliciesParameters } @@ -52,5 +58,6 @@ export { pimlicoPaymasterActions, sendCompressedUserOperation, sponsorUserOperation, + buildSimulateUserOperationCall, validateSponsorshipPolicies } diff --git a/packages/permissionless/actions/pimlico/buildSimulateUserOperationCall.ts b/packages/permissionless/actions/pimlico/buildSimulateUserOperationCall.ts new file mode 100644 index 00000000..3b1ab6b1 --- /dev/null +++ b/packages/permissionless/actions/pimlico/buildSimulateUserOperationCall.ts @@ -0,0 +1,513 @@ +import { + type Address, + type BlockTag, + type CallParameters, + type Chain, + type Client, + type Hex, + type StateOverride, + type Transport, + encodeFunctionData, + getAddress, + zeroAddress +} from "viem" +import type { + ENTRYPOINT_ADDRESS_V06_TYPE, + ENTRYPOINT_ADDRESS_V07_TYPE, + EntryPoint, + GetEntryPointVersion as GetEntryPointVersionType, + Prettify, + UserOperation +} from "../../types" +import { getEntryPointPaymasterDepositOverrides } from "../../utils/getEntryPointPaymasterDepositOverrides" +import { + isEntryPointVersion06, + isEntryPointVersion07, + isUserOperationVersion06, + isUserOperationVersion07 +} from "../../utils/getEntryPointVersion" +import { getPackedUserOperation } from "../../utils/getPackedUserOperation" + +const PIMLICO_ENTRYPOINT_SIMULATIONS_ADDRESS: Hex = getAddress( + "0x74Cb5e4eE81b86e70f9045036a1C5477de69eE87" +) +export const PimlicoEntryPointSimulationsAbi = [ + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor" + }, + { + inputs: [ + { + internalType: "address payable", + name: "ep", + type: "address" + }, + { + internalType: "bytes[]", + name: "data", + type: "bytes[]" + } + ], + name: "simulateEntryPoint", + outputs: [ + { + internalType: "bytes[]", + name: "", + type: "bytes[]" + } + ], + stateMutability: "nonpayable", + type: "function" + } +] + +const EntryPointSimulateHandleOpV07Abi = [ + { + type: "function", + name: "simulateHandleOp", + inputs: [ + { + name: "op", + type: "tuple", + internalType: "struct PackedUserOperation", + components: [ + { + name: "sender", + type: "address", + internalType: "address" + }, + { name: "nonce", type: "uint256", internalType: "uint256" }, + { name: "initCode", type: "bytes", internalType: "bytes" }, + { name: "callData", type: "bytes", internalType: "bytes" }, + { + name: "accountGasLimits", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256" + }, + { + name: "gasFees", + type: "bytes32", + internalType: "bytes32" + }, + { + name: "paymasterAndData", + type: "bytes", + internalType: "bytes" + }, + { name: "signature", type: "bytes", internalType: "bytes" } + ] + } + ], + outputs: [ + { + name: "", + type: "tuple", + internalType: "struct IEntryPointSimulations.ExecutionResult", + components: [ + { + name: "preOpGas", + type: "uint256", + internalType: "uint256" + }, + { name: "paid", type: "uint256", internalType: "uint256" }, + { + name: "accountValidationData", + type: "uint256", + internalType: "uint256" + }, + { + name: "paymasterValidationData", + type: "uint256", + internalType: "uint256" + }, + { + name: "paymasterVerificationGasLimit", + type: "uint256", + internalType: "uint256" + }, + { + name: "paymasterPostOpGasLimit", + type: "uint256", + internalType: "uint256" + }, + { + name: "targetSuccess", + type: "bool", + internalType: "bool" + }, + { + name: "targetResult", + type: "bytes", + internalType: "bytes" + } + ] + } + ], + stateMutability: "nonpayable" + } +] +const EntryPointSimulateHandleOpV06Abi = [ + { + inputs: [ + { + components: [ + { + internalType: "address", + name: "sender", + type: "address" + }, + { + internalType: "uint256", + name: "nonce", + type: "uint256" + }, + { + internalType: "bytes", + name: "initCode", + type: "bytes" + }, + { + internalType: "bytes", + name: "callData", + type: "bytes" + }, + { + internalType: "uint256", + name: "callGasLimit", + type: "uint256" + }, + { + internalType: "uint256", + name: "verificationGasLimit", + type: "uint256" + }, + { + internalType: "uint256", + name: "preVerificationGas", + type: "uint256" + }, + { + internalType: "uint256", + name: "maxFeePerGas", + type: "uint256" + }, + { + internalType: "uint256", + name: "maxPriorityFeePerGas", + type: "uint256" + }, + { + internalType: "bytes", + name: "paymasterAndData", + type: "bytes" + }, + { + internalType: "bytes", + name: "signature", + type: "bytes" + } + ], + internalType: "struct UserOperation", + name: "op", + type: "tuple" + }, + { + internalType: "address", + name: "target", + type: "address" + }, + { + internalType: "bytes", + name: "targetCallData", + type: "bytes" + } + ], + name: "simulateHandleOp", + outputs: [], + stateMutability: "nonpayable", + type: "function" + } +] + +export type GetEntryPointSimulationsParams< + TEntryPoint extends EntryPoint, + TEntryPointCode extends Hex | undefined +> = TEntryPoint extends ENTRYPOINT_ADDRESS_V06_TYPE + ? { + entryPointCode?: TEntryPointCode + targetAddress?: Address + targetCallData?: Hex + } + : { + pimlicoEntryPointSimulationsAddress: Address + } + +export type BuildSimulateUserOperationCallParams< + TEntryPoint extends EntryPoint, + TEntryPointCode extends Hex | undefined +> = { + entryPoint: TEntryPoint + userOperation: UserOperation> + stateOverrides?: StateOverride + balanceOverride?: boolean + paymasterDepositOverride?: boolean +} & ( + | { + blockNumber?: bigint + blockTag?: never + } + | { + blockNumber?: never + blockTag?: BlockTag + } +) & + GetEntryPointSimulationsParams + +const buildSimulateUserOperationCallV06 = < + TEntryPointCode extends Hex | undefined, + TTransport extends Transport = Transport, + TChain extends Chain | undefined = Chain | undefined +>( + _client: Client, + args: Prettify< + BuildSimulateUserOperationCallParams< + ENTRYPOINT_ADDRESS_V06_TYPE, + TEntryPointCode + > + > +): CallParameters => { + const { + entryPoint, + userOperation, + entryPointCode, + blockNumber, + blockTag, + targetAddress, + targetCallData, + stateOverrides + } = args + + const existingEntryPointStateOverrides = stateOverrides?.find( + (stateOverride) => stateOverride.address === entryPoint + ) + + const finalStateOverrides: StateOverride | undefined = entryPointCode + ? [ + ...(stateOverrides ?? []), + { + ...existingEntryPointStateOverrides, + address: entryPoint, + code: entryPointCode + } + ] + : stateOverrides + + const params: CallParameters = blockNumber + ? { + to: entryPoint, + data: encodeFunctionData({ + abi: EntryPointSimulateHandleOpV06Abi, + functionName: "simulateHandleOp", + args: [ + userOperation, + targetAddress ?? zeroAddress, + targetCallData ?? "0x" + ] + }), + blockNumber, + stateOverrides: finalStateOverrides + } + : { + to: entryPoint, + data: encodeFunctionData({ + abi: EntryPointSimulateHandleOpV06Abi, + functionName: "simulateHandleOp", + args: [ + userOperation, + targetAddress ?? zeroAddress, + targetCallData ?? "0x" + ] + }), + stateOverrides: finalStateOverrides, + blockTag: blockTag ?? "pending" + } + + return params +} + +const buildSimulateUserOperationCallV07 = < + TEntryPointCode extends Hex | undefined, + TTransport extends Transport = Transport, + TChain extends Chain | undefined = Chain | undefined +>( + _client: Client, + args: Prettify< + BuildSimulateUserOperationCallParams< + ENTRYPOINT_ADDRESS_V07_TYPE, + TEntryPointCode + > + > +): CallParameters => { + const { + entryPoint, + userOperation, + blockNumber, + blockTag, + stateOverrides, + pimlicoEntryPointSimulationsAddress + } = args + + const packedUserOperation = getPackedUserOperation(userOperation) + + const entryPointSimulationsSimulateHandleOpCallData = encodeFunctionData({ + abi: EntryPointSimulateHandleOpV07Abi, + functionName: "simulateHandleOp", + args: [packedUserOperation] + }) + + const callData = encodeFunctionData({ + abi: PimlicoEntryPointSimulationsAbi, + functionName: "simulateEntryPoint", + args: [entryPoint, [entryPointSimulationsSimulateHandleOpCallData]] + }) + + const params: CallParameters = blockNumber + ? { + to: pimlicoEntryPointSimulationsAddress, + data: callData, + blockNumber, + stateOverrides + } + : { + to: pimlicoEntryPointSimulationsAddress, + data: callData, + stateOverrides, + blockTag: blockTag ?? "pending" + } + + return params +} + +/** + * Simulate the user operation. + * + * - Docs: https://docs.pimlico.io/permissionless/reference/public-actions/simulateUserOperation + * + * @param client {@link client} that you created using viem's createPublicClient. + * @param args {@link GetAccountNonceParams} address, entryPoint & key + * + * @example + * import { createPublicClient } from "viem" + * import { simulateUserOperation } from "permissionless/actions/public" + * + * const client = createPublicClient({ + * chain: goerli, + * transport: http("https://goerli.infura.io/v3/your-infura-key") + * }) + * + * const nonce = await simulateUserOperation(client, { + * entryPoint, + * userOperation + * }) + * + * // Return 0n + */ +export const buildSimulateUserOperationCall = < + TEntryPoint extends EntryPoint, + TEntryPointCode extends Address | undefined, + TTransport extends Transport = Transport, + TChain extends Chain | undefined = Chain | undefined +>( + client: Client, + args: BuildSimulateUserOperationCallParams +): CallParameters => { + const { + entryPoint, + userOperation, + stateOverrides, + balanceOverride, + paymasterDepositOverride + } = args + + const finalStateOverrides: StateOverride = stateOverrides ?? [] + + if (balanceOverride) { + finalStateOverrides.push({ + address: userOperation.sender, + balance: BigInt( + "0x7ffffffffffffffffffffffffffff17fffffffffffffffffffffffffffffffff" + ) + }) + } + + if (paymasterDepositOverride) { + let paymaster: Address | undefined = undefined + + if (isUserOperationVersion06(entryPoint, userOperation)) { + paymaster = userOperation.paymasterAndData + ? getAddress(userOperation.paymasterAndData.slice(0, 42)) + : undefined + } + if (isUserOperationVersion07(entryPoint, userOperation)) { + paymaster = userOperation.paymaster ?? undefined + } + + if (paymaster) { + finalStateOverrides.concat( + getEntryPointPaymasterDepositOverrides({ + entryPoint, + paymaster, + amount: BigInt( + "0x7ffffffffffffffffffffffffffff17fffffffffffffffffffffffffffffffff" + ) + }) + ) + } + } + + if (isEntryPointVersion06(entryPoint)) { + if ( + "initCode" in userOperation && + "paymasterAndData" in userOperation + ) { + return buildSimulateUserOperationCallV06(client, { + entryPoint, + userOperation: userOperation as UserOperation<"v0.6">, + entryPointCode: ( + args as BuildSimulateUserOperationCallParams< + ENTRYPOINT_ADDRESS_V06_TYPE, + undefined + > + ).entryPointCode, + stateOverrides: finalStateOverrides + }) + } + throw new Error("UserOperation is not v0.6") + } + if (isEntryPointVersion07(entryPoint)) { + if ("factory" in userOperation && "paymaster" in userOperation) { + return buildSimulateUserOperationCallV07(client, { + entryPoint, + userOperation: userOperation as UserOperation<"v0.7">, + pimlicoEntryPointSimulationsAddress: + ( + args as BuildSimulateUserOperationCallParams< + ENTRYPOINT_ADDRESS_V07_TYPE, + Hex + > + ).pimlicoEntryPointSimulationsAddress ?? + PIMLICO_ENTRYPOINT_SIMULATIONS_ADDRESS, + stateOverrides: finalStateOverrides + }) + } + throw new Error("UserOperation is not v0.7") + } + throw new Error("EntryPoint is not recognized") +} diff --git a/packages/permissionless/actions/pimlico/sendCompressedUserOperation.ts b/packages/permissionless/actions/pimlico/sendCompressedUserOperation.ts index ad64b590..ed47b16e 100644 --- a/packages/permissionless/actions/pimlico/sendCompressedUserOperation.ts +++ b/packages/permissionless/actions/pimlico/sendCompressedUserOperation.ts @@ -8,7 +8,7 @@ import type { Transport } from "viem" import type { Prettify } from "../../types/" -import { type PimlicoBundlerRpcSchema } from "../../types/pimlico" +import type { PimlicoBundlerRpcSchema } from "../../types/pimlico" export type SendCompressedUserOperationParameters = { compressedUserOperation: Hex diff --git a/packages/permissionless/clients/decorators/pimlico.ts b/packages/permissionless/clients/decorators/pimlico.ts index 69fc4904..eefd25e6 100644 --- a/packages/permissionless/clients/decorators/pimlico.ts +++ b/packages/permissionless/clients/decorators/pimlico.ts @@ -1,4 +1,4 @@ -import type { Client, Hash } from "viem" +import type { Client, Hash, Hex } from "viem" import { type SendCompressedUserOperationParameters, type ValidateSponsorshipPolicies, @@ -6,6 +6,10 @@ import { sendCompressedUserOperation, validateSponsorshipPolicies } from "../../actions/pimlico" +import { + type BuildSimulateUserOperationCallParams, + buildSimulateUserOperationCall +} from "../../actions/pimlico/buildSimulateUserOperationCall" import { type GetUserOperationGasPriceReturnType, getUserOperationGasPrice @@ -24,7 +28,7 @@ import type { Prettify } from "../../types/" import type { EntryPoint } from "../../types/entrypoint" import type { PimlicoBundlerClient, PimlicoPaymasterClient } from "../pimlico" -export type PimlicoBundlerActions = { +export type PimlicoBundlerActions = { /** * Returns the live gas prices that you can use to send a user operation. * @@ -98,11 +102,23 @@ export type PimlicoBundlerActions = { Omit > ) => Promise + + buildSimulateUserOperationCall: ( + args: Prettify< + Omit< + BuildSimulateUserOperationCallParams< + entryPoint, + TEntryPointCode + >, + "entryPoint" + > + > + ) => ReturnType } export const pimlicoBundlerActions = (entryPointAddress: entryPoint) => - (client: Client): PimlicoBundlerActions => ({ + (client: Client): PimlicoBundlerActions => ({ getUserOperationGasPrice: async () => getUserOperationGasPrice( client as PimlicoBundlerClient @@ -123,7 +139,27 @@ export const pimlicoBundlerActions = ...args, entryPoint: entryPointAddress } - ) + ), + buildSimulateUserOperationCall: < + TEntryPointCode extends Hex | undefined + >( + args: Prettify< + Omit< + BuildSimulateUserOperationCallParams< + entryPoint, + TEntryPointCode + >, + "entryPoint" + > + > + ) => + buildSimulateUserOperationCall(client, { + ...args, + entryPoint: entryPointAddress + } as BuildSimulateUserOperationCallParams< + entryPoint, + TEntryPointCode + >) }) export type PimlicoPaymasterClientActions = { diff --git a/packages/permissionless/clients/pimlico.ts b/packages/permissionless/clients/pimlico.ts index 1eda6396..caadba73 100644 --- a/packages/permissionless/clients/pimlico.ts +++ b/packages/permissionless/clients/pimlico.ts @@ -24,7 +24,7 @@ export type PimlicoBundlerClient = Client< Chain | undefined, Account | undefined, PimlicoBundlerRpcSchema, - PimlicoBundlerActions & BundlerActions + PimlicoBundlerActions & BundlerActions > export type PimlicoPaymasterClient = Client< diff --git a/packages/permissionless/utils/getEntryPointPaymasterDepositOverrides.ts b/packages/permissionless/utils/getEntryPointPaymasterDepositOverrides.ts new file mode 100644 index 00000000..e1f98d10 --- /dev/null +++ b/packages/permissionless/utils/getEntryPointPaymasterDepositOverrides.ts @@ -0,0 +1,41 @@ +import { + type Address, + type StateOverride, + encodeAbiParameters, + keccak256, + toHex +} from "viem" + +export function getEntryPointPaymasterDepositOverrides({ + entryPoint, + paymaster, + amount +}: { + entryPoint: Address + paymaster: Address + amount: bigint +}): StateOverride { + return [ + { + address: entryPoint, + stateDiff: [ + { + slot: keccak256( + encodeAbiParameters( + [ + { + type: "address" + }, + { + type: "uint256" + } + ], + [paymaster, BigInt(0)] + ) + ), + value: toHex(amount) + } + ] + } + ] +} diff --git a/packages/permissionless/utils/getEntryPointVersion.ts b/packages/permissionless/utils/getEntryPointVersion.ts index c6a1e3f0..5b762058 100644 --- a/packages/permissionless/utils/getEntryPointVersion.ts +++ b/packages/permissionless/utils/getEntryPointVersion.ts @@ -15,6 +15,18 @@ export const getEntryPointVersion = ( ): GetEntryPointVersion => entryPoint === ENTRYPOINT_ADDRESS_V06 ? "v0.6" : "v0.7" +export function isEntryPointVersion06( + entryPoint: EntryPoint +): entryPoint is ENTRYPOINT_ADDRESS_V06_TYPE { + return entryPoint === ENTRYPOINT_ADDRESS_V06 +} + +export function isEntryPointVersion07( + entryPoint: EntryPoint +): entryPoint is ENTRYPOINT_ADDRESS_V07_TYPE { + return entryPoint === ENTRYPOINT_ADDRESS_V07 +} + export function isUserOperationVersion06( entryPoint: EntryPoint, _operation: UserOperation<"v0.6"> | UserOperation<"v0.7"> From 013e896e0825740f0b7103fda434ee5504a26870 Mon Sep 17 00:00:00 2001 From: Garvit Khatri Date: Mon, 3 Jun 2024 20:34:09 +0530 Subject: [PATCH 2/2] don't hardcode --- .../permissionless-test/src/ep-0.6/pimlicoActions.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/permissionless-test/src/ep-0.6/pimlicoActions.test.ts b/packages/permissionless-test/src/ep-0.6/pimlicoActions.test.ts index 9ab27a48..c46d567a 100644 --- a/packages/permissionless-test/src/ep-0.6/pimlicoActions.test.ts +++ b/packages/permissionless-test/src/ep-0.6/pimlicoActions.test.ts @@ -229,8 +229,6 @@ describe("Pimlico Actions tests", () => { }) expect(result.to).toBe(ENTRYPOINT_ADDRESS_V06) - expect(result.data).toBe( - "0xd6383f940000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004400000000000000000000000009438cc89a7b4ef056f3d861fa8e1b4a1a74b1ae70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000001388000000000000000000000000000000000000000000000000000000000000783580000000000000000000000000000000000000000000000000000000000010178000000000000000000000000000000000000000000000000000000003b9aca08000000000000000000000000000000000000000000000000000000003b9aca0000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000000589406Cc6185a346906296840746125a0E449764545fbfb9cf000000000000000000000000bc8f57ea0ed1ae23ea4646ac7a132f5c5140508b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084b61d27f60000000000000000000000005af0d9827e0c53e4799bb226655a1de152a425a500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009528ec0633192d0cBd9E1156CE05D5FdACAcB9394700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000be9dc396306ab3b177c83f35e27c1c3d1a9e02d8d4c2a466fc468a856e80d1cf2b671e0ccd67885e775ac11e2f06384336650a8de2ae9cdc212e89d62cbdf6aa1c00000000000000000000000000000000000000000000000000000000000000000000000000000000000041fffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - ) + expect(result.data).toBeTruthy() }) })