From c7dc1dffceb785a41fcfc9e77d28e11698e7aa15 Mon Sep 17 00:00:00 2001 From: livingrockrises <90545960+livingrockrises@users.noreply.github.com> Date: Mon, 20 Nov 2023 07:49:16 +0400 Subject: [PATCH] negative tests mentioned in threat model + bundler integration test --- .../biconomy-verifying-paymaster-v2-specs.ts | 173 +++++++++++++++++- .../biconomy-token-paymaster-specs.ts | 13 +- .../token-paymaster/btpm-undeployed-wallet.ts | 19 +- .../biconomy-verifying-paymaster-v2-specs.ts | 130 ++++++++++--- 4 files changed, 301 insertions(+), 34 deletions(-) diff --git a/test/bundler-integration/sponsorship-paymaster/biconomy-verifying-paymaster-v2-specs.ts b/test/bundler-integration/sponsorship-paymaster/biconomy-verifying-paymaster-v2-specs.ts index b7c2e7c..d3b31ed 100644 --- a/test/bundler-integration/sponsorship-paymaster/biconomy-verifying-paymaster-v2-specs.ts +++ b/test/bundler-integration/sponsorship-paymaster/biconomy-verifying-paymaster-v2-specs.ts @@ -30,7 +30,10 @@ import { } from "@biconomy-devx/account-contracts-v2/dist/types"; import { arrayify, hexConcat, parseEther } from "ethers/lib/utils"; import { BigNumber, BigNumberish, Contract, Signer } from "ethers"; -import { BundlerTestEnvironment } from "../environment/bundlerEnvironment"; +import { + BundlerTestEnvironment, + UserOperationSubmissionError, +} from "../environment/bundlerEnvironment"; export const AddressZero = ethers.constants.AddressZero; @@ -205,7 +208,7 @@ describe("EntryPoint with VerifyingPaymaster Singleton", function () { const userOp1 = await fillAndSign( { sender: walletAddress, - verificationGasLimit: 50000, // for positive case 200k + verificationGasLimit: 200000, // for positive case 200k preVerificationGas: 55000, // min expected by bundler is 46k }, walletOwner, @@ -269,5 +272,171 @@ describe("EntryPoint with VerifyingPaymaster Singleton", function () { ); expect(feeCollectorBalanceAfter).to.be.greaterThan(BigNumber.from(0)); }); + + it("fails if verificationGasLimit is not enough", async () => { + const feeCollectorBalanceBefore = + await verifyingSingletonPaymaster.getBalance( + await feeCollector.getAddress() + ); + expect(feeCollectorBalanceBefore).to.be.equal(BigNumber.from(0)); + const signer = await verifyingSingletonPaymaster.verifyingSigner(); + const offchainSignerAddress = await offchainSigner.getAddress(); + expect(signer).to.be.equal(offchainSignerAddress); + + await verifyingSingletonPaymaster.depositFor( + await offchainSigner.getAddress(), + { value: ethers.utils.parseEther("1") } + ); + const userOp1 = await fillAndSign( + { + sender: walletAddress, + verificationGasLimit: 50000, // for positive case 200k + preVerificationGas: 55000, // min expected by bundler is 46k + }, + walletOwner, + entryPoint, + "nonce" + ); + + const hash = await verifyingSingletonPaymaster.getHash( + userOp1, + await offchainSigner.getAddress(), + MOCK_VALID_UNTIL, + MOCK_VALID_AFTER, + dynamicMarkup + ); + const sig = await offchainSigner.signMessage(arrayify(hash)); + const userOp = await fillAndSign( + { + ...userOp1, + paymasterAndData: hexConcat([ + paymasterAddress, + ethers.utils.defaultAbiCoder.encode( + ["address", "uint48", "uint48", "uint32"], + [ + await offchainSigner.getAddress(), + MOCK_VALID_UNTIL, + MOCK_VALID_AFTER, + dynamicMarkup, + ] + ), + sig, + ]), + }, + walletOwner, + entryPoint, + "nonce" + ); + + const signatureWithModuleAddress = ethers.utils.defaultAbiCoder.encode( + ["bytes", "address"], + [userOp.signature, ecdsaModule.address] + ); + userOp.signature = signatureWithModuleAddress; + + console.log("userop VGL ", userOp.verificationGasLimit.toString()); + console.log("userop PVG ", userOp.preVerificationGas.toString()); + + let thrownError: Error | null = null; + + try { + await environment.sendUserOperation(userOp, entryPoint.address); + } catch (e) { + thrownError = e as Error; + } + + const expectedError = new UserOperationSubmissionError( + '{"message":"account validation failed: AA40 over verificationGasLimit' + ); + + expect(thrownError).to.contain(expectedError); + + await expect( + entryPoint.handleOps([userOp], await offchainSigner.getAddress()) + ).to.be.reverted; + }); + + it("fails if preVerificationGas is not enough", async () => { + const feeCollectorBalanceBefore = + await verifyingSingletonPaymaster.getBalance( + await feeCollector.getAddress() + ); + expect(feeCollectorBalanceBefore).to.be.equal(BigNumber.from(0)); + const signer = await verifyingSingletonPaymaster.verifyingSigner(); + const offchainSignerAddress = await offchainSigner.getAddress(); + expect(signer).to.be.equal(offchainSignerAddress); + + await verifyingSingletonPaymaster.depositFor( + await offchainSigner.getAddress(), + { value: ethers.utils.parseEther("1") } + ); + const userOp1 = await fillAndSign( + { + sender: walletAddress, + verificationGasLimit: 200000, // for positive case 200k + // preVerificationGas: 55000, // min expected by bundler is 46k + }, + walletOwner, + entryPoint, + "nonce" + ); + + const hash = await verifyingSingletonPaymaster.getHash( + userOp1, + await offchainSigner.getAddress(), + MOCK_VALID_UNTIL, + MOCK_VALID_AFTER, + dynamicMarkup + ); + const sig = await offchainSigner.signMessage(arrayify(hash)); + const userOp = await fillAndSign( + { + ...userOp1, + paymasterAndData: hexConcat([ + paymasterAddress, + ethers.utils.defaultAbiCoder.encode( + ["address", "uint48", "uint48", "uint32"], + [ + await offchainSigner.getAddress(), + MOCK_VALID_UNTIL, + MOCK_VALID_AFTER, + dynamicMarkup, + ] + ), + sig, + ]), + }, + walletOwner, + entryPoint, + "nonce" + ); + + const signatureWithModuleAddress = ethers.utils.defaultAbiCoder.encode( + ["bytes", "address"], + [userOp.signature, ecdsaModule.address] + ); + userOp.signature = signatureWithModuleAddress; + + console.log("userop VGL ", userOp.verificationGasLimit.toString()); + console.log("userop PVG ", userOp.preVerificationGas.toString()); + + let thrownError: Error | null = null; + + try { + await environment.sendUserOperation(userOp, entryPoint.address); + } catch (e) { + thrownError = e as Error; + } + + const expectedError = new UserOperationSubmissionError( + '{"message":"preVerificationGas too low: expected at least 45916' + ); + + expect(thrownError).to.contain(expectedError); + + // await expect( + // entryPoint.handleOps([userOp], await offchainSigner.getAddress()) + // ).to.be.reverted; + }); }); }); diff --git a/test/bundler-integration/token-paymaster/biconomy-token-paymaster-specs.ts b/test/bundler-integration/token-paymaster/biconomy-token-paymaster-specs.ts index 30557ae..0765756 100644 --- a/test/bundler-integration/token-paymaster/biconomy-token-paymaster-specs.ts +++ b/test/bundler-integration/token-paymaster/biconomy-token-paymaster-specs.ts @@ -236,6 +236,9 @@ describe("Biconomy Token Paymaster (with Bundler)", function () { .connect(deployer) .transfer(walletAddress, ethers.utils.parseEther("100")); + const accountBalBefore = await token.balanceOf(walletAddress); + const feeReceiverBalBefore = await token.balanceOf(paymasterAddress); + const userOp1 = await fillAndSign( { sender: walletAddress, @@ -294,8 +297,14 @@ describe("Biconomy Token Paymaster (with Bundler)", function () { await environment.sendUserOperation(userOp, entryPoint.address); - const ev = await getUserOpEvent(entryPoint); - expect(ev.args.success).to.be.true; + // const ev = await getUserOpEvent(entryPoint); + // expect(ev.args.success).to.be.true; + + const accountBalAfter = await token.balanceOf(walletAddress); + const feeReceiverBalAfter = await token.balanceOf(paymasterAddress); + + expect(accountBalAfter).to.be.lt(accountBalBefore); + expect(feeReceiverBalAfter).to.be.gt(feeReceiverBalBefore); await expect( entryPoint.handleOps([userOp], await offchainSigner.getAddress()) diff --git a/test/bundler-integration/token-paymaster/btpm-undeployed-wallet.ts b/test/bundler-integration/token-paymaster/btpm-undeployed-wallet.ts index 01bf57e..9c60331 100644 --- a/test/bundler-integration/token-paymaster/btpm-undeployed-wallet.ts +++ b/test/bundler-integration/token-paymaster/btpm-undeployed-wallet.ts @@ -228,6 +228,9 @@ describe("Biconomy Token Paymaster (with Bundler)", function () { .connect(deployer) .transfer(walletAddress, ethers.utils.parseEther("100")); + const accountBalBefore = await token.balanceOf(walletAddress); + const feeReceiverBalBefore = await token.balanceOf(paymasterAddress); + const owner = await walletOwner.getAddress(); const AccountFactory = await ethers.getContractFactory( "SmartAccountFactory" @@ -306,10 +309,20 @@ describe("Biconomy Token Paymaster (with Bundler)", function () { userOp.signature = signatureWithModuleAddress; - await environment.sendUserOperation(userOp, entryPoint.address); + const response = await environment.sendUserOperation( + userOp, + entryPoint.address + ); + console.log("response", response); + + // const ev = await getUserOpEvent(entryPoint); + // expect(ev.args.success).to.be.true; + + const accountBalAfter = await token.balanceOf(walletAddress); + const feeReceiverBalAfter = await token.balanceOf(paymasterAddress); - const ev = await getUserOpEvent(entryPoint); - expect(ev.args.success).to.be.true; + expect(accountBalAfter).to.be.lt(accountBalBefore); + expect(feeReceiverBalAfter).to.be.gt(feeReceiverBalBefore); await expect( entryPoint.handleOps([userOp], await offchainSigner.getAddress()) diff --git a/test/sponsorship-paymaster/biconomy-verifying-paymaster-v2-specs.ts b/test/sponsorship-paymaster/biconomy-verifying-paymaster-v2-specs.ts index 70b6465..85bfbef 100644 --- a/test/sponsorship-paymaster/biconomy-verifying-paymaster-v2-specs.ts +++ b/test/sponsorship-paymaster/biconomy-verifying-paymaster-v2-specs.ts @@ -161,33 +161,7 @@ describe("EntryPoint with VerifyingPaymaster Singleton", function () { ); } - describe("#validatePaymasterUserOp", () => { - it("Should parse data properly", async () => { - const paymasterAndData = hexConcat([ - paymasterAddress, - ethers.utils.defaultAbiCoder.encode( - ["address", "uint48", "uint48", "uint32"], - [ - await offchainSigner.getAddress(), - MOCK_VALID_UNTIL, - MOCK_VALID_AFTER, - dynamicMarkup, - ] - ), - "0x" + "00".repeat(65), - ]); - - const res = await verifyingSingletonPaymaster.parsePaymasterAndData( - paymasterAndData - ); - - expect(res.paymasterId).to.equal(await offchainSigner.getAddress()); - expect(res.validUntil).to.equal(ethers.BigNumber.from(MOCK_VALID_UNTIL)); - expect(res.validAfter).to.equal(ethers.BigNumber.from(MOCK_VALID_AFTER)); - expect(res.priceMarkup).to.equal(dynamicMarkup); - expect(res.signature).to.equal("0x" + "00".repeat(65)); - }); - + describe("Verifying paymaster basic positive tests", () => { it("Should Fail when there is no deposit for paymaster id", async () => { const paymasterId = await depositorSigner.getAddress(); console.log("paymaster Id ", paymasterId); @@ -271,4 +245,106 @@ describe("EntryPoint with VerifyingPaymaster Singleton", function () { expect(feeCollectorBalanceAfter).to.be.greaterThan(BigNumber.from(0)); }); }); + + describe("Verifying Paymaster - read methods and state checks", () => { + it("Should parse data properly", async () => { + const paymasterAndData = hexConcat([ + paymasterAddress, + ethers.utils.defaultAbiCoder.encode( + ["address", "uint48", "uint48", "uint32"], + [ + await offchainSigner.getAddress(), + MOCK_VALID_UNTIL, + MOCK_VALID_AFTER, + dynamicMarkup, + ] + ), + "0x" + "00".repeat(65), + ]); + + const res = await verifyingSingletonPaymaster.parsePaymasterAndData( + paymasterAndData + ); + + expect(res.paymasterId).to.equal(await offchainSigner.getAddress()); + expect(res.validUntil).to.equal(ethers.BigNumber.from(MOCK_VALID_UNTIL)); + expect(res.validAfter).to.equal(ethers.BigNumber.from(MOCK_VALID_AFTER)); + expect(res.priceMarkup).to.equal(dynamicMarkup); + expect(res.signature).to.equal("0x" + "00".repeat(65)); + }); + + it("Invalid paymasterAndData causes revert", async () => { + const paymasterAndData = + "0x9c145aed00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000124468721a7000000000000000000000000831153c6b9537d0ff5b7db830c2749de3042e77600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000649b890b7000000000000000000000000071bc333f52a7971dde5de5b6c1ab0e7341e9724c00000000000000000000000021a6f9fa7246de45762e6f9db1f55e3c0f8566db00000000000000000000000000000000000000000000000000000000000000"; + + await expect( + verifyingSingletonPaymaster.parsePaymasterAndData(paymasterAndData) + ).to.be.reverted; + }); + + it("should check the correct states set on the paymaster", async () => { + const owner = await verifyingSingletonPaymaster.owner(); + + const verifyingSigner = + await verifyingSingletonPaymaster.verifyingSigner(); + + const feeReceiver = await verifyingSingletonPaymaster.feeCollector(); + + expect(owner).to.be.equal(deployer.address); + expect(verifyingSigner).to.be.equal(offchainSigner.address); + expect(feeReceiver).to.be.equal(feeCollector.address); + }); + }); + + describe("Verifying Paymaster - deposit and withdraw tests", () => { + it("Deposits into the specified address", async () => { + const paymasterId = await depositorSigner.getAddress(); + + await verifyingSingletonPaymaster.depositFor(paymasterId, { + value: parseEther("1"), + }); + + const balance = await verifyingSingletonPaymaster.getBalance(paymasterId); + expect(balance).to.be.equal(parseEther("1")); + }); + + it("Does not allow 0 value deposits", async () => { + const paymasterId = await depositorSigner.getAddress(); + + await expect( + verifyingSingletonPaymaster.depositFor(paymasterId, { + value: parseEther("0"), + }) + ).to.be.revertedWithCustomError( + verifyingSingletonPaymaster, + "DepositCanNotBeZero" + ); + }); + + it("Does not allow deposits for 0 address paymasterId", async () => { + const paymasterId = ethers.constants.AddressZero; + + await expect( + verifyingSingletonPaymaster.depositFor(paymasterId, { + value: parseEther("0.5"), + }) + ).to.be.revertedWithCustomError( + verifyingSingletonPaymaster, + "PaymasterIdCannotBeZero" + ); + }); + + it("Reverts when paymasterIdBalance is not enough", async () => { + const paymasterId = await depositorSigner.getAddress(); + + await expect( + verifyingSingletonPaymaster + .connect(depositorSigner) + .withdrawTo(paymasterId, parseEther("1.1")) + ).to.be.revertedWithCustomError( + verifyingSingletonPaymaster, + "InsufficientBalance" + ); + }); + }); });