Skip to content

Commit

Permalink
Merge pull request #394 from bcnmy/DEVX-441/increase-test-coverage
Browse files Browse the repository at this point in the history
Added more tests and checks
  • Loading branch information
livingrockrises authored Feb 25, 2024
2 parents 75c9548 + acac619 commit 70fbb34
Show file tree
Hide file tree
Showing 10 changed files with 408 additions and 54 deletions.
19 changes: 13 additions & 6 deletions packages/account/tests/account.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ describe("Account Tests", () => {
const newBalance = (await checkBalance(publicClient, recipient)) as bigint;

expect(result?.receipt?.transactionHash).toBeTruthy();
expect(newBalance - balance).toBe(1n);
expect(result.success).toBe("true");
expect(newBalance).toBeGreaterThan(balance);
}, 50000);

it("Create a smart account with paymaster with an api key", async () => {
Expand Down Expand Up @@ -280,7 +281,7 @@ describe("Account Tests", () => {
},
});

const selectedFeeQuote = feeQuotesResponse.feeQuotes?.[0]!;
const selectedFeeQuote = feeQuotesResponse.feeQuotes?.[0];
const spender = feeQuotesResponse.tokenPaymasterAddress!;

const contract = getContract({
Expand All @@ -292,12 +293,18 @@ describe("Account Tests", () => {
const allowanceBefore = (await contract.read.allowance([smartAccountAddress, spender])) as bigint;

if (allowanceBefore > 0) {
const setAllowanceToZeroTransaction = await (smartAccount?.paymaster as IHybridPaymaster<any>)?.buildTokenApprovalTransaction({
feeQuote: { ...selectedFeeQuote, maxGasFee: 0 },
spender,
const decreaseAllowanceData = encodeFunctionData({
abi: parseAbi(["function decreaseAllowance(address spender, uint256 subtractedValue)"]),
functionName: "decreaseAllowance",
args: [spender, allowanceBefore],
});

const { wait } = await smartAccount.sendTransaction([setAllowanceToZeroTransaction]);
const decreaseAllowanceTx = {
to: "0xda5289fcaaf71d52a80a254da614a192b693e977",
data: decreaseAllowanceData,
};

const { wait } = await smartAccount.sendTransaction(decreaseAllowanceTx, { paymasterServiceData: { mode: PaymasterMode.SPONSORED } });
const { success } = await wait();

expect(success).toBe("true");
Expand Down
103 changes: 103 additions & 0 deletions packages/account/tests/account.read.e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { TestData } from "../../../tests";
import { createSmartAccountClient } from "../src/index";
import { DEFAULT_ECDSA_OWNERSHIP_MODULE, createECDSAOwnershipValidationModule } from "@biconomy/modules";

describe("Account Tests", () => {
let mumbai: TestData;

beforeEach(() => {
// @ts-ignore: Comes from setup-e2e-tests
[mumbai] = testDataPerChain;
});

it("should check if module is enabled on the smart account", async () => {
const {
whale: { viemWallet: signer },
bundlerUrl,
} = mumbai;

const smartWallet = await createSmartAccountClient({
signer,
bundlerUrl,
});

const isEnabled = await smartWallet.isModuleEnabled(DEFAULT_ECDSA_OWNERSHIP_MODULE);
expect(isEnabled).toBeTruthy();
}, 30000);

it("should get all modules", async () => {
const {
whale: { viemWallet: signer },
bundlerUrl,
} = mumbai;

const smartWallet = await createSmartAccountClient({
signer,
bundlerUrl,
});

const modules = await smartWallet.getAllModules();
expect(modules).toContain("0x000000D50C68705bd6897B2d17c7de32FB519fDA"); // erc20 module
expect(modules).toContain("0x000002FbFfedd9B33F4E7156F2DE8D48945E7489"); // session manager module
expect(modules).toContain("0x0000001c5b32F37F5beA87BDD5374eB2aC54eA8e"); // ecdsa ownership module
}, 30000);

it("should disabled module data", async () => {
const {
whale: { viemWallet: signer },
bundlerUrl,
} = mumbai;

const smartWallet = await createSmartAccountClient({
signer,
bundlerUrl,
});

const disableModuleData = await smartWallet.getDisableModuleData(DEFAULT_ECDSA_OWNERSHIP_MODULE, DEFAULT_ECDSA_OWNERSHIP_MODULE);
expect(disableModuleData).toBeTruthy();
}, 30000);

it("should get setup and enable module data", async () => {
const {
whale: { viemWallet: signer },
bundlerUrl,
} = mumbai;

const smartWallet = await createSmartAccountClient({
signer,
bundlerUrl,
});

const module = await createECDSAOwnershipValidationModule({ signer });
const initData = await module.getInitData();
const setupAndEnableModuleData = await smartWallet.getSetupAndEnableModuleData(DEFAULT_ECDSA_OWNERSHIP_MODULE, initData);
expect(setupAndEnableModuleData).toBeTruthy();
}, 30000);

it("should read estimated user op gas values", async () => {
const {
whale: { viemWallet: signer },
bundlerUrl,
} = mumbai;

const smartWallet = await createSmartAccountClient({
signer,
bundlerUrl,
});

const tx = {
to: "0x000000D50C68705bd6897B2d17c7de32FB519fDA",
data: "0x",
};

const userOp = await smartWallet.buildUserOp([tx]);

const estimatedGas = await smartWallet.estimateUserOpGas(userOp);
expect(estimatedGas.maxFeePerGas).toBeTruthy();
expect(estimatedGas.maxPriorityFeePerGas).toBeTruthy();
expect(estimatedGas.verificationGasLimit).toBeTruthy();
expect(estimatedGas.callGasLimit).toBeTruthy();
expect(estimatedGas.preVerificationGas).toBeTruthy();
expect(estimatedGas).toHaveProperty("paymasterAndData", "0x");
}, 35000);
});
2 changes: 1 addition & 1 deletion packages/common/src/utils/HttpRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export async function sendRequest<T>({ url, method, body }: HttpRequest, service
return jsonResponse as T;
}
if (jsonResponse.error) {
throw new Error(`${jsonResponse.error.message} from ${service}`);
throw new Error(`Error coming from ${service}: ${jsonResponse.error.message}`);
}
if (jsonResponse.message) {
throw new Error(jsonResponse.message);
Expand Down
3 changes: 2 additions & 1 deletion packages/modules/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
"@biconomy/common": "4.0.0",
"@ethersproject/abi": "^5.7.0",
"merkletreejs": "^0.3.11",
"viem": "^2.7.3"
"viem": "^2.7.3",
"@ethersproject/abi": "^5.7.0"
},
"devDependencies": {
"@types/node": "^20.11.10",
Expand Down
215 changes: 215 additions & 0 deletions packages/modules/tests/batchedSessionValidationModule.e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import {
DEFAULT_BATCHED_SESSION_ROUTER_MODULE,
DEFAULT_SESSION_KEY_MANAGER_MODULE,
createBatchedSessionRouterModule,
createSessionKeyManagerModule,
} from "@biconomy/modules";
import { SessionFileStorage } from "./utils/customSession";
import { WalletClientSigner, createSmartAccountClient } from "../../account/src/index";
import { encodeAbiParameters, encodeFunctionData, parseAbi, parseUnits } from "viem";
import { TestData } from "../../../tests";
import { checkBalance } from "../../../tests/utils";
import { PaymasterMode } from "@biconomy/paymaster";

describe("Batched Session Router Tests", () => {
let mumbai: TestData;

beforeEach(() => {
// @ts-ignore: Comes from setup-e2e-tests
[mumbai] = testDataPerChain;
});

// Make sure smart account used for tests has at least 0.01 USDC and some MATIC

it("Should send a user op using Batched Session Validation Module", async () => {
let sessionSigner: WalletClientSigner;

const {
whale: {
account: { address: sessionKeyEOA },
privateKey: pvKey,
viemWallet,
},
minnow: { publicAddress: recipient },
publicClient,
bundlerUrl,
biconomyPaymasterApiKey,
} = mumbai;

// Create smart account
let smartAccount = await createSmartAccountClient({
signer: viemWallet,
bundlerUrl,
biconomyPaymasterApiKey,
index: 3, // Increasing index to not conflict with other test cases and use a new smart account
});

const sessionFileStorage: SessionFileStorage = new SessionFileStorage(await smartAccount.getAddress());

try {
sessionSigner = await sessionFileStorage.getSignerByKey(sessionKeyEOA);
} catch (error) {
sessionSigner = await sessionFileStorage.addSigner({ pbKey: sessionKeyEOA, pvKey });
}

expect(sessionSigner).toBeTruthy();

const smartAccountAddress = await smartAccount.getAddress();
console.log("Smart Account Address: ", smartAccountAddress);

// First we need to check if smart account is deployed
// if not deployed, send an empty transaction to deploy it
const isDeployed = await smartAccount.isAccountDeployed();
if (!isDeployed) {
const emptyTx = {
to: smartAccountAddress,
data: "0x",
};
const userOpResponse = await smartAccount.sendTransaction(emptyTx, { paymasterServiceData: { mode: PaymasterMode.SPONSORED } });
await userOpResponse.wait();
}

// Create session module
const sessionModule = await createSessionKeyManagerModule({
moduleAddress: DEFAULT_SESSION_KEY_MANAGER_MODULE,
smartAccountAddress: await smartAccount.getAddress(),
sessionStorageClient: sessionFileStorage,
});

// Create batched session module
const batchedSessionModule = await createBatchedSessionRouterModule({
moduleAddress: DEFAULT_BATCHED_SESSION_ROUTER_MODULE,
smartAccountAddress: await smartAccount.getAddress(),
sessionKeyManagerModule: sessionModule,
});

// Set enabled call on session, only allows calling USDC contract transfer with <= 10 USDC
const sessionKeyData = encodeAbiParameters(
[{ type: "address" }, { type: "address" }, { type: "address" }, { type: "uint256" }],
[
sessionKeyEOA,
"0xdA5289fCAAF71d52a80A254da614a192b693e977", // erc20 token address
recipient, // receiver address
parseUnits("10", 6),
],
);

// only requires that the caller is the session key
// can call anything using the mock session module
const sessionKeyData2 = encodeAbiParameters([{ type: "address" }], [sessionKeyEOA]);

const erc20ModuleAddr = "0x000000D50C68705bd6897B2d17c7de32FB519fDA";
const mockSessionModuleAddr = "0x7Ba4a7338D7A90dfA465cF975Cc6691812C3772E";

const sessionTxData = await batchedSessionModule.createSessionData([
{
validUntil: 0,
validAfter: 0,
sessionValidationModule: erc20ModuleAddr,
sessionPublicKey: sessionKeyEOA,
sessionKeyData: sessionKeyData,
},
{
validUntil: 0,
validAfter: 0,
sessionValidationModule: mockSessionModuleAddr,
sessionPublicKey: sessionKeyEOA,
sessionKeyData: sessionKeyData2,
},
]);

const setSessionAllowedTrx = {
to: DEFAULT_SESSION_KEY_MANAGER_MODULE,
data: sessionTxData.data,
};

const txArray: any = [];

// Check if session module is enabled
const isEnabled = await smartAccount.isModuleEnabled(DEFAULT_SESSION_KEY_MANAGER_MODULE);
if (!isEnabled) {
const enableModuleTrx = await smartAccount.getEnableModuleData(DEFAULT_SESSION_KEY_MANAGER_MODULE);
txArray.push(enableModuleTrx);
}

// Check if batched session module is enabled
const isBRMenabled = await smartAccount.isModuleEnabled(DEFAULT_BATCHED_SESSION_ROUTER_MODULE);
if (!isBRMenabled) {
// -----> enableModule batched session router module
const tx2 = await smartAccount.getEnableModuleData(DEFAULT_BATCHED_SESSION_ROUTER_MODULE);
txArray.push(tx2);
}

txArray.push(setSessionAllowedTrx);

const userOpResponse1 = await smartAccount.sendTransaction(txArray, { paymasterServiceData: { mode: PaymasterMode.SPONSORED } }); // this user op will enable the modules and setup session allowed calls
const transactionDetails = await userOpResponse1.wait();
console.log("Tx Hash: ", transactionDetails.receipt.transactionHash);

const usdcBalance = await checkBalance(publicClient, await smartAccount.getAccountAddress(), "0xdA5289fCAAF71d52a80A254da614a192b693e977");
expect(usdcBalance).toBeGreaterThan(0);

smartAccount = smartAccount.setActiveValidationModule(batchedSessionModule);

// WARNING* If the smart account does not have enough USDC, user op execution will FAIL
const encodedCall = encodeFunctionData({
abi: parseAbi(["function transfer(address _to, uint256 _value)"]),
functionName: "transfer",
args: [recipient, parseUnits("0.01", 6)],
});

const encodedCall2 = encodeFunctionData({
abi: parseAbi(["function transfer(address _to, uint256 _value)"]),
functionName: "transfer",
args: ["0xd3C85Fdd3695Aee3f0A12B3376aCD8DC54020549", parseUnits("0.01", 6)],
});

const transferTx = {
to: "0xdA5289fCAAF71d52a80A254da614a192b693e977",
data: encodedCall,
};

const transferTx2 = {
to: "0xdA5289fCAAF71d52a80A254da614a192b693e977",
data: encodedCall2,
};

const activeModule = smartAccount.activeValidationModule;
expect(activeModule).toEqual(batchedSessionModule);

const maticBalanceBefore = await checkBalance(publicClient, await smartAccount.getAccountAddress());

// failing with dummyTx because of invalid sessionKeyData
const userOpResponse2 = await smartAccount.sendTransaction([transferTx, transferTx2], {
params: {
batchSessionParams: [
{
sessionSigner: sessionSigner,
sessionValidationModule: erc20ModuleAddr,
},
{
sessionSigner: sessionSigner,
sessionValidationModule: mockSessionModuleAddr,
},
],
},
paymasterServiceData: {
mode: PaymasterMode.SPONSORED,
},
});


const receipt = await userOpResponse2.wait();

expect(receipt.success).toBe("true");

expect(userOpResponse2.userOpHash).toBeTruthy();
expect(userOpResponse2.userOpHash).not.toBeNull();

const maticBalanceAfter = await checkBalance(publicClient, await smartAccount.getAccountAddress());

expect(maticBalanceAfter).toEqual(maticBalanceBefore);

console.log(`Tx at: https://jiffyscan.xyz/userOpHash/${userOpResponse2.userOpHash}?network=mumbai`);
}, 60000);
});
4 changes: 2 additions & 2 deletions packages/modules/tests/modules.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe("Account Tests", () => {
expect(address).toBeTruthy();
// expect the relevant module to be set
expect(smartAccount.activeValidationModule).toEqual(defaultValidationModule);
});
}, 50000);

it("should create a ECDSAOwnershipValidationModule from a viem signer using convertSigner", async () => {
const {
Expand All @@ -41,5 +41,5 @@ describe("Account Tests", () => {
expect(address).toBeTruthy();
// expect the relevant module to be set
expect(smartAccount.activeValidationModule).toEqual(defaultValidationModule);
});
}, 50000);
});
Loading

0 comments on commit 70fbb34

Please sign in to comment.