diff --git a/README.md b/README.md index 90f7a4536..b4c9f7df7 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 3d7bee684..180da229c 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index ce1d25900..b56e40af2 100644 --- a/package.json +++ b/package.json @@ -37,12 +37,14 @@ "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/constants/index.ts b/src/sdk/constants/index.ts index ba22292fb..d0c47ee50 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/smartSessionsValidator/Helpers.ts b/src/sdk/modules/smartSessionsValidator/Helpers.ts index b61a5783f..4fa748c6a 100644 --- a/src/sdk/modules/smartSessionsValidator/Helpers.ts +++ b/src/sdk/modules/smartSessionsValidator/Helpers.ts @@ -30,13 +30,12 @@ import { parseReferenceValue } from "../utils/Helpers" import type { AnyData } from "../utils/Types" import type { ActionConfig, - ActionPolicyData, + ActionPolicyInfo, CreateSessionDataParams, FullCreateSessionDataParams, RawActionConfig, - Rule, - SpendingLimitsParams, - SudoPolicyData + ResolvedActionPolicyInfo, + Rule } from "./Types" export const MAX_RULES = 16 @@ -94,23 +93,6 @@ export const applyDefaults = ( sessionInfo.sessionValidatorAddress ?? SIMPLE_SESSION_VALIDATOR_ADDRESS } } -/** - * Creates an ActionData object for a sudo policy. - * - * @param contractAddress - The address of the contract. - * @param functionSelector - The function selector or AbiFunction. - * @returns An ActionData object. - */ -export const createSudoData = ( - contractAddress: Address, - functionSelector: string | AbiFunction -): ActionData => ({ - actionTargetSelector: (typeof functionSelector === "string" - ? functionSelector - : functionSelector.name) as Hex, - actionTarget: contractAddress, - actionPolicies: [getSudoPolicy()] -}) /** * Creates an ActionData object. @@ -206,13 +188,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. @@ -260,34 +243,6 @@ export const toTimeRangePolicy = ( return timeFramePolicyData } -/** - * 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 - } -} as const - /** * Stringifies an object, explicitly tagging BigInt values. * @@ -357,47 +312,39 @@ export const getTrustedAttesters = async ({ } /** - * Converts an ABI to a list of ActionPolicyData objects. + * Converts an ABI to a list of ActionPolicyInfo objects. * * @param params - The parameters object * @param params.abi - The ABI to convert - * @param params.actionPolicyData - The ActionPolicyData object to apply to each function in the ABI - * @returns An array of ActionPolicyData objects - */ -export const abi2ActionPolicy = ({ - abi, - actionPolicyData -}: { - abi: Abi - actionPolicyData: Omit & { - rules?: never // Rules should not be available here because they should be custom per method, not used in a loop - } -}): ActionPolicyData[] => - abi - .filter((item): item is AbiFunction => item.type === "function") - .map((func) => ({ - ...actionPolicyData, - functionSelector: toFunctionSelector(func) - })) - -/** - * Converts an ABI to a list of SudoPolicyData objects. + * @param params.actionPolicyInfo - The ActionPolicyInfo object to apply to each function in the ABI * - * @param params - The parameters object - * @param params.abi - The ABI to convert - * @param params.contractAddress - The address of the contract - * @returns An array of SudoPolicyData objects + * @example + * const actionPoliciesInfo = abiToPoliciesInfo({ + * abi: CounterAbi, + * actionPolicyInfo: { + * contractAddress: testAddresses.Counter, + * sudo: false, + * tokenLimits: [], + * usageLimit: 1000n, + * valueLimit: 1000n + * } + * }) + * @returns An array of ActionPolicyInfo objects */ -export const abi2SudoPolicy = ({ + +export type AbiToPoliciesInfoParams = Omit< + ActionPolicyInfo, + "functionSelector" | "rules" +> & { abi: Abi } + +export const abiToPoliciesInfo = ({ abi, - contractAddress -}: { - abi: Abi - contractAddress: Address -}): SudoPolicyData[] => - abi + ...actionPolicyInfo +}: AbiToPoliciesInfoParams): ResolvedActionPolicyInfo[] => + (abi ?? []) .filter((item): item is AbiFunction => item.type === "function") .map((func) => ({ - contractAddress, - functionSelector: toFunctionSelector(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 16f7479b8..2d8a93b39 100644 --- a/src/sdk/modules/smartSessionsValidator/Types.ts +++ b/src/sdk/modules/smartSessionsValidator/Types.ts @@ -2,7 +2,7 @@ import type { EnableSessionData, SmartSessionMode } from "@rhinestone/module-sdk" -import type { AbiFunction, Address, Hex, OneOf } from "viem" +import type { Abi, AbiFunction, Address, Hex, OneOf } from "viem" import type { AnyReferenceValue } from "../utils/Helpers" import type { Execution } from "../utils/Types" @@ -92,16 +92,9 @@ export type CreateSessionDataParams = OptionalSessionKeyData & { sessionValidAfter?: number /** Chain IDs where the session should be enabled. Useful for enable mode. */ chainIds?: bigint[] -} & OneOf< - | { - /** Array of sudo policy data for the session. */ - sudoPoliciesInfo: SudoPolicyData[] - } - | { - /** Array of action policy data for the session. */ - actionPoliciesInfo: ActionPolicyData[] - } - > + /** Array of action policy data for the session. */ + actionPoliciesInfo: ActionPolicyInfo[] +} export type FullCreateSessionDataParams = { /** Public key for the session. Required for K1 algorithm validators. */ @@ -120,16 +113,16 @@ export type FullCreateSessionDataParams = { sessionValidAfter: number /** Chain IDs where the session should be enabled. Useful for enable mode. */ chainIds?: bigint[] -} & OneOf< - | { - /** Array of sudo policy data for the session. */ - sudoPoliciesInfo: SudoPolicyData[] - } - | { - /** Array of action policy data for the session. */ - actionPoliciesInfo: ActionPolicyData[] - } -> + /** 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 */ @@ -140,20 +133,41 @@ export type SudoPolicyData = { /** * 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 ed361357c..4191e7310 100644 --- a/src/sdk/modules/smartSessionsValidator/decorators/grantPermission.ts +++ b/src/sdk/modules/smartSessionsValidator/decorators/grantPermission.ts @@ -1,27 +1,29 @@ 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, - createSudoData, generateSalt, getPermissionId, toTimeRangePolicy, @@ -29,7 +31,8 @@ import { } from "../Helpers" import type { CreateSessionDataParams, - FullCreateSessionDataParams + FullCreateSessionDataParams, + ResolvedActionPolicyInfo } from "../Types" import type { GrantPermissionActionReturnParams, @@ -79,41 +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[] = [] + const resolvedPolicyInfo2ActionData = ( + actionPolicyInfo: ResolvedActionPolicyInfo + ): ActionData => { + const actionConfig = createActionConfig( + actionPolicyInfo.rules ?? [], + actionPolicyInfo.valueLimit + ) - for (const sudoPolicy of sessionInfo.sudoPoliciesInfo ?? []) { - const sudoPolicyData = createSudoData( - sudoPolicy.contractAddress, - sudoPolicy.functionSelector - ) - actionPolicies.push(sudoPolicyData) + const policyData: PolicyData[] = [] + + // 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 sudo policy here.. + if (actionPolicyInfo.sudo) { + const sudoPolicy = getSudoPolicy() + policyData.push(sudoPolicy) } - 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 - ) + // create value limit policy here.. + if (actionPolicyInfo.valueLimit) { + const valueLimitPolicy = getValueLimitPolicy({ + limit: actionPolicyInfo.valueLimit + }) + policyData.push(valueLimitPolicy) + } - // 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 usage limit policy here.. + if (actionPolicyInfo.usageLimit) { + const usageLimitPolicy = getUsageLimitPolicy({ + limit: actionPolicyInfo.usageLimit + }) + policyData.push(usageLimitPolicy) + } - // Create ActionData - const actionPolicy = createActionData( - actionPolicyInfo.contractAddress, - actionPolicyInfo.functionSelector, - [uniActionPolicyData, timeFramePolicyData] + // create token spending limit policy here.. + if (actionPolicyInfo.tokenLimits?.length) { + const spendingLimitPolicy = getSpendingLimitsPolicy( + actionPolicyInfo.tokenLimits ) + policyData.push(spendingLimitPolicy) + } + + // Create ActionData + const actionPolicy = createActionData( + actionPolicyInfo.contractAddress, + actionPolicyInfo.functionSelector, + policyData + ) + + return actionPolicy + } - actionPolicies.push(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( @@ -135,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) @@ -229,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 @@ -257,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") @@ -273,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/trustAttesters.ts b/src/sdk/modules/smartSessionsValidator/decorators/trustAttesters.ts index fc9393c39..1bc5b0ff6 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/toSmartSessionsValidator.dx.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts index 9f4a0416b..02e5cfd2b 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 { abi2ActionPolicy, 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" @@ -122,15 +122,15 @@ describe("modules.smartSessions.dx", async () => { // sessionValidUntil: number // sessionValidAfter: number // chainIds: bigint[] - actionPoliciesInfo: abi2ActionPolicy({ - abi: CounterAbi, - actionPolicyData: { + actionPoliciesInfo: [ + { + abi: CounterAbi, contractAddress: testAddresses.Counter // validUntil?: number // validAfter?: number // valueLimit?: bigint } - }) + ] } ] }) 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 000000000..531b6b2e1 --- /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 index fa20026c2..ad1120ce3 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.sudo.policy.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.sudo.policy.test.ts @@ -6,11 +6,7 @@ import { type Chain, type Hex, type LocalAccount, - type PublicClient, - encodeFunctionData, - getContract, - slice, - toFunctionSelector + encodeFunctionData } from "viem" import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" import { afterAll, beforeAll, describe, expect, test } from "vitest" @@ -30,8 +26,8 @@ import { createNexusClient } from "../../clients/createNexusClient" import type { Module } from "../utils/Types" -import { abi2SudoPolicy, parse, stringify } from "./Helpers" -import type { CreateSessionDataParams, SessionData } from "./Types" +import { parse, stringify } from "./Helpers" +import type { SessionData } from "./Types" import { smartSessionCreateActions, smartSessionUseActions } from "./decorators" import { toSmartSessionsValidator } from "./toSmartSessionsValidator" @@ -96,6 +92,9 @@ describe("modules.smartSessions.sudo.policy", async () => { 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) @@ -104,14 +103,17 @@ describe("modules.smartSessions.sudo.policy", async () => { const createSessionsResponse = await usersNexusClient.grantPermission({ sessionRequestedInfo: [ { - sessionPublicKey, // session key signer + sessionPublicKey, // sessionValidUntil: number // sessionValidAfter: number // chainIds: bigint[] - sudoPoliciesInfo: abi2SudoPolicy({ - abi: CounterAbi, - contractAddress: testAddresses.Counter - }) + actionPoliciesInfo: [ + { + abi: CounterAbi, + contractAddress: testAddresses.Counter, + sudo: true + } + ] } ] }) diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts index a0726b84c..cdb0d099c 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts @@ -27,7 +27,7 @@ import { import { createNexusSessionClient } from "../../clients/createNexusSessionClient" import { parseReferenceValue } from "../utils/Helpers" import type { Module } from "../utils/Types" -import { abi2ActionPolicy, toUniversalActionPolicy } from "./Helpers" +import { abiToPoliciesInfo, toUniversalActionPolicy } from "./Helpers" import type { CreateSessionDataParams } from "./Types" import { ParamCondition } from "./Types" import { smartSessionCreateActions, smartSessionUseActions } from "./decorators" @@ -86,11 +86,9 @@ describe("modules.smartSessions", async () => { }) test("should convert an ABI to a contract whitelist", async () => { - const contractWhitelist = abi2ActionPolicy({ + const contractWhitelist = abiToPoliciesInfo({ abi: CounterAbi, - actionPolicyData: { - contractAddress: testAddresses.Counter - } + contractAddress: testAddresses.Counter }) expect(contractWhitelist).toBeDefined() @@ -99,19 +97,23 @@ describe("modules.smartSessions", async () => { expect(contractWhitelist).toEqual([ { contractAddress: testAddresses.Counter, - functionSelector: "0x871cc9d4" // decrementNumber + functionSelector: "0x871cc9d4", // decrementNumber + rules: [] }, { contractAddress: testAddresses.Counter, - functionSelector: "0xf2c9ecd8" // getNumber + functionSelector: "0xf2c9ecd8", // getNumber + rules: [] }, { contractAddress: testAddresses.Counter, - functionSelector: "0x273ea3e3" // incrementNumber + functionSelector: "0x273ea3e3", // incrementNumber + rules: [] }, { contractAddress: testAddresses.Counter, - functionSelector: "0x12467434" // revertOperation + functionSelector: "0x12467434", // revertOperation + rules: [] } ]) @@ -219,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.uni.policy.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts index cef0fefc8..6da4c8894 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,8 +33,8 @@ 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" diff --git a/src/test/callDatas.ts b/src/test/callDatas.ts index 75933b364..b5d8d75c1 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 3add7b224..649287c43 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)