diff --git a/CHANGELOG.md b/CHANGELOG.md index 995d5255..b002050f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # @biconomy/sdk +## 0.0.12 + +### Patch Changes + +- Policy support + ## 0.0.11 ### Patch Changes diff --git a/README.md b/README.md index 90f7a453..b4c9f7df 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ [![Biconomy](https://img.shields.io/badge/Made_with_%F0%9F%8D%8A_by-Biconomy-ff4e17?style=flat)](https://biconomy.io) [![License MIT](https://img.shields.io/badge/License-MIT-blue?&style=flat)](./LICENSE) [![codecov](https://codecov.io/github/bcnmy/sdk/graph/badge.svg?token=DTdIR5aBDA)](https://codecov.io/github/bcnmy/sdk) - - # SDK 🚀 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/bcnmy/sdk) @@ -55,6 +53,34 @@ const { status, transactionHash } = await nexusClient.waitForTransactionReceipt( ``` +3. Testing + +To run the tests, ensure you have the following prerequisites installed: + +- Node.js v22 or higher +- [Bun](https://bun.sh/) package manager +- [Foundry](https://book.getfoundry.sh/getting-started/installation) + +Install the dependencies: + +```bash +bun install --frozen-lockfile +``` + +### Run all tests + +```bash +bun run test +``` + +### Run tests for a specific module + +```bash +bun run test -t=smartSessions +``` + +For detailed information about the testing framework, network configurations, and debugging guidelines, please refer to our [Testing Documentation](./src/test/README.md). + ## Documentation and Resources For a comprehensive understanding of our project and to contribute effectively, please refer to the following resources: diff --git a/bun.lockb b/bun.lockb index 3d7bee68..180da229 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index ce1d2590..fbc314ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@biconomy/sdk", - "version": "0.0.11", + "version": "0.0.12", "author": "Biconomy", "repository": "github:bcnmy/sdk", "main": "./dist/_cjs/index.js", @@ -37,12 +37,13 @@ "typedoc": "^0.25.9", "viem": "2.21.6", "vitest": "^1.3.1", - "yargs": "^17.7.2" + "yargs": "^17.7.2", + "@rhinestone/module-sdk": "^0.1.28" }, "peerDependencies": { "typescript": "^5", "viem": "^2.20.0", - "@rhinestone/module-sdk": "^0.1.25" + "@rhinestone/module-sdk": "^0.1.28" }, "exports": { ".": { diff --git a/src/sdk/clients/decorators/dan/Helpers.ts b/src/sdk/clients/decorators/dan/Helpers.ts deleted file mode 100644 index 2321509a..00000000 --- a/src/sdk/clients/decorators/dan/Helpers.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { - IBrowserWallet, - TypedData -} from "@silencelaboratories/walletprovider-sdk" -import { http, type Chain, type WalletClient, createWalletClient } from "viem" -import type { LocalAccount } from "viem/accounts" - -/** - * Implementation of IBrowserWallet for DAN (Distributed Account Network). - * Provides wallet functionality using viem's WalletClient. - */ -export class DanWallet implements IBrowserWallet { - walletClient: WalletClient - - /** - * Creates a new DanWallet instance. - * - * @param account - The local account to use for transactions - * @param chain - The blockchain chain configuration - */ - constructor(account: LocalAccount, chain: Chain) { - this.walletClient = createWalletClient({ - account, - chain, - transport: http() - }) - } - - /** - * Signs typed data according to EIP-712. - * - * @param _ - Unused parameter (kept for interface compatibility) - * @param request - The typed data to sign - * @returns A promise resolving to the signature - */ - async signTypedData(_: string, request: TypedData): Promise { - // @ts-ignore - return await this.walletClient.signTypedData(request) - } -} - -/** - * Converts a hexadecimal string to a Uint8Array. - * - * @param hex - The hexadecimal string to convert (must have even length) - * @returns A Uint8Array representation of the hex string - * @throws If the hex string has an odd number of characters - */ -export const hexToUint8Array = (hex: string): Uint8Array => { - if (hex.length % 2 !== 0) { - throw new Error("Hex string must have an even number of characters") - } - const array = new Uint8Array(hex.length / 2) - for (let i = 0; i < hex.length; i += 2) { - array[i / 2] = Number.parseInt(hex.substr(i, 2), 16) - } - return array -} - -/** - * Generates a random UUID string of specified length. - * - * @param length - The desired length of the UUID (default: 24) - * @returns A random string of the specified length - */ -export const uuid = (length = 24) => { - let result = "" - const characters = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" - const charactersLength = characters.length - let counter = 0 - while (counter < length) { - result += characters.charAt(Math.floor(Math.random() * charactersLength)) - counter += 1 - } - return result -} diff --git a/src/sdk/clients/decorators/dan/decorators/dan.decorators.test.ts b/src/sdk/clients/decorators/dan/decorators/dan.decorators.test.ts deleted file mode 100644 index 4308477a..00000000 --- a/src/sdk/clients/decorators/dan/decorators/dan.decorators.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { http, type Address, type Chain, type LocalAccount, isHex } from "viem" -import { verifyMessage } from "viem" -import type { UserOperation } from "viem/account-abstraction" -import { afterAll, beforeAll, describe, expect, test } from "vitest" -import { toNetwork } from "../../../../../test/testSetup" -import { - type MasterClient, - type NetworkConfig, - fundAndDeployClients, - getTestAccount, - killNetwork, - toTestClient -} from "../../../../../test/testUtils" -import { type NexusClient, createNexusClient } from "../../../createNexusClient" -import { DanWallet, hexToUint8Array, uuid } from "../Helpers" -import { danActions } from "./" - -describe("dan.decorators", async () => { - let network: NetworkConfig - let chain: Chain - let bundlerUrl: string - - // Test utils - let testClient: MasterClient - let eoaAccount: LocalAccount - let nexusAccountAddress: Address - let nexusClient: NexusClient - let userTwo: LocalAccount - let userThree: LocalAccount - - beforeAll(async () => { - network = await toNetwork() - - chain = network.chain - bundlerUrl = network.bundlerUrl - eoaAccount = getTestAccount(0) - userTwo = getTestAccount(1) - userThree = getTestAccount(2) - testClient = toTestClient(chain, getTestAccount(5)) - - nexusClient = await createNexusClient({ - signer: eoaAccount, - chain, - transport: http(), - bundlerTransport: http(bundlerUrl) - }) - - nexusAccountAddress = await nexusClient.account.getCounterFactualAddress() - await fundAndDeployClients(testClient, [nexusClient]) - }) - afterAll(async () => { - await killNetwork([network?.rpcPort, network?.bundlerPort]) - }) - - test("DanWallet should initialize correctly", () => { - const account = getTestAccount(0) - const danWallet = new DanWallet(account, chain) - expect(danWallet.walletClient).toBeDefined() - expect(danWallet.walletClient.account).toBe(account) - expect(danWallet.walletClient.chain).toBe(chain) - }) - - test("DanWallet should sign typed data", async () => { - const account = getTestAccount(0) - const danWallet = new DanWallet(account, chain) - - const typedData = { - types: { - Test: [{ name: "test", type: "string" }] - }, - primaryType: "Test", - domain: { - name: "Test Domain", - version: "1", - chainId: 1 - }, - message: { - test: "Hello World" - } - } - - const signature = await danWallet.signTypedData("", typedData) - expect(signature).toBeDefined() - expect(isHex(signature as string)).toBe(true) - }) - - test("hexToUint8Array should convert hex string correctly", () => { - const hex = "0a0b0c" - const result = hexToUint8Array(hex) - expect(result).toBeInstanceOf(Uint8Array) - expect(result.length).toBe(3) - expect(Array.from(result)).toEqual([10, 11, 12]) - }) - - test("hexToUint8Array should throw on invalid hex string", () => { - expect(() => hexToUint8Array("0a0")).toThrow( - "Hex string must have an even number of characters" - ) - }) - - test("uuid should generate string of correct length", () => { - const length = 32 - const result = uuid(length) - expect(result.length).toBe(length) - expect(typeof result).toBe("string") - }) - - test("uuid should use default length of 24", () => { - const result = uuid() - expect(result.length).toBe(24) - }) - - test("uuid should generate unique values", () => { - const uuid1 = uuid() - const uuid2 = uuid() - expect(uuid1).not.toBe(uuid2) - }) - - test("should check that signature is verified", async () => { - const danNexusClient = nexusClient.extend(danActions()) - const keyGenData = await danNexusClient.keyGen() - - // @ts-ignore - const preparedUserOperation = (await danNexusClient.prepareUserOperation({ - calls: [{ to: userTwo.address, value: 1n }] - })) as UserOperation - - const sendUserOperationParameters = await danNexusClient.sigGen({ - keyGenData, - ...preparedUserOperation - }) - - const userOpHash = await danNexusClient?.account?.getUserOpHash( - preparedUserOperation - ) - - if (!userOpHash || !sendUserOperationParameters.signature) - throw new Error("Missing userOpHash or signature") - - const valid = await verifyMessage({ - address: keyGenData.sessionPublicKey, - message: { raw: userOpHash }, - signature: sendUserOperationParameters.signature - }) - - // Verify transaction success - expect(valid).toBe(true) - }) -}) diff --git a/src/sdk/clients/decorators/dan/decorators/index.ts b/src/sdk/clients/decorators/dan/decorators/index.ts deleted file mode 100644 index 5117e96d..00000000 --- a/src/sdk/clients/decorators/dan/decorators/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { Chain, Client, Transport } from "viem" -import type { UserOperation } from "viem/account-abstraction" -import type { ModularSmartAccount } from "../../../../modules/utils/Types" -import { type KeyGenData, type KeyGenParameters, keyGen } from "./keyGen" -import { type SigGenParameters, sigGen } from "./sigGen" - -/** - * Defines the available DAN (Distributed Account Network) actions for a modular smart account. - * Provides methods for key generation, signature generation, and transaction sending. - * - * @template TModularSmartAccount - The type of modular smart account being used - */ -export type DanActions< - TModularSmartAccount extends ModularSmartAccount | undefined -> = { - /** Generates keys for the smart account with optional parameters */ - keyGen: (args?: KeyGenParameters) => Promise - /** Generates signatures for user operations */ - /** Generates signatures for user operations */ - sigGen: (parameters: SigGenParameters) => Promise> -} - -/** - * Creates a set of DAN-specific actions for interacting with a modular smart account. - * This function is a decorator that adds DAN functionality to a viem Client instance. - * - * @returns A function that takes a client and returns DAN-specific actions - * - * @example - * const client = createClient(...) - * const danClient = client.extend(danActions()) - */ -export function danActions() { - return < - TModularSmartAccount extends ModularSmartAccount | undefined, - chain extends Chain | undefined - >( - client: Client - ): DanActions => ({ - keyGen: (args) => keyGen(client, args), - sigGen: (parameters) => sigGen(client, parameters) - }) -} diff --git a/src/sdk/clients/decorators/dan/decorators/keyGen.ts b/src/sdk/clients/decorators/dan/decorators/keyGen.ts deleted file mode 100644 index 2d4ed4ee..00000000 --- a/src/sdk/clients/decorators/dan/decorators/keyGen.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { - EOAAuth, - NetworkSigner, - WalletProviderServiceClient, - computeAddress, - generateEphPrivateKey, - getEphPublicKey -} from "@silencelaboratories/walletprovider-sdk" -import { type Chain, type Client, type Hex, type Transport, toHex } from "viem" -import { ERROR_MESSAGES, type Signer } from "../../../../account" -import type { ModularSmartAccount } from "../../../../modules/utils/Types" -import { DanWallet, uuid } from "../Helpers" - -/** - * Constants for DAN (Distributed Account Network) configuration - */ -export const EPHEMERAL_KEY_TTL = 60 * 60 * 24 // 1 day -export const QUORUM_PARTIES = 3 -export const QUORUM_THRESHOLD = 2 -export const DEFAULT_DAN_URL = "wss://dan.staging.biconomy.io/v1" - -/** - * Response data from the key generation process - */ -export type KeyGenData = { - /** The generated public key */ - publicKey: Hex - /** Unique identifier for the generated key */ - keyId: Hex - /** EOA address derived from the session key */ - sessionPublicKey: Hex - /** Secret key of the ephemeral key pair */ - ephSK: Hex - /** Unique identifier for the ephemeral key */ - ephId: Hex -} - -/** - * Parameters for key generation - */ -export type KeyGenParameters< - TModularSmartAccount extends ModularSmartAccount | undefined -> = { - /** The smart account to add the owner to. If not provided, the client's account will be used */ - account?: TModularSmartAccount - /** Optional Signer, defaults to the one in the client */ - signer?: Signer - /** Secret key of the ephemeral key pair */ - ephSK: Hex -} & DanParameters - -/** - * Configuration parameters for DAN network - */ -export type DanParameters = { - /** Chain configuration */ - chain?: Chain - /** Minimum number of parties required for signing (default: 2) */ - threshold?: number - /** Total number of parties in the signing group (default: 3) */ - partiesNumber?: number - /** Duration of the ephemeral key validity in seconds (default: 24 hours) */ - duration?: number - /** URL of the wallet provider service */ - walletProviderUrl?: string - /** Unique identifier for the ephemeral key */ - ephId?: string -} - -/** - * Generates a key using the Distributed Account Network (DAN). - * - * @typeParam TModularSmartAccount - The type of the modular smart account, or undefined. - * @param client - The viem client instance. - * @param parameters - Optional parameters for key generation. - * @returns A Promise that resolves to the key generation data. - */ -export async function keyGen< - TModularSmartAccount extends ModularSmartAccount | undefined ->( - client: Client, - parameters?: KeyGenParameters -): Promise { - const { - signer: signer_ = client?.account?.client?.account as Signer, - walletProviderUrl = DEFAULT_DAN_URL, - partiesNumber = QUORUM_PARTIES, - threshold = QUORUM_THRESHOLD, - duration = EPHEMERAL_KEY_TTL, - ephId = uuid(), - chain: chain_ = client.account?.client?.chain - } = parameters ?? {} - - if (!signer_) { - throw new Error(ERROR_MESSAGES.SIGNER_REQUIRED) - } - - if (!chain_) { - throw new Error(ERROR_MESSAGES.CHAIN_NOT_FOUND) - } - - const skArr = generateEphPrivateKey() - const ephPKArr = getEphPublicKey(skArr) - const ephSK = toHex(skArr) - - const wpClient = new WalletProviderServiceClient({ - walletProviderId: "WalletProvider", - walletProviderUrl - }) - - const wallet = new DanWallet(signer_, chain_) - - const eoaAuth = new EOAAuth( - ephId, - signer_.address, - wallet, - ephPKArr, - duration - ) - - const networkSigner = new NetworkSigner( - wpClient, - threshold, - partiesNumber, - eoaAuth - ) - - const createdKey = await networkSigner.generateKey() - - const sessionPublicKey = computeAddress(createdKey.publicKey) - - return { - ...createdKey, - sessionPublicKey, - ephSK, - ephId - } as KeyGenData -} diff --git a/src/sdk/clients/decorators/dan/decorators/sigGen.ts b/src/sdk/clients/decorators/dan/decorators/sigGen.ts deleted file mode 100644 index 363348d2..00000000 --- a/src/sdk/clients/decorators/dan/decorators/sigGen.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { - EphAuth, - NetworkSigner, - WalletProviderServiceClient -} from "@silencelaboratories/walletprovider-sdk" -import type { Chain, Client, Hex, PartialBy, Transport } from "viem" -import { - type PrepareUserOperationParameters, - type UserOperation, - prepareUserOperation -} from "viem/account-abstraction" -import { getAction } from "viem/utils" -import { ERROR_MESSAGES, type Signer } from "../../../../account" -import { deepHexlify } from "../../../../account/utils/deepHexlify" -import type { - AnyData, - ModularSmartAccount -} from "../../../../modules/utils/Types" -import { hexToUint8Array } from "../Helpers" -import { - DEFAULT_DAN_URL, - type DanParameters, - type KeyGenData, - QUORUM_PARTIES, - QUORUM_THRESHOLD -} from "./keyGen" - -/** - * Parameters required for signature generation - */ -export type SigGenParameters = PartialBy< - PrepareUserOperationParameters & { - /** Optional Signer, defaults to the one in the client */ - signer?: Signer - /** Data from previous key generation step */ - keyGenData: KeyGenData - } & DanParameters, - "account" -> - -export const REQUIRED_FIELDS = [ - "sender", - "nonce", - "callData", - "callGasLimit", - "verificationGasLimit", - "preVerificationGas", - "maxFeePerGas", - "maxPriorityFeePerGas", - "factoryData" -] - -export const sigGen = async < - TModularSmartAccount extends ModularSmartAccount | undefined, - chain extends Chain | undefined ->( - client: Client, - parameters: SigGenParameters -): Promise> => { - const { - walletProviderUrl = DEFAULT_DAN_URL, - partiesNumber = QUORUM_PARTIES, - threshold = QUORUM_THRESHOLD, - chain: chain_ = client.account?.client?.chain, - keyGenData: { ephSK, ephId, keyId } - } = parameters ?? {} - - if (!chain_) { - throw new Error(ERROR_MESSAGES.CHAIN_NOT_FOUND) - } - - const ephSKArr = hexToUint8Array(ephSK.slice(2)) - - const wpClient = new WalletProviderServiceClient({ - walletProviderId: "WalletProvider", - walletProviderUrl - }) - const authModule = new EphAuth(ephId, ephSKArr) - - const networkSigner = new NetworkSigner( - wpClient, - threshold, - partiesNumber, - authModule - ) - - const preparedUserOperation = await getAction( - client, - prepareUserOperation, - "prepareUserOperation" - )(parameters as PrepareUserOperationParameters) - - const userOperation = REQUIRED_FIELDS.reduce((acc, field) => { - if (field in preparedUserOperation) { - // @ts-ignore - acc[field] = preparedUserOperation[field] - } - return acc - }, {} as AnyData) - - const userOperationHexed = deepHexlify(userOperation) - - const signMessage = JSON.stringify({ - message: JSON.stringify({ - userOperation: userOperationHexed, - entryPointVersion: "v0.7.0", - entryPointAddress: "0x0000000071727De22E5E9d8BAf0edAc6f37da032", - chainId: chain_.id - }), - requestType: "accountAbstractionTx" - }) - - const { sign, recid } = await networkSigner.signMessage(keyId, signMessage) - - const recid_hex = (27 + recid).toString(16) - const signature = `0x${sign}${recid_hex}` as Hex - return { ...userOperationHexed, signature } -} diff --git a/src/sdk/clients/decorators/dan/index.ts b/src/sdk/clients/decorators/dan/index.ts deleted file mode 100644 index f0656306..00000000 --- a/src/sdk/clients/decorators/dan/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./decorators" -export * from "./Helpers" diff --git a/src/sdk/clients/decorators/index.ts b/src/sdk/clients/decorators/index.ts index 4aff93b4..a651fc25 100644 --- a/src/sdk/clients/decorators/index.ts +++ b/src/sdk/clients/decorators/index.ts @@ -1,4 +1,3 @@ export * from "./erc7579" export * from "./smartAccount" export * from "./bundler" -export * from "./dan" diff --git a/src/sdk/constants/index.ts b/src/sdk/constants/index.ts index ba22292f..d0c47ee5 100644 --- a/src/sdk/constants/index.ts +++ b/src/sdk/constants/index.ts @@ -1,14 +1,17 @@ -import type { Hex } from "viem" +import { + getSpendingLimitsPolicy, + getTimeFramePolicy, + getUniversalActionPolicy, + getUsageLimitPolicy, + getValueLimitPolicy +} from "@rhinestone/module-sdk" +import { type Hex, toBytes, toHex } from "viem" import { isTesting } from "../account" +import { ParamCondition } from "../modules/smartSessionsValidator/Types" export * from "./abi" -export { - SMART_SESSIONS_ADDRESS, - OWNABLE_VALIDATOR_ADDRESS, - OWNABLE_EXECUTOR_ADDRESS, - MOCK_ATTESTER_ADDRESS, - REGISTRY_ADDRESS -} from "@rhinestone/module-sdk" + +// export const SIMPLE_SESSION_VALIDATOR_ADDRESS: Hex = OWNABLE_VALIDATOR_ADDRESS export const SIMPLE_SESSION_VALIDATOR_ADDRESS: Hex = "0x41f143f4B5f19AfCd2602F6ADE18E75e9b5E37d3" export const ENTRY_POINT_ADDRESS: Hex = @@ -19,8 +22,6 @@ export const TIMEFRAME_POLICY_ADDRESS: Hex = "0x0B7BB9bD65858593D97f12001FaDa94828307805" export const NEXUS_BOOTSTRAP_ADDRESS: Hex = "0x00000008c901d8871b6F6942De0B5D9cCf3873d3" -export const UNIVERSAL_ACTION_POLICY_ADDRESS: Hex = - "0x148CD6c24F4dd23C396E081bBc1aB1D92eeDe2BF" export const TEST_ADDRESS_NEXUS_IMPLEMENTATION_ADDRESS: Hex = "0x3AdEa1898eb7d9FbD49242618782717A1f86DA14" @@ -45,3 +46,48 @@ export const k1ValidatorFactoryAddress: Hex = isTesting() export const k1ValidatorAddress: Hex = isTesting() ? TEST_ADDRESS_K1_VALIDATOR_ADDRESS : MAINNET_ADDRESS_K1_VALIDATOR_ADDRESS + +// Rhinestone constants +export { + SMART_SESSIONS_ADDRESS, + OWNABLE_VALIDATOR_ADDRESS, + OWNABLE_EXECUTOR_ADDRESS, + MOCK_ATTESTER_ADDRESS, + RHINESTONE_ATTESTER_ADDRESS, + REGISTRY_ADDRESS +} from "@rhinestone/module-sdk" + +// Rhinestone doesn't export the universal action policy address, so we need to get it from the policies +export const UNIVERSAL_ACTION_POLICY_ADDRESS: Hex = getUniversalActionPolicy({ + valueLimitPerUse: 0n, + paramRules: { + length: 16, + rules: new Array(16).fill({ + condition: ParamCondition.EQUAL, + isLimited: false, + offset: 0, + ref: toHex(toBytes("0x", { size: 32 })), + usage: { limit: BigInt(0), used: BigInt(0) } + }) + } +}).address + +export const TIME_FRAME_POLICY_ADDRESS: Hex = getTimeFramePolicy({ + validUntil: 0, + validAfter: 0 +}).address + +export const VALUE_LIMIT_POLICY_ADDRESS: Hex = getValueLimitPolicy({ + limit: 0n +}).address + +export const USAGE_LIMIT_POLICY_ADDRESS: Hex = getUsageLimitPolicy({ + limit: 0n +}).address + +export const SPENDING_LIMITS_POLICY_ADDRESS: Hex = getSpendingLimitsPolicy([ + { + token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + limit: 0n + } +]).address diff --git a/src/sdk/modules/ownableValidator/toOwnableValidator.dan.test.ts b/src/sdk/modules/ownableValidator/toOwnableValidator.dan.test.ts deleted file mode 100644 index d76fbe91..00000000 --- a/src/sdk/modules/ownableValidator/toOwnableValidator.dan.test.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { http, type Chain, type LocalAccount } from "viem" -import type { BundlerClient } from "viem/account-abstraction" -import { afterAll, beforeAll, describe, expect, test } from "vitest" -import { toNetwork } from "../../../test/testSetup" -import { - fundAndDeployClients, - getTestAccount, - killNetwork, - toTestClient -} from "../../../test/testUtils" -import type { MasterClient, NetworkConfig } from "../../../test/testUtils" -import { createNexusClient } from "../../clients/createNexusClient" -import { danActions } from "../../clients/decorators/dan/decorators" -import { keyGen } from "../../clients/decorators/dan/decorators/keyGen" -import { ownableActions } from "./decorators" -import { toOwnableValidator } from "./toOwnableValidator" - -describe("modules.dan.dx", async () => { - let network: NetworkConfig - let chain: Chain - let bundlerUrl: string - - // Test utils - let testClient: MasterClient - let eoaAccount: LocalAccount - - const recipient = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" // vitalik.eth - - beforeAll(async () => { - network = await toNetwork() - - chain = network.chain - bundlerUrl = network.bundlerUrl - eoaAccount = getTestAccount(0) - - testClient = toTestClient(chain, getTestAccount(5)) - }) - - afterAll(async () => { - await killNetwork([network?.rpcPort, network?.bundlerPort]) - }) - - test("should demonstrate ownables module dx using a dan account", async () => { - const nexusClient = await createNexusClient({ - signer: eoaAccount, - chain, - transport: http(), - bundlerTransport: http(bundlerUrl) - }) - - const danNexusClient = nexusClient.extend(danActions()) - - const keyGenData = await danNexusClient.keyGen() - - // Fund the account and deploy the smart contract wallet - // This is just a reminder to fund the account and deploy the smart contract wallet - await fundAndDeployClients(testClient, [nexusClient]) - - // Create an ownables module with the following configuration: - // - Threshold: 1 (requires 1 signature for approval) - // - Owners: danAccount - const ownableModule = toOwnableValidator({ - account: nexusClient.account, - signer: eoaAccount, - moduleInitArgs: { - threshold: 1n, - owners: [keyGenData.sessionPublicKey] - } - }) - - // Install the ownables module on the Nexus client's smart contract account - const hash = await nexusClient.installModule({ - module: ownableModule.moduleInitData - }) - - // Extend the Nexus client with ownable-specific actions - // This allows the client to use the new module's functionality - const ownableDanClient = nexusClient - .extend(ownableActions(ownableModule)) - .extend(danActions()) - - // Wait for the module installation transaction to be mined and check its success - await ownableDanClient.waitForUserOperationReceipt({ hash }) - - // Prepare a user operation to withdraw 1 wei to userTwo - // This demonstrates a simple transaction that requires multi-sig approval - // @ts-ignore - const withdrawalUserOp = await ownableDanClient.prepareUserOperation({ - calls: [ - { - to: recipient, // vitalik.eth - value: 1n - } - ] - }) - - // Collect signature - const { signature } = await ownableDanClient.sigGen({ - keyGenData, - ...withdrawalUserOp - }) - - if (!signature) throw new Error("Missing signature") - - // Send the user operation with the collected signatures - const userOpHash = await nexusClient.sendUserOperation({ - ...withdrawalUserOp, - signature - }) - - // Wait for the user operation to be mined and check its success - const { success: userOpSuccess } = - await ownableDanClient.waitForUserOperationReceipt({ hash: userOpHash }) - - // Verify that the multi-sig transaction was successful - expect(userOpSuccess).toBe(true) - }) -}) diff --git a/src/sdk/modules/smartSessionsValidator/Helpers.ts b/src/sdk/modules/smartSessionsValidator/Helpers.ts index 52f9e756..0383260a 100644 --- a/src/sdk/modules/smartSessionsValidator/Helpers.ts +++ b/src/sdk/modules/smartSessionsValidator/Helpers.ts @@ -1,5 +1,6 @@ import type { ActionData, PolicyData, Session } from "@rhinestone/module-sdk" import { + type Abi, type AbiFunction, type Address, type Hex, @@ -8,6 +9,7 @@ import { encodePacked, pad, toBytes, + toFunctionSelector, toHex } from "viem" import { @@ -23,11 +25,12 @@ import { parseReferenceValue } from "../utils/Helpers" import type { AnyData } from "../utils/Types" import type { ActionConfig, + ActionPolicyInfo, CreateSessionDataParams, FullCreateSessionDataParams, RawActionConfig, - Rule, - SpendingLimitsParams + ResolvedActionPolicyInfo, + Rule } from "./Types" export const MAX_RULES = 16 @@ -180,13 +183,14 @@ export const isPermissionEnabled = async ({ client: PublicClient accountAddress: Address permissionId: Hex -}) => - client.readContract({ +}) => { + return client.readContract({ address: SMART_SESSIONS_ADDRESS, abi: SmartSessionAbi, functionName: "isPermissionEnabled", args: [permissionId, accountAddress] }) +} /** * Converts an ActionConfig to a UniversalActionPolicy. @@ -234,43 +238,6 @@ export const toTimeRangePolicy = ( return timeFramePolicyData } -/** - * A PolicyData object representing a sudo policy. - */ -export const sudoPolicy: PolicyData = { - policy: "0x529Ad04F4D83aAb25144a90267D4a1443B84f5A6", - initData: "0x" -} - -/** - * Converts SpendingLimitsParams to a SpendingLimitsPolicy. - * - * @param params - An array of SpendingLimitsParams. - * @returns A PolicyData object representing the SpendingLimitsPolicy. - */ -export const toSpendingLimitsPolicy = ( - params: SpendingLimitsParams -): PolicyData => { - return { - policy: "0x8e58f4945e6ba2a11b184a9c20b6c765a0891b95", - initData: encodeAbiParameters( - [{ type: "address[]" }, { type: "uint256[]" }], - [params.map(({ token }) => token), params.map(({ limit }) => limit)] - ) - } -} - -/** - * An object containing policy conversion functions. - */ -export const policies = { - to: { - universalAction: toUniversalActionPolicy, - spendingLimits: toSpendingLimitsPolicy - }, - sudo: sudoPolicy -} as const - /** * Stringifies an object, explicitly tagging BigInt values. * @@ -339,4 +306,40 @@ export const getTrustedAttesters = async ({ } } -export default policies +/** + * Converts an ABI to a list of ActionPolicyInfo objects. + * + * @param params - The parameters object + * @param params.abi - The ABI to convert + * @param params.actionPolicyInfo - The ActionPolicyInfo object to apply to each function in the ABI + * + * @example + * const actionPoliciesInfo = abiToPoliciesInfo({ + * abi: CounterAbi, + * actionPolicyInfo: { + * contractAddress: testAddresses.Counter, + * sudo: false, + * tokenLimits: [], + * usageLimit: 1000n, + * valueLimit: 1000n + * } + * }) + * @returns An array of ActionPolicyInfo objects + */ + +export type AbiToPoliciesInfoParams = Omit< + ActionPolicyInfo, + "functionSelector" | "rules" +> & { abi: Abi } + +export const abiToPoliciesInfo = ({ + abi, + ...actionPolicyInfo +}: AbiToPoliciesInfoParams): ResolvedActionPolicyInfo[] => + (abi ?? []) + .filter((item): item is AbiFunction => item.type === "function") + .map((func) => ({ + ...actionPolicyInfo, + functionSelector: toFunctionSelector(func), + rules: [] // Rules should not be available here because they should be custom per method, not used in a loop + })) diff --git a/src/sdk/modules/smartSessionsValidator/Types.ts b/src/sdk/modules/smartSessionsValidator/Types.ts index c650ccf0..2d8a93b3 100644 --- a/src/sdk/modules/smartSessionsValidator/Types.ts +++ b/src/sdk/modules/smartSessionsValidator/Types.ts @@ -2,8 +2,7 @@ import type { EnableSessionData, SmartSessionMode } from "@rhinestone/module-sdk" -import type { AbiFunction, Address, Hex, OneOf } from "viem" -import type { KeyGenData } from "../../clients/decorators/dan/decorators/keyGen" +import type { Abi, AbiFunction, Address, Hex, OneOf } from "viem" import type { AnyReferenceValue } from "../utils/Helpers" import type { Execution } from "../utils/Types" @@ -60,8 +59,6 @@ export type UsePermissionModuleData = { mode?: SmartSessionModeType /** Data for enabling the session. */ enableSessionData?: EnableSessionData - /** Key generation data for the session. */ - keyGenData?: KeyGenData /** The index of the permission ID to use for the session. Defaults to 0. */ permissionIdIndex?: number } @@ -93,10 +90,10 @@ export type CreateSessionDataParams = OptionalSessionKeyData & { sessionValidUntil?: number /** Timestamp after which the session becomes valid. */ sessionValidAfter?: number - /** Array of action policy data for the session. */ - actionPoliciesInfo: ActionPolicyData[] /** Chain IDs where the session should be enabled. Useful for enable mode. */ chainIds?: bigint[] + /** Array of action policy data for the session. */ + actionPoliciesInfo: ActionPolicyInfo[] } export type FullCreateSessionDataParams = { @@ -114,28 +111,63 @@ export type FullCreateSessionDataParams = { sessionValidUntil: number /** Timestamp after which the session becomes valid. */ sessionValidAfter: number - /** Array of action policy data for the session. */ - actionPoliciesInfo: ActionPolicyData[] /** Chain IDs where the session should be enabled. Useful for enable mode. */ chainIds?: bigint[] + /** Array of action policy data for the session. */ + actionPoliciesInfo: ActionPolicyInfo[] +} + +export type SpendingLimitPolicyData = { + /** The address of the token to be included in the policy */ + token: Address + /** The limit for the token */ + limit: bigint +} + +export type SudoPolicyData = { + /** The address of the contract to be included in the policy */ + contractAddress: Hex + /** The specific function selector from the contract to be included in the policy */ + functionSelector: string | AbiFunction } /** * Represents the data structure for an action policy. + * + * Get the universal action policy to use when creating a new session. + * The universal action policy can be used to ensure that only actions where the calldata has certain parameters can be used. + * For example, it could restrict swaps on Uniswap to be only under X amount of input token. */ -export type ActionPolicyData = { +export type ActionPolicyInfo = { /** The address of the contract to be included in the policy */ contractAddress: Hex - /** The specific function selector from the contract to be included in the policy */ - functionSelector: string | AbiFunction - /** Timestamp until which the policy is valid */ + /** The timeframe policy can be used to restrict a session to only be able to be used within a certain timeframe */ validUntil?: number /** Timestamp after which the policy becomes valid */ validAfter?: number - /** Array of rules for the policy */ - rules?: Rule[] - /** The maximum value that can be transferred in a single transaction */ + /** The value limit policy can be used to enforce that only a certain amount of native value can be spent. For ERC-20 limits, use the spending limit policy */ valueLimit?: bigint + /** The spending limits policy can be used to ensure that only a certain amount of ERC-20 tokens can be spent. For native value spends, use the value limit policy */ + tokenLimits?: SpendingLimitPolicyData[] + /** The value limit policy can be used to enforce that only a certain amount of native value can be spent. For ERC-20 limits, use the spending limit policy. */ + usageLimit?: bigint + /** The sudo policy is an action policy that will allow any action for the specified target and selector. */ + sudo?: boolean +} & OneOf< + | { + /** The specific function selector from the contract to be included in the policy */ + functionSelector: string | AbiFunction + /** Array of rules for the policy */ + rules?: Rule[] + } + | { + /** The ABI of the contract to be included in the policy */ + abi: Abi + } +> + +export type ResolvedActionPolicyInfo = ActionPolicyInfo & { + functionSelector: string | AbiFunction } /** diff --git a/src/sdk/modules/smartSessionsValidator/decorators/grantPermission.ts b/src/sdk/modules/smartSessionsValidator/decorators/grantPermission.ts index 21b26de5..4191e731 100644 --- a/src/sdk/modules/smartSessionsValidator/decorators/grantPermission.ts +++ b/src/sdk/modules/smartSessionsValidator/decorators/grantPermission.ts @@ -1,23 +1,26 @@ import { type ActionData, + MOCK_ATTESTER_ADDRESS, type PolicyData, + SMART_SESSIONS_ADDRESS, type Session, findTrustedAttesters, - getTrustAttestersAction + getSpendingLimitsPolicy, + getSudoPolicy, + getTrustAttestersAction, + getUsageLimitPolicy, + getValueLimitPolicy } from "@rhinestone/module-sdk" import type { Chain, Client, Hex, PublicClient, Transport } from "viem" import { sendUserOperation } from "viem/account-abstraction" import { encodeFunctionData, getAction, parseAccount } from "viem/utils" -import { ERROR_MESSAGES, Logger } from "../../../account" +import { type Call, ERROR_MESSAGES } from "../../../account" import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" -import { MOCK_ATTESTER_ADDRESS } from "../../../constants" -import { - SIMPLE_SESSION_VALIDATOR_ADDRESS, - SMART_SESSIONS_ADDRESS -} from "../../../constants" +import { SIMPLE_SESSION_VALIDATOR_ADDRESS } from "../../../constants" import { SmartSessionAbi } from "../../../constants/abi/SmartSessionAbi" import type { ModularSmartAccount } from "../../utils/Types" import { + abiToPoliciesInfo, applyDefaults, createActionConfig, createActionData, @@ -28,7 +31,8 @@ import { } from "../Helpers" import type { CreateSessionDataParams, - FullCreateSessionDataParams + FullCreateSessionDataParams, + ResolvedActionPolicyInfo } from "../Types" import type { GrantPermissionActionReturnParams, @@ -78,34 +82,83 @@ export const getPermissionAction = async ({ const sessions: Session[] = [] const permissionIds: Hex[] = [] - // Start populating the session for each param provided - for (const sessionInfo of sessionRequestedInfo) { - const actionPolicies: ActionData[] = [] - for (const actionPolicyInfo of sessionInfo.actionPoliciesInfo) { - // TODO: make it easy to generate rules for particular contract and selectors. - const actionConfig = createActionConfig( - actionPolicyInfo.rules ?? [], - actionPolicyInfo.valueLimit - ) + const resolvedPolicyInfo2ActionData = ( + actionPolicyInfo: ResolvedActionPolicyInfo + ): ActionData => { + const actionConfig = createActionConfig( + actionPolicyInfo.rules ?? [], + actionPolicyInfo.valueLimit + ) - // one may also pass baked up policyData. + const policyData: PolicyData[] = [] - // create uni action policy here.. - const uniActionPolicyData = toUniversalActionPolicy(actionConfig) - // create time range policy here.. - const timeFramePolicyData: PolicyData = toTimeRangePolicy( - actionPolicyInfo.validUntil ?? 0, - actionPolicyInfo.validAfter ?? 0 - ) + // create uni action policy here.. + const uniActionPolicyInfo = toUniversalActionPolicy(actionConfig) + policyData.push(uniActionPolicyInfo) + + // create time frame policy here.. + const timeFramePolicyData: PolicyData = toTimeRangePolicy( + actionPolicyInfo.validUntil ?? 0, + actionPolicyInfo.validAfter ?? 0 + ) + policyData.push(timeFramePolicyData) - // Create ActionData - const actionPolicy = createActionData( - actionPolicyInfo.contractAddress, - actionPolicyInfo.functionSelector, - [uniActionPolicyData, timeFramePolicyData] + // create sudo policy here.. + if (actionPolicyInfo.sudo) { + const sudoPolicy = getSudoPolicy() + policyData.push(sudoPolicy) + } + + // create value limit policy here.. + if (actionPolicyInfo.valueLimit) { + const valueLimitPolicy = getValueLimitPolicy({ + limit: actionPolicyInfo.valueLimit + }) + policyData.push(valueLimitPolicy) + } + + // create usage limit policy here.. + if (actionPolicyInfo.usageLimit) { + const usageLimitPolicy = getUsageLimitPolicy({ + limit: actionPolicyInfo.usageLimit + }) + policyData.push(usageLimitPolicy) + } + + // create token spending limit policy here.. + if (actionPolicyInfo.tokenLimits?.length) { + const spendingLimitPolicy = getSpendingLimitsPolicy( + actionPolicyInfo.tokenLimits ) + policyData.push(spendingLimitPolicy) + } - actionPolicies.push(actionPolicy) + // Create ActionData + const actionPolicy = createActionData( + actionPolicyInfo.contractAddress, + actionPolicyInfo.functionSelector, + policyData + ) + + return actionPolicy + } + + // Start populating the session for each param provided + for (const sessionInfo of sessionRequestedInfo) { + const actionPolicies: ActionData[] = [] + + for (const actionPolicyInfo of sessionInfo.actionPoliciesInfo ?? []) { + if (actionPolicyInfo.abi) { + // Resolve the abi to multiple function selectors... + const resolvedPolicyInfos = abiToPoliciesInfo(actionPolicyInfo) + const actionPolicies_ = resolvedPolicyInfos.map( + resolvedPolicyInfo2ActionData + ) + actionPolicies.push(...actionPolicies_) + } else { + const actionPolicy = resolvedPolicyInfo2ActionData(actionPolicyInfo) + actionPolicies.push(actionPolicy) + } } const userOpTimeFramePolicyData: PolicyData = toTimeRangePolicy( @@ -127,10 +180,10 @@ export const getPermissionAction = async ({ } } - const permissionId = (await getPermissionId({ - client: client, - session: session - })) as Hex + const permissionId = await getPermissionId({ + client, + session + }) // push permissionId to the array permissionIds.push(permissionId) @@ -221,6 +274,9 @@ export async function grantPermission< } const account = parseAccount(account_) as ModularSmartAccount + if (!account || !account.address) { + throw new Error("Account not found") + } const chainId = publicClient_?.chain?.id @@ -228,7 +284,8 @@ export async function grantPermission< throw new Error(ERROR_MESSAGES.CHAIN_NOT_FOUND) } - const defaultedSessionRequestedInfo = sessionRequestedInfo.map(applyDefaults) + const defaultedSessionRequestedInfo: FullCreateSessionDataParams[] = + sessionRequestedInfo.map(applyDefaults) const attestersToTrust = attesters ?? [MOCK_ATTESTER_ADDRESS] const actionResponse = await getPermissionAction({ @@ -248,7 +305,7 @@ export async function grantPermission< }) const needToAddTrustAttesters = trustedAttesters.length === 0 - Logger.log("needToAddTrustAttesters", needToAddTrustAttesters) + console.log("needToAddTrustAttesters", needToAddTrustAttesters) if (!("action" in actionResponse)) { throw new Error("Error getting enable sessions action") @@ -264,26 +321,21 @@ export async function grantPermission< throw new Error("Error getting trust attesters action") } - const calls = needToAddTrustAttesters - ? [ - { - to: trustAttestersAction.target, - value: trustAttestersAction.value.valueOf(), - data: trustAttestersAction.callData - }, - { - to: action.target, - value: action.value, - data: action.callData - } - ] - : [ - { - to: action.target, - value: action.value, - data: action.callData - } - ] + const calls: Call[] = [] + + if (needToAddTrustAttesters) { + calls.push({ + to: trustAttestersAction.target, + value: trustAttestersAction.value.valueOf(), + data: trustAttestersAction.callData + }) + } + + calls.push({ + to: action.target, + value: action.value, + data: action.callData + }) if ("action" in actionResponse) { const userOpHash = (await getAction( diff --git a/src/sdk/modules/smartSessionsValidator/decorators/index.ts b/src/sdk/modules/smartSessionsValidator/decorators/index.ts index bbbec690..b9e94b15 100644 --- a/src/sdk/modules/smartSessionsValidator/decorators/index.ts +++ b/src/sdk/modules/smartSessionsValidator/decorators/index.ts @@ -1,5 +1,4 @@ import type { Chain, Client, Hash, Transport } from "viem" -import { danActions } from "../../../clients/decorators/dan/decorators" import type { ModularSmartAccount, Module } from "../../utils/Types" import type { GrantPermissionResponse } from "../Types" import type { SmartSessionModule } from "../toSmartSessionsValidator" @@ -8,11 +7,6 @@ import { grantPermission } from "./grantPermission" import { type TrustAttestersParameters, trustAttesters } from "./trustAttesters" -import { - type DanClient, - type UseDistributedPermissionParameters, - useDistributedPermission -} from "./useDistributedPermission" import { type UsePermissionParameters, usePermission } from "./usePermission" /** * Defines the shape of actions available for creating smart sessions. @@ -60,15 +54,6 @@ export type SmartSessionUseActions< usePermission: ( args: UsePermissionParameters ) => Promise - /** - * Uses a session to perform multiple actions. - * - * @param args - Parameters for using a session. - * @returns A promise that resolves to the transaction hash. - */ - useDistributedPermission: ( - args: UseDistributedPermissionParameters - ) => Promise } /** @@ -102,11 +87,7 @@ export function smartSessionUseActions( ): SmartSessionUseActions => { client?.account?.setModule(smartSessionsModule) return { - usePermission: (args) => usePermission(client, args), - useDistributedPermission: (args) => { - const danClient = client.extend(danActions()) as unknown as DanClient - return useDistributedPermission(danClient, args) - } + usePermission: (args) => usePermission(client, args) } } } @@ -114,4 +95,3 @@ export function smartSessionUseActions( export * from "./grantPermission" export * from "./trustAttesters" export * from "./usePermission" -export * from "./useDistributedPermission" diff --git a/src/sdk/modules/smartSessionsValidator/decorators/trustAttesters.ts b/src/sdk/modules/smartSessionsValidator/decorators/trustAttesters.ts index fc9393c3..1bc5b0ff 100644 --- a/src/sdk/modules/smartSessionsValidator/decorators/trustAttesters.ts +++ b/src/sdk/modules/smartSessionsValidator/decorators/trustAttesters.ts @@ -5,7 +5,6 @@ import { getAction, parseAccount } from "viem/utils" import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" import { MOCK_ATTESTER_ADDRESS, REGISTRY_ADDRESS } from "../../../constants" import type { ModularSmartAccount } from "../../utils/Types" - /** * Parameters for trusting attesters in a smart session validator. * diff --git a/src/sdk/modules/smartSessionsValidator/decorators/useDistributedPermission.ts b/src/sdk/modules/smartSessionsValidator/decorators/useDistributedPermission.ts deleted file mode 100644 index fa82c488..00000000 --- a/src/sdk/modules/smartSessionsValidator/decorators/useDistributedPermission.ts +++ /dev/null @@ -1,113 +0,0 @@ -import type { Chain, Client, Hex, Transport } from "viem" -import { type BundlerClient, sendUserOperation } from "viem/account-abstraction" -import { getAction } from "viem/utils" -import { ERROR_MESSAGES } from "../../../account" -import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" -import type { Call } from "../../../account/utils/Types" -import type { Signer } from "../../../account/utils/toSigner" -import type { DanActions } from "../../../clients/decorators/dan/decorators" -import { parseModule } from "../../utils/Helpers" -import type { ModularSmartAccount } from "../../utils/Types" -import type { SmartSessionModule } from "../toSmartSessionsValidator" - -/** - * Parameters for using a smart session to execute actions. - * - * @template TModularSmartAccount - Type of the modular smart account, extending ModularSmartAccount or undefined. - */ -export type UseDistributedPermissionParameters< - TModularSmartAccount extends ModularSmartAccount | undefined -> = { - /** Array of executions to perform in the session. Allows for batch transactions if the session is enabled for multiple actions. */ - calls: Call[] - /** The maximum fee per gas unit the transaction is willing to pay. */ - maxFeePerGas?: bigint - /** The maximum priority fee per gas unit the transaction is willing to pay. */ - maxPriorityFeePerGas?: bigint - /** The nonce of the transaction. If not provided, it will be determined automatically. */ - nonce?: bigint - /** The modular smart account to use for the session. If not provided, the client's account will be used. */ - account?: TModularSmartAccount - /** The signer to use for the session. Defaults to the signer of the client. */ - signer?: Signer -} - -export type DanClient = Client< - Transport, - Chain | undefined, - ModularSmartAccount -> & - DanActions & - BundlerClient - -/** - * Executes actions using a smart session. - * - * This function allows for the execution of one or more actions within an enabled smart session. - * It can handle batch transactions if the session is configured for multiple actions. - * - * @template TModularSmartAccount - Type of the modular smart account, extending ModularSmartAccount or undefined. - * @param client - The client used to interact with the blockchain. - * @param parameters - Parameters for using the session, including actions to execute and optional gas settings. - * @returns A promise that resolves to the hash of the sent user operation. - * - * @throws {AccountNotFoundError} If no account is provided and the client doesn't have an associated account. - * - * @example - * ```typescript - * const result = await useDistributedPermission(nexusClient, { - * calls: [ - * { - * to: '0x1234...', - * data: '0xabcdef...' - * } - * ], - * maxFeePerGas: 1000000000n - * }); - * console.log(`Transaction hash: ${result}`); - * ``` - * - * @remarks - * - Ensure that the session is enabled and has the necessary permissions for the actions being executed. - * - For batch transactions, all actions must be permitted within the same session. - * - The function uses the `sendUserOperation` method, which is specific to account abstraction implementations. - */ -export async function useDistributedPermission< - TModularSmartAccount extends ModularSmartAccount | undefined ->( - client: DanClient, - parameters: UseDistributedPermissionParameters -): Promise { - const { account: account_ = client.account } = parameters - - if (!account_) { - throw new AccountNotFoundError({ - docsPath: "/nexus-client/methods#sendtransaction" - }) - } - - const params = { ...parameters, account: account_ } - - const preppedUserOp = await client.prepareUserOperation(params) - const sessionsModule = parseModule(client) as SmartSessionModule - const keyGenData = sessionsModule?.moduleData?.keyGenData - - if (!keyGenData) { - throw new Error(ERROR_MESSAGES.KEY_GEN_DATA_NOT_FOUND) - } - - const { signature } = await client.sigGen({ ...preppedUserOp, keyGenData }) - - if (!signature) { - throw new Error(ERROR_MESSAGES.SIGNATURE_NOT_FOUND) - } - - const extendedSignature = sessionsModule.sigGen(signature) - - return await getAction( - client, - sendUserOperation, - "sendUserOperation" - // @ts-ignore - )({ ...preppedUserOp, account: account_, signature: extendedSignature }) -} diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts deleted file mode 100644 index a2288bf4..00000000 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { SmartSessionMode } from "@rhinestone/module-sdk/module" -import { - http, - type Chain, - type Hex, - type LocalAccount, - encodeFunctionData -} from "viem" -import { afterAll, beforeAll, describe, expect, test } from "vitest" -import { CounterAbi } from "../../../test/__contracts/abi/CounterAbi" -import { testAddresses } from "../../../test/callDatas" -import { toNetwork } from "../../../test/testSetup" -import { - fundAndDeployClients, - getTestAccount, - killNetwork, - toTestClient -} from "../../../test/testUtils" -import type { MasterClient, NetworkConfig } from "../../../test/testUtils" -import { - type NexusClient, - createNexusClient -} from "../../clients/createNexusClient" -import { createNexusSessionClient } from "../../clients/createNexusSessionClient" -import { danActions } from "../../clients/decorators/dan" -import type { Module } from "../utils/Types" -import { parse, stringify } from "./Helpers" -import type { CreateSessionDataParams, SessionData } from "./Types" -import { smartSessionCreateActions, smartSessionUseActions } from "./decorators" -import { toSmartSessionsValidator } from "./toSmartSessionsValidator" - -// This test suite demonstrates how to create and use a smart session using Biconomy's Distributed Sessions (DAN). -// Distributed Sessions enhance security and efficiency by storing session keys on Biconomy's Delegated Authorisation Network (DAN), -// providing features like automated transaction processing and reduced exposure of private keys. - -describe("modules.smartSessions.dan.dx", async () => { - let network: NetworkConfig - let chain: Chain - let bundlerUrl: string - - // Test utilities and variables - let testClient: MasterClient - let eoaAccount: LocalAccount - let usersNexusClient: NexusClient - let dappAccount: LocalAccount - let zippedSessionDatum: string - let sessionsModule: Module - - beforeAll(async () => { - // Setup test network and accounts - network = await toNetwork("BASE_SEPOLIA_FORKED") - chain = network.chain - bundlerUrl = network.bundlerUrl - eoaAccount = getTestAccount(0) - dappAccount = getTestAccount(7) - testClient = toTestClient(chain, getTestAccount(5)) - }) - - afterAll(async () => { - // Clean up the network after tests - await killNetwork([network?.rpcPort, network?.bundlerPort]) - }) - - test("should demonstrate creating a smart session using DAN", async () => { - // Initialize the user's Nexus client with DAN actions - usersNexusClient = await createNexusClient({ - signer: eoaAccount, - chain, - transport: http(), - bundlerTransport: http(bundlerUrl) - }) - - const danNexusClient = usersNexusClient.extend(danActions()) - - // Generate a session key using DAN - const keyGenData = await danNexusClient.keyGen() - const sessionPublicKey = keyGenData.sessionPublicKey - - // Fund and deploy the user's smart account - await fundAndDeployClients(testClient, [usersNexusClient]) - - // Initialize the smart sessions validator module - sessionsModule = toSmartSessionsValidator({ - account: usersNexusClient.account, - signer: eoaAccount - }) - - // Install the sessions module - const hash = await usersNexusClient.installModule({ - module: sessionsModule.moduleInitData - }) - - // Extend the Nexus client with smart session creation actions - const nexusSessionClient = usersNexusClient.extend( - smartSessionCreateActions(sessionsModule) - ) - - // Wait for the module installation to complete - const { success: installSuccess } = - await usersNexusClient.waitForUserOperationReceipt({ hash }) - - expect(installSuccess).toBe(true) - - // Define the permissions for the smart session - const sessionRequestedInfo: CreateSessionDataParams[] = [ - { - sessionPublicKey, // Public key of the session stored in DAN - actionPoliciesInfo: [ - { - contractAddress: testAddresses.Counter, - functionSelector: "0x273ea3e3" as Hex // Selector for 'incrementNumber' function - } - ] - } - ] - - // Create the smart session with the specified permissions - const createSessionsResponse = await nexusSessionClient.grantPermission({ - sessionRequestedInfo - }) - - // Wait for the permission grant operation to complete - const { success: sessionCreateSuccess } = - await usersNexusClient.waitForUserOperationReceipt({ - hash: createSessionsResponse.userOpHash - }) - - expect(installSuccess).toBe(sessionCreateSuccess) - - // Prepare the session data to be stored by the dApp. This could be saved in a Database or client side in local storage. - const sessionData: SessionData = { - granter: usersNexusClient.account.address, - sessionPublicKey, - moduleData: { - keyGenData, - permissionIds: createSessionsResponse.permissionIds, - mode: SmartSessionMode.USE - } - } - - // Serialize the session data - zippedSessionDatum = stringify(sessionData) - }, 200000) - - test("should demonstrate using a smart session using DAN", async () => { - // Parse the session data received from the user - const { moduleData, granter } = parse(zippedSessionDatum) - - // Initialize the smart session client's Nexus client - const smartSessionNexusClient = await createNexusSessionClient({ - chain, - accountAddress: granter, - signer: dappAccount, - transport: http(), - bundlerTransport: http(bundlerUrl) - }) - - // Initialize the smart sessions validator module with the received module data - const usePermissionsModule = toSmartSessionsValidator({ - account: smartSessionNexusClient.account, - signer: dappAccount, - moduleData // This includes the keyGenData - }) - - // Extend the Nexus client with smart session usage and dan actions - const danSessionClient = smartSessionNexusClient - .extend(smartSessionUseActions(usePermissionsModule)) - .extend(danActions()) - - // Use the distributed permission to execute a transaction - const userOpHash = await danSessionClient.useDistributedPermission({ - calls: [ - { - to: testAddresses.Counter, - data: encodeFunctionData({ - abi: CounterAbi, - functionName: "incrementNumber" - }) - } - ] - }) - - // Wait for the transaction to be processed - const { success: sessionUseSuccess } = - await danSessionClient.waitForUserOperationReceipt({ - hash: userOpHash - }) - - expect(sessionUseSuccess).toBe(true) - }, 200000) // Test timeout set to 200 seconds -}) diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts index c84df261..02e5cfd2 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts @@ -25,7 +25,7 @@ import { } from "../../clients/createNexusClient" import { createNexusSessionClient } from "../../clients/createNexusSessionClient" import type { Module } from "../utils/Types" -import { parse, stringify } from "./Helpers" +import { abiToPoliciesInfo, parse, stringify } from "./Helpers" import type { CreateSessionDataParams, SessionData } from "./Types" import { smartSessionCreateActions, smartSessionUseActions } from "./decorators" import { toSmartSessionsValidator } from "./toSmartSessionsValidator" @@ -115,21 +115,24 @@ describe("modules.smartSessions.dx", async () => { // Define the session parameters // This includes the session key, validator, and action policies - const sessionRequestedInfo: CreateSessionDataParams[] = [ - { - sessionPublicKey, // Public key of the session - actionPoliciesInfo: [ - { - contractAddress: testAddresses.Counter, - functionSelector: "0x273ea3e3" as Hex // Selector for 'incrementNumber' - } - ] - } - ] - - // Create the smart session const createSessionsResponse = await nexusSessionClient.grantPermission({ - sessionRequestedInfo + sessionRequestedInfo: [ + { + sessionPublicKey, // Public key of the session + // sessionValidUntil: number + // sessionValidAfter: number + // chainIds: bigint[] + actionPoliciesInfo: [ + { + abi: CounterAbi, + contractAddress: testAddresses.Counter + // validUntil?: number + // validAfter?: number + // valueLimit?: bigint + } + ] + } + ] }) // Wait for the session creation transaction to be mined and check its success @@ -138,7 +141,7 @@ describe("modules.smartSessions.dx", async () => { hash: createSessionsResponse.userOpHash }) - expect(installSuccess).toBe(sessionCreateSuccess) + expect(sessionCreateSuccess).toBe(true) // Prepare the session data to be stored by the dApp. This could be saved in a Database by the dApp, or client side in local storage. const sessionData: SessionData = { @@ -183,7 +186,7 @@ describe("modules.smartSessions.dx", async () => { smartSessionUseActions(usePermissionsModule) ) - // Use the session to perform an action (increment the counter) + // Use the session to perform an action (increment and decrement the counter using the same permissionId) const userOpHash = await useSmartSessionNexusClient.usePermission({ calls: [ { @@ -192,6 +195,13 @@ describe("modules.smartSessions.dx", async () => { abi: CounterAbi, functionName: "incrementNumber" }) + }, + { + to: testAddresses.Counter, + data: encodeFunctionData({ + abi: CounterAbi, + functionName: "decrementNumber" + }) } ] }) diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.policies.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.policies.test.ts new file mode 100644 index 00000000..531b6b2e --- /dev/null +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.policies.test.ts @@ -0,0 +1,150 @@ +import { + http, + type Address, + type Chain, + type Hex, + type LocalAccount +} from "viem" +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" +import { afterAll, beforeAll, describe, expect, test } from "vitest" +import { testAddresses } from "../../../test/callDatas" +import { toNetwork } from "../../../test/testSetup" +import { + fundAndDeployClients, + getTestAccount, + killNetwork, + toTestClient +} from "../../../test/testUtils" +import type { MasterClient, NetworkConfig } from "../../../test/testUtils" +import { + type NexusClient, + createNexusClient +} from "../../clients/createNexusClient" +import type { Module } from "../utils/Types" +import { smartSessionCreateActions } from "./decorators" +import { toSmartSessionsValidator } from "./toSmartSessionsValidator" + +describe("modules.smartSessions.policies", async () => { + let network: NetworkConfig + let chain: Chain + let bundlerUrl: string + + // Test utils + let testClient: MasterClient + let eoaAccount: LocalAccount + let nexusClient: NexusClient + let nexusAccountAddress: Address + let sessionKeyAccount: LocalAccount + let sessionPublicKey: Address + + let sessionsModule: Module + + beforeAll(async () => { + network = await toNetwork("BASE_SEPOLIA_FORKED") + + chain = network.chain + bundlerUrl = network.bundlerUrl + eoaAccount = getTestAccount(0) + sessionKeyAccount = privateKeyToAccount(generatePrivateKey()) // Generally belongs to the dapp + sessionPublicKey = sessionKeyAccount.address + + testClient = toTestClient(chain, getTestAccount(5)) + + nexusClient = await createNexusClient({ + signer: eoaAccount, + chain, + transport: http(), + bundlerTransport: http(bundlerUrl) + }) + + nexusAccountAddress = await nexusClient.account.getCounterFactualAddress() + + sessionsModule = toSmartSessionsValidator({ + account: nexusClient.account, + signer: eoaAccount + }) + + await fundAndDeployClients(testClient, [nexusClient]) + }) + + afterAll(async () => { + await killNetwork([network?.rpcPort, network?.bundlerPort]) + }) + + test("should install smartSessionValidator with no init data", async () => { + const isInstalledBefore = await nexusClient.isModuleInstalled({ + module: sessionsModule.moduleInitData + }) + + if (!isInstalledBefore) { + const hash = await nexusClient.installModule({ + module: sessionsModule.moduleInitData + }) + + const { success: installSuccess } = + await nexusClient.waitForUserOperationReceipt({ hash }) + expect(installSuccess).toBe(true) + } + + const isInstalledAfter = await nexusClient.isModuleInstalled({ + module: sessionsModule.moduleInitData + }) + expect(isInstalledAfter).toBe(true) + }) + + test("should grant permission with all available policies", async () => { + const usersNexusClient = await createNexusClient({ + signer: eoaAccount, + chain, + transport: http(), + bundlerTransport: http(bundlerUrl) + }) + + // Create a smart sessions module for the user's account + sessionsModule = toSmartSessionsValidator({ + account: usersNexusClient.account, + signer: eoaAccount + }) + + // Extend the Nexus client with smart session creation actions + const nexusSessionClient = usersNexusClient.extend( + smartSessionCreateActions(sessionsModule) + ) + + // Define the session parameters + // This includes the session key, validator, and action policies + const createSessionsResponse = await nexusSessionClient.grantPermission({ + sessionRequestedInfo: [ + { + sessionPublicKey, // Public key of the session + sessionValidUntil: Date.now() + 1000 * 60 * 60 * 24, // 1 day from now + chainIds: [BigInt(chain.id)], + actionPoliciesInfo: [ + { + contractAddress: testAddresses.Counter, + sudo: false, // covered in another test + tokenLimits: [ + { + token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH + limit: BigInt(1000) + } + ], // covered in another test + // usageLimit: 1000n, // TODO: failing because of attestations + // valueLimit: 1000n, // TODO: failing because of attestations + validUntil: Date.now() + 1000 * 60 * 60 * 24, // 1 day from now + functionSelector: "0x871cc9d4" // decrementNumber + } + ] + } + ] + }) + + // Wait for the session creation transaction to be mined and check its success + const { success: sessionCreateSuccess } = + await nexusSessionClient.waitForUserOperationReceipt({ + hash: createSessionsResponse.userOpHash + }) + + expect(sessionCreateSuccess).toBe(true) + }) +}) diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.sudo.policy.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.sudo.policy.test.ts new file mode 100644 index 00000000..ad1120ce --- /dev/null +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.sudo.policy.test.ts @@ -0,0 +1,215 @@ +import { SmartSessionMode } from "@rhinestone/module-sdk/module" +import { + http, + type AbiFunction, + type Address, + type Chain, + type Hex, + type LocalAccount, + encodeFunctionData +} from "viem" +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" +import { afterAll, beforeAll, describe, expect, test } from "vitest" +import { CounterAbi } from "../../../test/__contracts/abi/CounterAbi" +import { MockCalleeAbi } from "../../../test/__contracts/abi/MockCalleeAbi" +import { testAddresses } from "../../../test/callDatas" +import { toNetwork } from "../../../test/testSetup" +import { + fundAndDeployClients, + getTestAccount, + killNetwork, + toTestClient +} from "../../../test/testUtils" +import type { MasterClient, NetworkConfig } from "../../../test/testUtils" +import { + type NexusClient, + createNexusClient +} from "../../clients/createNexusClient" +import type { Module } from "../utils/Types" +import { parse, stringify } from "./Helpers" +import type { SessionData } from "./Types" +import { smartSessionCreateActions, smartSessionUseActions } from "./decorators" +import { toSmartSessionsValidator } from "./toSmartSessionsValidator" + +describe("modules.smartSessions.sudo.policy", async () => { + let network: NetworkConfig + let chain: Chain + let bundlerUrl: string + + // Test utils + let testClient: MasterClient + let eoaAccount: LocalAccount + let nexusClient: NexusClient + let nexusAccountAddress: Address + let sessionKeyAccount: LocalAccount + let sessionPublicKey: Address + + let zippedSessionDatum: string // Session data to be stored by the dApp + + let sessionsModule: Module + + beforeAll(async () => { + network = await toNetwork("BASE_SEPOLIA_FORKED") + + chain = network.chain + bundlerUrl = network.bundlerUrl + eoaAccount = getTestAccount(0) + sessionKeyAccount = privateKeyToAccount(generatePrivateKey()) // Generally belongs to the dapp + sessionPublicKey = sessionKeyAccount.address + + testClient = toTestClient(chain, getTestAccount(5)) + + nexusClient = await createNexusClient({ + signer: eoaAccount, + chain, + transport: http(), + bundlerTransport: http(bundlerUrl) + }) + + nexusAccountAddress = await nexusClient.account.getCounterFactualAddress() + + sessionsModule = toSmartSessionsValidator({ + account: nexusClient.account, + signer: eoaAccount + }) + + await fundAndDeployClients(testClient, [nexusClient]) + }) + + afterAll(async () => { + await killNetwork([network?.rpcPort, network?.bundlerPort]) + }) + + test("should install smartSessionValidator with no init data", async () => { + // Create a smart sessions module for the user's account + sessionsModule = toSmartSessionsValidator({ + account: nexusClient.account, + signer: eoaAccount + }) + + // Install the smart sessions module on the Nexus client's smart contract account + const hash = await nexusClient.installModule({ + module: sessionsModule.moduleInitData + }) + + const { success } = await nexusClient.waitForUserOperationReceipt({ hash }) + expect(success).toBe(true) + + // Extend the Nexus client with smart session creation actions + const usersNexusClient = nexusClient.extend( + smartSessionCreateActions(sessionsModule) + ) + + const createSessionsResponse = await usersNexusClient.grantPermission({ + sessionRequestedInfo: [ + { + sessionPublicKey, + // sessionValidUntil: number + // sessionValidAfter: number + // chainIds: bigint[] + actionPoliciesInfo: [ + { + abi: CounterAbi, + contractAddress: testAddresses.Counter, + sudo: true + } + ] + } + ] + }) + + // Wait for the session creation transaction to be mined and check its success + const { success: sessionCreateSuccess } = + await usersNexusClient.waitForUserOperationReceipt({ + hash: createSessionsResponse.userOpHash + }) + + expect(sessionCreateSuccess).toBe(true) + + // Prepare the session data to be stored by the dApp. This could be saved in a Database by the dApp, or client side in local storage. + const sessionData: SessionData = { + granter: usersNexusClient?.account?.address as Hex, + sessionPublicKey, + moduleData: { + permissionIds: createSessionsResponse.permissionIds, + mode: SmartSessionMode.USE + } + } + + // Zip the session data, and store it for later use by a dapp + zippedSessionDatum = stringify(sessionData) + }) + + test("should demonstrate using a smart session from dapp's perspective", async () => { + // Now assume the user has left the dapp and the usersNexusClient signer is no longer available + // The following code demonstrates how a dapp can use the session to act on behalf of the user + + // Unzip the session data + const usersSessionData = parse(zippedSessionDatum) + + // Create a new Nexus client for the session + // This client will be used to interact with the smart contract account using the session key + const smartSessionNexusClient = await createNexusClient({ + chain, + accountAddress: usersSessionData.granter, + signer: sessionKeyAccount, + transport: http(), + bundlerTransport: http(bundlerUrl) + }) + + // Create a new smart sessions module with the session key + const usePermissionsModule = toSmartSessionsValidator({ + account: smartSessionNexusClient.account, + signer: sessionKeyAccount, + moduleData: usersSessionData.moduleData + }) + + // Extend the session client with smart session use actions + const useSmartSessionNexusClient = smartSessionNexusClient.extend( + smartSessionUseActions(usePermissionsModule) + ) + + const counterBefore = await testClient.readContract({ + address: testAddresses.Counter, + abi: CounterAbi, + functionName: "getNumber" + }) + + // Use the session to perform an action (increment the counter) + const userOpHash = await useSmartSessionNexusClient.usePermission({ + calls: [ + { + to: testAddresses.Counter, + data: encodeFunctionData({ + abi: CounterAbi, + functionName: "incrementNumber" + }) + }, + { + to: testAddresses.Counter, + data: encodeFunctionData({ + abi: CounterAbi, + functionName: "decrementNumber" + }) + } + ] + }) + + const counterAfter = await testClient.readContract({ + address: testAddresses.Counter, + abi: CounterAbi, + functionName: "getNumber" + }) + + // Counter should be unchanged + expect(counterAfter).toBe(counterBefore) + + // Wait for the action to be mined and check its success + const { success: sessionUseSuccess } = + await useSmartSessionNexusClient.waitForUserOperationReceipt({ + hash: userOpHash + }) + + expect(sessionUseSuccess).toBe(true) + }, 200000) // Test timeout set to 60 seconds +}) diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts index 98bab7ca..cdb0d099 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts @@ -6,12 +6,10 @@ import { type LocalAccount, encodeFunctionData, pad, - toBytes, toHex } from "viem" import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" import { afterAll, beforeAll, describe, expect, test } from "vitest" -import { MockRegistryAbi } from "../../../test/__contracts/abi" import { CounterAbi } from "../../../test/__contracts/abi/CounterAbi" import { testAddresses } from "../../../test/callDatas" import { toNetwork } from "../../../test/testSetup" @@ -27,10 +25,9 @@ import { createNexusClient } from "../../clients/createNexusClient" import { createNexusSessionClient } from "../../clients/createNexusSessionClient" -import { SIMPLE_SESSION_VALIDATOR_ADDRESS } from "../../constants" import { parseReferenceValue } from "../utils/Helpers" import type { Module } from "../utils/Types" -import policies from "./Helpers" +import { abiToPoliciesInfo, toUniversalActionPolicy } from "./Helpers" import type { CreateSessionDataParams } from "./Types" import { ParamCondition } from "./Types" import { smartSessionCreateActions, smartSessionUseActions } from "./decorators" @@ -88,6 +85,42 @@ describe("modules.smartSessions", async () => { expect(bytecodes.every((bytecode) => !!bytecode?.length)).toBeTruthy() }) + test("should convert an ABI to a contract whitelist", async () => { + const contractWhitelist = abiToPoliciesInfo({ + abi: CounterAbi, + contractAddress: testAddresses.Counter + }) + + expect(contractWhitelist).toBeDefined() + + // Verify the structure matches all CounterAbi functions + expect(contractWhitelist).toEqual([ + { + contractAddress: testAddresses.Counter, + functionSelector: "0x871cc9d4", // decrementNumber + rules: [] + }, + { + contractAddress: testAddresses.Counter, + functionSelector: "0xf2c9ecd8", // getNumber + rules: [] + }, + { + contractAddress: testAddresses.Counter, + functionSelector: "0x273ea3e3", // incrementNumber + rules: [] + }, + { + contractAddress: testAddresses.Counter, + functionSelector: "0x12467434", // revertOperation + rules: [] + } + ]) + + // Verify the length matches the number of functions in CounterAbi + expect(contractWhitelist).toHaveLength(4) + }) + test.concurrent( "should parse a human friendly policy reference value to the hex version expected by the contracts", async () => { @@ -134,30 +167,12 @@ describe("modules.smartSessions", async () => { ] } } - const installUniversalPolicy = policies.to.universalAction(actionConfigData) + const installUniversalPolicy = toUniversalActionPolicy(actionConfigData) expect(installUniversalPolicy.policy).toEqual(testAddresses.UniActionPolicy) expect(installUniversalPolicy.initData).toBeDefined() }) - test.concurrent("should get a sudo action policy", async () => { - const installSudoActionPolicy = policies.sudo - expect(installSudoActionPolicy.policy).toBeDefined() - expect(installSudoActionPolicy.initData).toEqual("0x") - }) - - test.concurrent("should get a spending limit policy", async () => { - const installSpendingLimitPolicy = policies.to.spendingLimits([ - { - limit: BigInt(1000), - token: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" - } - ]) - - expect(installSpendingLimitPolicy.policy).toBeDefined() - expect(installSpendingLimitPolicy.initData).toBeDefined() - }) - test.concurrent( "should have valid smartSessionValidator properties", async () => { @@ -206,8 +221,6 @@ describe("modules.smartSessions", async () => { expect(isInstalledBefore).toBe(true) - // Note: grantPermission decorator will take care of trusting the attester. - // session key signer address is declared here const sessionRequestedInfo: CreateSessionDataParams[] = [ { diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.ts index 7f4ca78e..24530d23 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.ts @@ -103,8 +103,7 @@ export const toSmartSessionsValidator = ( permissionIdIndex = 0, permissionIds = [], mode = SmartSessionMode.USE, - enableSessionData, - keyGenData: _ + enableSessionData } = {} } = parameters @@ -135,16 +134,6 @@ export const toSmartSessionsValidator = ( signature: await signer.signMessage({ message: { raw: userOpHash as Hex } }) - }), - extend: { - sigGen: (signature: Hex): Hex => { - return encodeSmartSessionSignature({ - mode, - permissionId: permissionIds[permissionIdIndex], - enableSessionData, - signature - }) - } - } + }) }) as SmartSessionModule } diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts index 7856a883..6da4c889 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts @@ -1,5 +1,7 @@ +import { SmartSessionMode } from "@rhinestone/module-sdk" import { http, + type Abi, type AbiFunction, type Address, type Chain, @@ -9,13 +11,11 @@ import { encodeFunctionData, getContract, slice, - toBytes, - toFunctionSelector, - toHex + toFunctionSelector } from "viem" import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" import { afterAll, beforeAll, describe, expect, test } from "vitest" -import { MockRegistryAbi } from "../../../test/__contracts/abi" +import { CounterAbi, MockRegistryAbi } from "../../../test/__contracts/abi" import { MockCalleeAbi } from "../../../test/__contracts/abi/MockCalleeAbi" import { testAddresses } from "../../../test/callDatas" import { toNetwork } from "../../../test/testSetup" @@ -33,13 +33,13 @@ import { import { createNexusSessionClient } from "../../clients/createNexusSessionClient" import { SMART_SESSIONS_ADDRESS } from "../../constants" import type { Module } from "../utils/Types" -import { isPermissionEnabled } from "./Helpers" -import type { CreateSessionDataParams, Rule } from "./Types" +import { abiToPoliciesInfo, isPermissionEnabled } from "./Helpers" +import type { CreateSessionDataParams, Rule, SessionData } from "./Types" import { ParamCondition } from "./Types" import { smartSessionCreateActions, smartSessionUseActions } from "./decorators" import { toSmartSessionsValidator } from "./toSmartSessionsValidator" -describe("modules.smartSessions.uniPolicy", async () => { +describe("modules.smartSessions.uni.policy", async () => { let network: NetworkConfig let chain: Chain let bundlerUrl: string diff --git a/src/test/callDatas.ts b/src/test/callDatas.ts index 75933b36..b5d8d75c 100644 --- a/src/test/callDatas.ts +++ b/src/test/callDatas.ts @@ -5,8 +5,13 @@ import { OWNABLE_EXECUTOR_ADDRESS, OWNABLE_VALIDATOR_ADDRESS, REGISTRY_ADDRESS, + RHINESTONE_ATTESTER_ADDRESS, SIMPLE_SESSION_VALIDATOR_ADDRESS, - SMART_SESSIONS_ADDRESS + SMART_SESSIONS_ADDRESS, + TIME_FRAME_POLICY_ADDRESS, + UNIVERSAL_ACTION_POLICY_ADDRESS, + USAGE_LIMIT_POLICY_ADDRESS, + VALUE_LIMIT_POLICY_ADDRESS } from "../sdk/constants" export const TEST_CONTRACTS: Record< @@ -37,7 +42,7 @@ export const TEST_CONTRACTS: Record< UniActionPolicy: { chainId: baseSepolia.id, name: "UniActionPolicy", - address: "0x148CD6c24F4dd23C396E081bBc1aB1D92eeDe2BF" + address: UNIVERSAL_ACTION_POLICY_ADDRESS }, Counter: { chainId: baseSepolia.id, @@ -64,6 +69,11 @@ export const TEST_CONTRACTS: Record< name: "MockAttester", address: MOCK_ATTESTER_ADDRESS }, + RhinestoneAttester: { + chainId: baseSepolia.id, + name: "RhinestoneAttester", + address: RHINESTONE_ATTESTER_ADDRESS + }, MockRegistry: { chainId: baseSepolia.id, name: "MockRegistry", @@ -72,17 +82,17 @@ export const TEST_CONTRACTS: Record< TimeFramePolicy: { chainId: baseSepolia.id, name: "TimeFramePolicy", - address: "0x0B7BB9bD65858593D97f12001FaDa94828307805" + address: TIME_FRAME_POLICY_ADDRESS }, UsageLimitPolicy: { chainId: baseSepolia.id, name: "UsageLimitPolicy", - address: "0x80EF509D2F79eA332540e9698bDbc7B7FA3E1f74" + address: USAGE_LIMIT_POLICY_ADDRESS }, ValueLimitPolicy: { chainId: baseSepolia.id, name: "ValueLimitPolicy", - address: "0xDe9688b24c00699Ad51961ef90Ce5a9a8C49982B" + address: VALUE_LIMIT_POLICY_ADDRESS }, WalletConnectCoSigner: { chainId: baseSepolia.id, diff --git a/src/test/testUtils.ts b/src/test/testUtils.ts index 3add7b22..649287c4 100644 --- a/src/test/testUtils.ts +++ b/src/test/testUtils.ts @@ -210,7 +210,7 @@ export const toConfiguredAnvil = async ({ port: rpcPort, codeSizeLimit: 1000000000000, forkUrl: shouldForkBaseSepolia - ? "https://virtual.base-sepolia.rpc.tenderly.co/6deb172f-d5d9-4ae3-9d1d-8f04d52714d6" + ? "https://virtual.base-sepolia.rpc.tenderly.co/42d65eaa-5a3b-46d5-8b51-814f4a5661d0" : undefined } const instance = anvil(config)