From bb8d60deb81e9c9e89f2513f166559c68d53bd24 Mon Sep 17 00:00:00 2001
From: Joe Pegler <joepegler123@gmail.com>
Date: Tue, 31 Dec 2024 16:33:23 +0000
Subject: [PATCH] chore: smart sessions migration

---
 src/sdk/account/toNexusAccount.test.ts        |  19 ++
 src/sdk/account/utils/Utils.ts                |  41 ++++
 src/sdk/account/utils/contractSimulation.ts   |  29 +++
 src/sdk/account/utils/tenderlySimulation.ts   |  66 ++++++
 .../smartAccount/debugUserOperation.ts        | 199 +++++++-----------
 .../modules/smartSessionsValidator/Types.ts   |   2 +-
 .../toSmartSessionsValidator.dx.test.ts       |   5 -
 7 files changed, 229 insertions(+), 132 deletions(-)
 create mode 100644 src/sdk/account/utils/contractSimulation.ts
 create mode 100644 src/sdk/account/utils/tenderlySimulation.ts

diff --git a/src/sdk/account/toNexusAccount.test.ts b/src/sdk/account/toNexusAccount.test.ts
index 4e35e884c..420b6d845 100644
--- a/src/sdk/account/toNexusAccount.test.ts
+++ b/src/sdk/account/toNexusAccount.test.ts
@@ -13,6 +13,7 @@ import {
   createWalletClient,
   domainSeparator,
   encodeAbiParameters,
+  encodeFunctionData,
   encodePacked,
   getContract,
   hashMessage,
@@ -27,6 +28,7 @@ import {
 } from "viem"
 import type { UserOperation } from "viem/account-abstraction"
 import { afterAll, beforeAll, describe, expect, test } from "vitest"
+import { CounterAbi } from "../../test/__contracts/abi/CounterAbi"
 import { MockSignatureValidatorAbi } from "../../test/__contracts/abi/MockSignatureValidatorAbi"
 import { TokenWithPermitAbi } from "../../test/__contracts/abi/TokenWithPermitAbi"
 import { testAddresses } from "../../test/callDatas"
@@ -600,4 +602,21 @@ describe("nexus.account", async () => {
       expect(BICONOMY_ATTESTER_ADDRESS).toBe(biconomyAttesterAddress)
     }
   )
+
+  testnetTest(
+    "should debug user operation and generate tenderly link",
+    async ({ config: { chain } }) => {
+      await nexusClient.debugUserOperation({
+        calls: [
+          {
+            to: testAddresses.Counter,
+            data: encodeFunctionData({
+              abi: CounterAbi,
+              functionName: "incrementNumber"
+            })
+          }
+        ]
+      })
+    }
+  )
 })
diff --git a/src/sdk/account/utils/Utils.ts b/src/sdk/account/utils/Utils.ts
index 2e7e59f06..49650f761 100644
--- a/src/sdk/account/utils/Utils.ts
+++ b/src/sdk/account/utils/Utils.ts
@@ -433,3 +433,44 @@ export const getAllowance = async (
 
   return approval as bigint
 }
+
+export function parseRequestArguments(input: string[]) {
+  const fieldsToOmit = [
+    "callGasLimit",
+    "preVerificationGas",
+    "maxFeePerGas",
+    "maxPriorityFeePerGas",
+    "paymasterAndData",
+    "verificationGasLimit"
+  ]
+
+  // Skip the first element which is just "Request Arguments:"
+  const argsString = input.slice(1).join("")
+
+  // Split by newlines and filter out empty lines
+  const lines = argsString.split("\n").filter((line) => line.trim())
+
+  // Create an object from the key-value pairs
+  const result = lines.reduce(
+    (acc, line) => {
+      // Remove extra spaces and split by ':'
+      const [key, value] = line.split(":").map((s) => s.trim())
+
+      // Clean up the key (remove trailing spaces and colons)
+      const cleanKey = key.trim()
+
+      // Clean up the value (remove 'gwei' and other units)
+      const cleanValue: string | number = value.replace("gwei", "").trim()
+
+      if (fieldsToOmit.includes(cleanKey)) {
+        return acc
+      }
+
+      acc[cleanKey] = cleanValue
+      return acc
+    },
+    {} as Record<string, string | number>
+  )
+
+  return result
+}
diff --git a/src/sdk/account/utils/contractSimulation.ts b/src/sdk/account/utils/contractSimulation.ts
new file mode 100644
index 000000000..d8779fd74
--- /dev/null
+++ b/src/sdk/account/utils/contractSimulation.ts
@@ -0,0 +1,29 @@
+import { http, type Address, createPublicClient, parseEther } from "viem"
+import { ENTRY_POINT_ADDRESS, EntrypointAbi } from "../../constants"
+import { getChain } from "./getChain"
+import { getSimulationUserOp } from "./tenderlySimulation"
+import type { AnyUserOperation } from "./tenderlySimulation"
+
+export async function contractSimulation(
+  partialUserOp: AnyUserOperation,
+  chainId: number
+) {
+  const packed = getSimulationUserOp(partialUserOp)
+
+  return createPublicClient({
+    chain: getChain(chainId),
+    transport: http()
+  }).simulateContract({
+    account: partialUserOp.sender as Address,
+    address: ENTRY_POINT_ADDRESS,
+    abi: EntrypointAbi,
+    functionName: "handleOps",
+    args: [[packed], packed.sender],
+    stateOverride: [
+      {
+        address: partialUserOp.sender as Address,
+        balance: parseEther("1000")
+      }
+    ]
+  })
+}
diff --git a/src/sdk/account/utils/tenderlySimulation.ts b/src/sdk/account/utils/tenderlySimulation.ts
new file mode 100644
index 000000000..3f4790923
--- /dev/null
+++ b/src/sdk/account/utils/tenderlySimulation.ts
@@ -0,0 +1,66 @@
+import type { RpcUserOperation } from "viem"
+import {
+  type UserOperation,
+  toPackedUserOperation
+} from "viem/account-abstraction"
+import { getTenderlyDetails } from "."
+import { ENTRY_POINT_ADDRESS } from "../../constants"
+import { deepHexlify } from "./deepHexlify"
+
+export type AnyUserOperation = Partial<UserOperation<"0.7"> | RpcUserOperation>
+
+export const getSimulationUserOp = (partialUserOp: AnyUserOperation) => {
+  const simulationGasLimits = {
+    callGasLimit: 100_000_000_000n,
+    verificationGasLimit: 100_000_000_000n,
+    preVerificationGas: 1n,
+    maxFeePerGas: 100_000_000_000n,
+    maxPriorityFeePerGas: 1n,
+    paymasterVerificationGasLimit: 100_000_000_000n,
+    paymasterPostOpGasLimit: 100_000n
+  }
+
+  const mergedUserOp = deepHexlify({
+    ...simulationGasLimits,
+    ...partialUserOp
+  })
+
+  return toPackedUserOperation(mergedUserOp)
+}
+
+export function tenderlySimulation(
+  partialUserOp: AnyUserOperation,
+  chainId = 84532
+) {
+  const tenderlyDetails = getTenderlyDetails()
+
+  if (!tenderlyDetails) {
+    console.log(
+      "Tenderly details not found in environment variables. Please set TENDERLY_API_KEY, TENDERLY_ACCOUNT_SLUG, and TENDERLY_PROJECT_SLUG."
+    )
+    return null
+  }
+
+  const tenderlyUrl = new URL(
+    `https://dashboard.tenderly.co/${tenderlyDetails.accountSlug}/${tenderlyDetails.projectSlug}/simulator/new`
+  )
+
+  const packedUserOp = getSimulationUserOp(partialUserOp)
+
+  const params = new URLSearchParams({
+    contractAddress: ENTRY_POINT_ADDRESS,
+    value: "0",
+    network: chainId.toString(),
+    contractFunction: "0x765e827f", // handleOps
+    functionInputs: JSON.stringify([packedUserOp]),
+    stateOverrides: JSON.stringify([
+      {
+        contractAddress: packedUserOp.sender,
+        balance: "100000000000000000000"
+      }
+    ])
+  })
+
+  tenderlyUrl.search = params.toString()
+  return tenderlyUrl.toString()
+}
diff --git a/src/sdk/clients/decorators/smartAccount/debugUserOperation.ts b/src/sdk/clients/decorators/smartAccount/debugUserOperation.ts
index b68aaba85..eaffb92a8 100644
--- a/src/sdk/clients/decorators/smartAccount/debugUserOperation.ts
+++ b/src/sdk/clients/decorators/smartAccount/debugUserOperation.ts
@@ -17,28 +17,24 @@ import {
   toPackedUserOperation
 } from "viem/account-abstraction"
 
-import {
-  http,
-  type Assign,
-  type BaseError,
-  type Chain,
-  type Client,
-  type Hex,
-  type MaybeRequired,
-  type Narrow,
-  type OneOf,
-  type Transport,
-  createPublicClient,
-  parseEther
+import type {
+  Assign,
+  BaseError,
+  Chain,
+  Client,
+  Hex,
+  MaybeRequired,
+  Narrow,
+  OneOf,
+  Transport
 } from "viem"
 import { type Address, parseAccount } from "viem/accounts"
 import { type RequestErrorType, getAction } from "viem/utils"
 import { AccountNotFoundError } from "../../../account/utils/AccountNotFound"
-import { getTenderlyDetails } from "../../../account/utils/Utils"
+import { parseRequestArguments } from "../../../account/utils/Utils"
 import { deepHexlify } from "../../../account/utils/deepHexlify"
 import { getAAError } from "../../../account/utils/getAAError"
-import { getChain } from "../../../account/utils/getChain"
-import { ENTRY_POINT_ADDRESS, EntrypointAbi } from "../../../constants"
+import { tenderlySimulation } from "../../../account/utils/tenderlySimulation"
 export type DebugUserOperationParameters<
   account extends SmartAccount | undefined = SmartAccount | undefined,
   accountOverride extends SmartAccount | undefined = SmartAccount | undefined,
@@ -125,128 +121,79 @@ export async function debugUserOperation<
   client: Client<Transport, Chain | undefined, account>,
   parameters: DebugUserOperationParameters<account, accountOverride, calls>
 ) {
-  const tenderlyDetails = getTenderlyDetails()
-
-  const { account: account_ = client.account, entryPointAddress } = parameters
-
-  if (!account_ && !parameters.sender) throw new AccountNotFoundError()
-  const account = account_ ? parseAccount(account_) : undefined
+  const chainId = Number(client.account?.client?.chain?.id?.toString() ?? 84532)
 
-  const request = account
-    ? await getAction(
-        client,
-        prepareUserOperation,
-        "prepareUserOperation"
-      )(parameters as unknown as PrepareUserOperationParameters)
-    : parameters
-
-  // biome-ignore lint/style/noNonNullAssertion: <explanation>
-  const signature = (parameters.signature ||
-    (await account?.signUserOperation(request as UserOperation)))!
-
-  const userOpWithSignature = {
-    ...request,
-    signature
-  } as UserOperation
+  try {
+    const { account: account_ = client.account, entryPointAddress } = parameters
 
-  const packed = toPackedUserOperation(userOpWithSignature)
-  console.log(
-    "Packed userOp:\n",
-    JSON.stringify([deepHexlify(packed)], null, 2)
-  )
-  const rpcParameters = formatUserOperationRequest(userOpWithSignature)
-  console.log("Bundler userOp:", rpcParameters)
+    if (!account_ && !parameters.sender) throw new AccountNotFoundError()
+    const account = account_ ? parseAccount(account_) : undefined
 
-  const chainId = client.account?.client?.chain?.id?.toString()
+    const request = account
+      ? await getAction(
+          client,
+          prepareUserOperation,
+          "prepareUserOperation"
+        )(parameters as unknown as PrepareUserOperationParameters)
+      : parameters
 
-  if (tenderlyDetails) {
-    const tenderlyUrl = new URL(
-      `https://dashboard.tenderly.co/${tenderlyDetails.accountSlug}/${tenderlyDetails.projectSlug}/simulator/new`
-    )
+    // biome-ignore lint/style/noNonNullAssertion: <explanation>
+    const signature = (parameters.signature ||
+      (await account?.signUserOperation(request as UserOperation)))!
 
-    const formattedRpcParams = {
-      sender: rpcParameters.sender,
-      nonce: rpcParameters.nonce,
-      initCode: rpcParameters.initCode,
-      callData: rpcParameters.callData,
-      accountGasLimits: rpcParameters.callGasLimit,
-      preVerificationGas: rpcParameters.preVerificationGas,
-      gasFees: rpcParameters.maxFeePerGas,
-      maxPriorityFeePerGas: rpcParameters.maxPriorityFeePerGas,
-      paymasterAndData: rpcParameters.paymasterAndData,
-      signature: rpcParameters.signature
-    }
+    const userOpWithSignature = {
+      ...request,
+      signature
+    } as UserOperation
 
-    const params = new URLSearchParams({
-      contractAddress: ENTRY_POINT_ADDRESS,
-      value: "0",
-      network: chainId ?? "84532",
-      contractFunction: "0x765e827f",
-      rawFunctionInput: packed.callData,
-      functionInputs: JSON.stringify([formattedRpcParams]),
-      stateOverrides: JSON.stringify([
-        {
-          contractAddress: rpcParameters.sender,
-          balance: "100000000000000000000"
-        }
-      ])
-    })
-    tenderlyUrl.search = params.toString()
-  } else {
+    const packed = toPackedUserOperation(userOpWithSignature)
     console.log(
-      "Tenderly details not found in environment variables. Please set TENDERLY_API_KEY, TENDERLY_ACCOUNT_SLUG, and TENDERLY_PROJECT_SLUG."
+      "Packed userOp:\n",
+      JSON.stringify([deepHexlify(packed)], null, 2)
     )
-  }
+    const rpcParameters = formatUserOperationRequest(userOpWithSignature)
+    console.log("Bundler userOp:", rpcParameters)
 
-  try {
-    const simulation = await createPublicClient({
-      chain: client.account?.client?.chain ?? getChain(Number(chainId)),
-      transport: http()
-    }).simulateContract({
-      account: rpcParameters.sender,
-      address: ENTRY_POINT_ADDRESS,
-      abi: EntrypointAbi,
-      functionName: "handleOps",
-      args: [[packed], rpcParameters.sender],
-      stateOverride: [
-        {
-          address: rpcParameters.sender,
-          balance: parseEther("1000")
-        }
-      ]
-    })
+    const tenderlyUrl = tenderlySimulation(rpcParameters, chainId)
+    console.log({ tenderlyUrl })
 
-    console.log("Simulation:", { simulation })
-  } catch (error) {
-    console.error("Simulation failed")
-  }
-
-  try {
-    const hash = await client.request(
-      {
-        method: "eth_sendUserOperation",
-        params: [
-          rpcParameters,
-          // biome-ignore lint/style/noNonNullAssertion: <explanation>
-          (entryPointAddress ?? account?.entryPoint.address)!
-        ]
-      },
-      { retryCount: 0 }
-    )
-    console.log("User Operation Hash:", hash)
-    return hash
+    try {
+      const hash = await client.request(
+        {
+          method: "eth_sendUserOperation",
+          params: [
+            rpcParameters,
+            // biome-ignore lint/style/noNonNullAssertion: <explanation>
+            (entryPointAddress ?? account?.entryPoint.address)!
+          ]
+        },
+        { retryCount: 0 }
+      )
+      console.log("User Operation Hash:", hash)
+      return hash
+      // biome-ignore lint/suspicious/noExplicitAny: <explanation>
+    } catch (error: any) {
+      if (error?.details) {
+        const aaError = await getAAError(error?.details)
+        console.log({ aaError })
+      }
+
+      const calls = (parameters as any).calls
+      throw getUserOperationError(error as BaseError, {
+        ...(request as UserOperation),
+        ...(calls ? { calls } : {}),
+        signature
+      })
+    }
     // biome-ignore lint/suspicious/noExplicitAny: <explanation>
   } catch (error: any) {
-    if (error?.details) {
-      const aaError = await getAAError(error?.details)
-      console.log({ aaError })
+    if (error.metaMessages) {
+      try {
+        const messageJson = parseRequestArguments(error.metaMessages)
+        const tenderlyUrl = tenderlySimulation(messageJson)
+        console.log({ tenderlyUrl })
+      } catch (error) {}
     }
-
-    const calls = (parameters as any).calls
-    throw getUserOperationError(error as BaseError, {
-      ...(request as UserOperation),
-      ...(calls ? { calls } : {}),
-      signature
-    })
+    throw error
   }
 }
diff --git a/src/sdk/modules/smartSessionsValidator/Types.ts b/src/sdk/modules/smartSessionsValidator/Types.ts
index cc63d037a..39be3a7a0 100644
--- a/src/sdk/modules/smartSessionsValidator/Types.ts
+++ b/src/sdk/modules/smartSessionsValidator/Types.ts
@@ -205,7 +205,7 @@ export type Rule = {
  */
 export type RawParamRule = {
   condition: ParamCondition
-  offset: bigint
+  offset: number
   isLimited: boolean
   ref: Hex
   usage: LimitUsage
diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts
index 0a626e589..d0e02670d 100644
--- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts
+++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts
@@ -174,11 +174,6 @@ describe("modules.smartSessions.dx", async () => {
     const byteCode = await testClient.getCode({
       address: testAddresses.Counter
     })
-    console.log(
-      "testAddresses.Counter",
-      testAddresses.Counter,
-      byteCode?.length
-    )
     const userOpHash = await useSmartSessionNexusClient.usePermission({
       calls: [
         {