diff --git a/contracts/interfaces/IBiconomySponsorshipPaymaster.sol b/contracts/interfaces/IBiconomySponsorshipPaymaster.sol index 0f2cfac..00a4c39 100644 --- a/contracts/interfaces/IBiconomySponsorshipPaymaster.sol +++ b/contracts/interfaces/IBiconomySponsorshipPaymaster.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.26; interface IBiconomySponsorshipPaymaster { - event PostopCostChanged(uint256 indexed oldValue, uint256 indexed newValue); + event UnaccountedGasChanged(uint256 indexed oldValue, uint256 indexed newValue); event FixedDynamicAdjustmentChanged(uint32 indexed oldValue, uint32 indexed newValue); event VerifyingSignerChanged(address indexed oldSigner, address indexed newSigner, address indexed actor); diff --git a/contracts/sponsorship/SponsorshipPaymasterWithDynamicAdjustment.sol b/contracts/sponsorship/SponsorshipPaymasterWithDynamicAdjustment.sol index a85bcc2..ce689cd 100644 --- a/contracts/sponsorship/SponsorshipPaymasterWithDynamicAdjustment.sol +++ b/contracts/sponsorship/SponsorshipPaymasterWithDynamicAdjustment.sol @@ -51,7 +51,8 @@ contract BiconomySponsorshipPaymaster is address _owner, IEntryPoint _entryPoint, address _verifyingSigner, - address _feeCollector + address _feeCollector, + uint48 _unaccountedGas ) BasePaymaster(_owner, _entryPoint) { @@ -59,9 +60,12 @@ contract BiconomySponsorshipPaymaster is revert VerifyingSignerCanNotBeZero(); } else if (_feeCollector == address(0)) { revert FeeCollectorCanNotBeZero(); + } else if (_unaccountedGas > 200_000) { + revert UnaccountedGasTooHigh(); } verifyingSigner = _verifyingSigner; feeCollector = _feeCollector; + unaccountedGas = _unaccountedGas; } receive() external payable { @@ -119,13 +123,13 @@ contract BiconomySponsorshipPaymaster is * @param value The new value to be set as the unaccountedEPGasOverhead. * @notice only to be called by the owner of the contract. */ - function setPostopCost(uint48 value) external payable onlyOwner { + function setUnaccountedGas(uint48 value) external payable onlyOwner { if (value > 200_000) { revert UnaccountedGasTooHigh(); } uint256 oldValue = unaccountedGas; unaccountedGas = value; - emit PostopCostChanged(oldValue, value); + emit UnaccountedGasChanged(oldValue, value); } /** @@ -254,18 +258,21 @@ contract BiconomySponsorshipPaymaster is (address paymasterId, uint32 dynamicAdjustment, bytes32 userOpHash) = abi.decode(context, (address, uint32, bytes32)); - uint256 totalGasCost = actualGasCost + (unaccountedGas * actualUserOpFeePerGas); - uint256 adjustedGasCost = (totalGasCost * dynamicAdjustment) / PRICE_DENOMINATOR; + // Include unaccountedGas since EP doesn't include this in actualGasCost + // unaccountedGas = postOpGas + EP overhead gas + estimated penalty + actualGasCost = actualGasCost + (unaccountedGas * actualUserOpFeePerGas); + // Apply the dynamic adjustment + uint256 adjustedGasCost = (actualGasCost * dynamicAdjustment) / PRICE_DENOMINATOR; // Deduct the adjusted cost paymasterIdBalances[paymasterId] -= adjustedGasCost; if (adjustedGasCost > actualGasCost) { - // Add dynamicAdjustment to fee - uint256 dynamicAdjustment = adjustedGasCost - actualGasCost; - paymasterIdBalances[feeCollector] += dynamicAdjustment; + // Apply dynamicAdjustment to fee collector balance + uint256 premium = adjustedGasCost - actualGasCost; + paymasterIdBalances[feeCollector] += premium; // Review if we should emit adjustedGasCost as well - emit DynamicAdjustmentCollected(paymasterId, dynamicAdjustment); + emit DynamicAdjustmentCollected(paymasterId, premium); } emit GasBalanceDeducted(paymasterId, adjustedGasCost, userOpHash); @@ -292,8 +299,8 @@ contract BiconomySponsorshipPaymaster is override returns (bytes memory context, uint256 validationData) { - (address paymasterId, uint48 validUntil, uint48 validAfter, uint32 dynamicAdjustment, bytes calldata signature) = - parsePaymasterAndData(userOp.paymasterAndData); + (address paymasterId, uint48 validUntil, uint48 validAfter, uint32 dynamicAdjustment, bytes calldata signature) + = parsePaymasterAndData(userOp.paymasterAndData); //ECDSA library supports both 64 and 65-byte long signatures. // we only "require" it here so that the revert reason on invalid signature will be of "VerifyingPaymaster", and // not "ECDSA" @@ -325,8 +332,6 @@ contract BiconomySponsorshipPaymaster is context = abi.encode(paymasterId, dynamicAdjustment, userOpHash); - context = abi.encode(paymasterId, dynamicAdjustment, userOpHash); - //no need for other on-chain validation: entire UserOp should have been checked // by the external service prior to signing it. return (context, _packValidationData(false, validUntil, validAfter)); diff --git a/test/foundry/base/TestBase.sol b/test/foundry/base/TestBase.sol index 82b0f14..1cfdf3c 100644 --- a/test/foundry/base/TestBase.sol +++ b/test/foundry/base/TestBase.sol @@ -8,6 +8,8 @@ import "solady/src/utils/ECDSA.sol"; import { EntryPoint } from "account-abstraction/contracts/core/EntryPoint.sol"; import { IEntryPoint } from "account-abstraction/contracts/interfaces/IEntryPoint.sol"; +import { IAccount } from "account-abstraction/contracts/interfaces/IAccount.sol"; +import { Exec } from "account-abstraction/contracts/utils/Exec.sol"; import { IPaymaster } from "account-abstraction/contracts/interfaces/IPaymaster.sol"; import { PackedUserOperation } from "account-abstraction/contracts/interfaces/PackedUserOperation.sol"; @@ -20,7 +22,8 @@ import { Bootstrap, BootstrapConfig } from "nexus/contracts/utils/Bootstrap.sol" import { CheatCodes } from "nexus/test/foundry/utils/CheatCodes.sol"; import { BaseEventsAndErrors } from "./BaseEventsAndErrors.sol"; -import { BiconomySponsorshipPaymaster } from "../../../contracts/sponsorship/SponsorshipPaymasterWithDynamicAdjustment.sol"; +import { BiconomySponsorshipPaymaster } from + "../../../contracts/sponsorship/SponsorshipPaymasterWithDynamicAdjustment.sol"; abstract contract TestBase is CheatCodes, BaseEventsAndErrors { // ----------------------------------------- @@ -59,9 +62,12 @@ abstract contract TestBase is CheatCodes, BaseEventsAndErrors { BiconomyMetaFactory internal META_FACTORY; MockValidator internal VALIDATOR_MODULE; Nexus internal ACCOUNT_IMPLEMENTATION; - Bootstrap internal BOOTSTRAPPER; + // Used to buffer user op gas limits + // GAS_LIMIT = (ESTIMATED_GAS * GAS_BUFFER_RATIO) / 100 + uint8 private constant GAS_BUFFER_RATIO = 110; + // ----------------------------------------- // Modifiers // ----------------------------------------- @@ -332,23 +338,50 @@ abstract contract TestBase is CheatCodes, BaseEventsAndErrors { assertTrue(res, "Pre-funding account should succeed"); } + function estimateUserOpGasCosts(PackedUserOperation memory userOp) + internal + prankModifier(ENTRYPOINT_ADDRESS) + returns (uint256 verificationGasUsed, uint256 callGasUsed, uint256 verificationGasLimit, uint256 callGasLimit) + { + bytes32 userOpHash = ENTRYPOINT.getUserOpHash(userOp); + verificationGasUsed = gasleft(); + IAccount(userOp.sender).validateUserOp(userOp, userOpHash, 0); + verificationGasUsed = verificationGasUsed - gasleft(); + + callGasUsed = gasleft(); + bool success = Exec.call(userOp.sender, 0, userOp.callData, 3e6); + callGasUsed = callGasUsed - gasleft(); + assert(success); + + verificationGasLimit = (verificationGasUsed * GAS_BUFFER_RATIO) / 100; + callGasLimit = (callGasUsed * GAS_BUFFER_RATIO) / 100; + } + function estimatePaymasterGasCosts( BiconomySponsorshipPaymaster paymaster, PackedUserOperation memory userOp, - bytes32 userOpHash, uint256 requiredPreFund ) internal prankModifier(ENTRYPOINT_ADDRESS) - returns (uint256 validationGasLimit, uint256 postopGasLimit) + returns (uint256 validationGasUsed, uint256 postopGasUsed, uint256 validationGasLimit, uint256 postopGasLimit) { - validationGasLimit = gasleft(); + bytes32 userOpHash = ENTRYPOINT.getUserOpHash(userOp); + // Warm up accounts to get more accurate gas estimations (bytes memory context,) = paymaster.validatePaymasterUserOp(userOp, userOpHash, requiredPreFund); - validationGasLimit = validationGasLimit - gasleft(); + paymaster.postOp(IPaymaster.PostOpMode.opSucceeded, context, 1e12, 3e6); + + // Estimate gas used + validationGasUsed = gasleft(); + (context,) = paymaster.validatePaymasterUserOp(userOp, userOpHash, requiredPreFund); + validationGasUsed = validationGasUsed - gasleft(); - postopGasLimit = gasleft(); + postopGasUsed = gasleft(); paymaster.postOp(IPaymaster.PostOpMode.opSucceeded, context, 1e12, 3e6); - postopGasLimit = postopGasLimit - gasleft(); + postopGasUsed = (postopGasUsed - gasleft()); + + validationGasLimit = (validationGasUsed * GAS_BUFFER_RATIO) / 100; + postopGasLimit = (postopGasUsed * GAS_BUFFER_RATIO) / 100; } function createUserOp( @@ -359,24 +392,30 @@ abstract contract TestBase is CheatCodes, BaseEventsAndErrors { internal returns (PackedUserOperation memory userOp, bytes32 userOpHash) { - // Create userOp with no paymaster gas estimates + // Create userOp with no gas estimates uint48 validUntil = uint48(block.timestamp + 1 days); uint48 validAfter = uint48(block.timestamp); userOp = buildUserOpWithCalldata(sender, "", address(VALIDATOR_MODULE)); - (userOp.paymasterAndData, ) = generateAndSignPaymasterData( + (userOp.paymasterAndData,) = generateAndSignPaymasterData( userOp, PAYMASTER_SIGNER, paymaster, 3e6, 3e6, DAPP_ACCOUNT.addr, validUntil, validAfter, dynamicAdjustment ); userOp.signature = signUserOp(sender, userOp); + (,, uint256 verificationGasLimit, uint256 callGasLimit) = estimateUserOpGasCosts(userOp); // Estimate paymaster gas limits - userOpHash = ENTRYPOINT.getUserOpHash(userOp); - (uint256 validationGasLimit, uint256 postopGasLimit) = - estimatePaymasterGasCosts(paymaster, userOp, userOpHash, 5e4); + (, uint256 postopGasUsed, uint256 validationGasLimit, uint256 postopGasLimit) = + estimatePaymasterGasCosts(paymaster, userOp, 5e4); + + vm.startPrank(paymaster.owner()); + // Set unaccounted gas to be gas used in postop + 1000 for EP overhead and penalty + paymaster.setUnaccountedGas(uint48(postopGasUsed + 1000)); + vm.stopPrank(); // Ammend the userop to have new gas limits and signature - (userOp.paymasterAndData, ) = generateAndSignPaymasterData( + userOp.accountGasLimits = bytes32(abi.encodePacked(uint128(verificationGasLimit), uint128(callGasLimit))); + (userOp.paymasterAndData,) = generateAndSignPaymasterData( userOp, PAYMASTER_SIGNER, paymaster, diff --git a/test/foundry/unit/concrete/TestSponsorshipPaymasterWithPremiumTest.t.sol b/test/foundry/unit/concrete/TestSponsorshipPaymasterWithDynamicAdjustmentTest.t.sol similarity index 82% rename from test/foundry/unit/concrete/TestSponsorshipPaymasterWithPremiumTest.t.sol rename to test/foundry/unit/concrete/TestSponsorshipPaymasterWithDynamicAdjustmentTest.t.sol index 5733cbe..dc7bf64 100644 --- a/test/foundry/unit/concrete/TestSponsorshipPaymasterWithPremiumTest.t.sol +++ b/test/foundry/unit/concrete/TestSponsorshipPaymasterWithDynamicAdjustmentTest.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Unlicensed pragma solidity ^0.8.26; -import { console2 } from "forge-std/src/console2.sol"; import { TestBase } from "../../base/TestBase.sol"; import { IBiconomySponsorshipPaymaster } from "../../../../contracts/interfaces/IBiconomySponsorshipPaymaster.sol"; import { BiconomySponsorshipPaymaster } from @@ -16,29 +15,38 @@ contract TestSponsorshipPaymasterWithDynamicAdjustment is TestBase { setupTestEnvironment(); // Deploy Sponsorship Paymaster bicoPaymaster = new BiconomySponsorshipPaymaster( - PAYMASTER_OWNER.addr, ENTRYPOINT, PAYMASTER_SIGNER.addr, PAYMASTER_FEE_COLLECTOR.addr + PAYMASTER_OWNER.addr, ENTRYPOINT, PAYMASTER_SIGNER.addr, PAYMASTER_FEE_COLLECTOR.addr, 7e3 ); } function test_Deploy() external { BiconomySponsorshipPaymaster testArtifact = new BiconomySponsorshipPaymaster( - PAYMASTER_OWNER.addr, ENTRYPOINT, PAYMASTER_SIGNER.addr, PAYMASTER_FEE_COLLECTOR.addr + PAYMASTER_OWNER.addr, ENTRYPOINT, PAYMASTER_SIGNER.addr, PAYMASTER_FEE_COLLECTOR.addr, 7e3 ); assertEq(testArtifact.owner(), PAYMASTER_OWNER.addr); assertEq(address(testArtifact.entryPoint()), ENTRYPOINT_ADDRESS); assertEq(testArtifact.verifyingSigner(), PAYMASTER_SIGNER.addr); assertEq(testArtifact.feeCollector(), PAYMASTER_FEE_COLLECTOR.addr); - assertEq(testArtifact.unaccountedGas(), 0 wei); + assertEq(testArtifact.unaccountedGas(), 7e3); } function test_RevertIf_DeployWithSignerSetToZero() external { vm.expectRevert(abi.encodeWithSelector(VerifyingSignerCanNotBeZero.selector)); - new BiconomySponsorshipPaymaster(PAYMASTER_OWNER.addr, ENTRYPOINT, address(0), PAYMASTER_FEE_COLLECTOR.addr); + new BiconomySponsorshipPaymaster( + PAYMASTER_OWNER.addr, ENTRYPOINT, address(0), PAYMASTER_FEE_COLLECTOR.addr, 7e3 + ); } function test_RevertIf_DeployWithFeeCollectorSetToZero() external { vm.expectRevert(abi.encodeWithSelector(FeeCollectorCanNotBeZero.selector)); - new BiconomySponsorshipPaymaster(PAYMASTER_OWNER.addr, ENTRYPOINT, PAYMASTER_SIGNER.addr, address(0)); + new BiconomySponsorshipPaymaster(PAYMASTER_OWNER.addr, ENTRYPOINT, PAYMASTER_SIGNER.addr, address(0), 7e3); + } + + function test_RevertIf_DeployWithUnaccountedGasCostTooHigh() external { + vm.expectRevert(abi.encodeWithSelector(UnaccountedGasTooHigh.selector)); + new BiconomySponsorshipPaymaster( + PAYMASTER_OWNER.addr, ENTRYPOINT, PAYMASTER_SIGNER.addr, PAYMASTER_FEE_COLLECTOR.addr, 200_001 + ); } function test_CheckInitialPaymasterState() external view { @@ -46,7 +54,7 @@ contract TestSponsorshipPaymasterWithDynamicAdjustment is TestBase { assertEq(address(bicoPaymaster.entryPoint()), ENTRYPOINT_ADDRESS); assertEq(bicoPaymaster.verifyingSigner(), PAYMASTER_SIGNER.addr); assertEq(bicoPaymaster.feeCollector(), PAYMASTER_FEE_COLLECTOR.addr); - assertEq(bicoPaymaster.unaccountedGas(), 0 wei); + assertEq(bicoPaymaster.unaccountedGas(), 7e3); } function test_OwnershipTransfer() external prankModifier(PAYMASTER_OWNER.addr) { @@ -109,22 +117,22 @@ contract TestSponsorshipPaymasterWithDynamicAdjustment is TestBase { bicoPaymaster.setFeeCollector(DAN_ADDRESS); } - function test_SetPostopCost() external prankModifier(PAYMASTER_OWNER.addr) { - uint48 initialPostopCost = bicoPaymaster.unaccountedGas(); - uint48 newPostopCost = 5000; + function test_SetUnaccountedGas() external prankModifier(PAYMASTER_OWNER.addr) { + uint48 initialUnaccountedGas = bicoPaymaster.unaccountedGas(); + uint48 newUnaccountedGas = 5000; vm.expectEmit(true, true, false, true, address(bicoPaymaster)); - emit IBiconomySponsorshipPaymaster.PostopCostChanged(initialPostopCost, newPostopCost); - bicoPaymaster.setPostopCost(newPostopCost); + emit IBiconomySponsorshipPaymaster.UnaccountedGasChanged(initialUnaccountedGas, newUnaccountedGas); + bicoPaymaster.setUnaccountedGas(newUnaccountedGas); - uint48 resultingPostopCost = bicoPaymaster.unaccountedGas(); - assertEq(resultingPostopCost, newPostopCost); + uint48 resultingUnaccountedGas = bicoPaymaster.unaccountedGas(); + assertEq(resultingUnaccountedGas, newUnaccountedGas); } - function test_RevertIf_SetPostopCostToHigh() external prankModifier(PAYMASTER_OWNER.addr) { - uint48 newPostopCost = 200_001; + function test_RevertIf_SetUnaccountedGasToHigh() external prankModifier(PAYMASTER_OWNER.addr) { + uint48 newUnaccountedGas = 200_001; vm.expectRevert(abi.encodeWithSelector(UnaccountedGasTooHigh.selector)); - bicoPaymaster.setPostopCost(newPostopCost); + bicoPaymaster.setUnaccountedGas(newUnaccountedGas); } function test_DepositFor() external { @@ -182,13 +190,14 @@ contract TestSponsorshipPaymasterWithDynamicAdjustment is TestBase { function test_ValidatePaymasterAndPostOpWithoutDynamicAdjustment() external prankModifier(DAPP_ACCOUNT.addr) { bicoPaymaster.depositFor{ value: 10 ether }(DAPP_ACCOUNT.addr); - // No premoium + // No adjustment uint32 dynamicAdjustment = 1e6; PackedUserOperation[] memory ops = new PackedUserOperation[](1); (PackedUserOperation memory userOp, bytes32 userOpHash) = createUserOp(ALICE, bicoPaymaster, dynamicAdjustment); ops[0] = userOp; + uint256 initialBundlerBalance = BUNDLER.addr.balance; uint256 initialDappPaymasterBalance = bicoPaymaster.getBalance(DAPP_ACCOUNT.addr); uint256 initialFeeCollectorBalance = bicoPaymaster.getBalance(PAYMASTER_FEE_COLLECTOR.addr); @@ -196,11 +205,22 @@ contract TestSponsorshipPaymasterWithDynamicAdjustment is TestBase { emit IBiconomySponsorshipPaymaster.GasBalanceDeducted(DAPP_ACCOUNT.addr, 0, userOpHash); ENTRYPOINT.handleOps(ops, payable(BUNDLER.addr)); + uint256 resultingBundlerBalance = BUNDLER.addr.balance; + uint256 resultingDappPaymasterBalance = bicoPaymaster.getBalance(DAPP_ACCOUNT.addr); + + uint256 gasPaidByDapp = initialDappPaymasterBalance - resultingDappPaymasterBalance; + uint256 totalGasFeePaid = resultingBundlerBalance - initialBundlerBalance; (uint256 expectedDynamicAdjustment, uint256 actualDynamicAdjustment) = getDynamicAdjustments( bicoPaymaster, initialDappPaymasterBalance, initialFeeCollectorBalance, dynamicAdjustment ); + // Assert that adjustment collected (if any) is correct assertEq(expectedDynamicAdjustment, actualDynamicAdjustment); + // Gas paid by dapp is higher than paymaster + // Guarantees that EP always has sufficient deposit to pay back dapps + assertGt(gasPaidByDapp, totalGasFeePaid); + // Ensure that max 1% difference between total gas paid and gas paid by dapp (from paymaster) + assertApproxEqRel(totalGasFeePaid + actualDynamicAdjustment, gasPaidByDapp, 0.01e18); } function test_ValidatePaymasterAndPostOpWithDynamicAdjustment() external { @@ -212,6 +232,7 @@ contract TestSponsorshipPaymasterWithDynamicAdjustment is TestBase { (PackedUserOperation memory userOp, bytes32 userOpHash) = createUserOp(ALICE, bicoPaymaster, dynamicAdjustment); ops[0] = userOp; + uint256 initialBundlerBalance = BUNDLER.addr.balance; uint256 initialDappPaymasterBalance = bicoPaymaster.getBalance(DAPP_ACCOUNT.addr); uint256 initialFeeCollectorBalance = bicoPaymaster.getBalance(PAYMASTER_FEE_COLLECTOR.addr); @@ -222,11 +243,23 @@ contract TestSponsorshipPaymasterWithDynamicAdjustment is TestBase { emit IBiconomySponsorshipPaymaster.GasBalanceDeducted(DAPP_ACCOUNT.addr, 0, userOpHash); ENTRYPOINT.handleOps(ops, payable(BUNDLER.addr)); + uint256 resultingBundlerBalance = BUNDLER.addr.balance; + uint256 resultingDappPaymasterBalance = bicoPaymaster.getBalance(DAPP_ACCOUNT.addr); + + uint256 gasPaidByDapp = initialDappPaymasterBalance - resultingDappPaymasterBalance; + uint256 totalGasFeePaid = resultingBundlerBalance - initialBundlerBalance; (uint256 expectedDynamicAdjustment, uint256 actualDynamicAdjustment) = getDynamicAdjustments( bicoPaymaster, initialDappPaymasterBalance, initialFeeCollectorBalance, dynamicAdjustment ); + // Assert that adjustment collected (if any) is correct assertEq(expectedDynamicAdjustment, actualDynamicAdjustment); + // Gas paid by dapp is higher than paymaster + // Guarantees that EP always has sufficient deposit to pay back dapps + assertGt(gasPaidByDapp, totalGasFeePaid); + // Ensure that max 1% difference between total gas paid + the adjustment premium and gas paid by dapp (from + // paymaster) + assertApproxEqRel(totalGasFeePaid + actualDynamicAdjustment, gasPaidByDapp, 0.01e18); } function test_RevertIf_ValidatePaymasterUserOpWithIncorrectSignatureLength() external { @@ -236,7 +269,7 @@ contract TestSponsorshipPaymasterWithDynamicAdjustment is TestBase { uint48 validAfter = uint48(block.timestamp); PackedUserOperation memory userOp = buildUserOpWithCalldata(ALICE, "", address(VALIDATOR_MODULE)); - (userOp.paymasterAndData, ) = generateAndSignPaymasterData( + (userOp.paymasterAndData,) = generateAndSignPaymasterData( userOp, PAYMASTER_SIGNER, bicoPaymaster, 3e6, 3e6, DAPP_ACCOUNT.addr, validUntil, validAfter, 1e6 ); userOp.paymasterAndData = excludeLastNBytes(userOp.paymasterAndData, 2); @@ -255,7 +288,7 @@ contract TestSponsorshipPaymasterWithDynamicAdjustment is TestBase { uint48 validAfter = uint48(block.timestamp); PackedUserOperation memory userOp = buildUserOpWithCalldata(ALICE, "", address(VALIDATOR_MODULE)); - (userOp.paymasterAndData, ) = generateAndSignPaymasterData( + (userOp.paymasterAndData,) = generateAndSignPaymasterData( userOp, PAYMASTER_SIGNER, bicoPaymaster, 3e6, 3e6, DAPP_ACCOUNT.addr, validUntil, validAfter, 1e6 ); userOp.signature = signUserOp(ALICE, userOp); @@ -273,7 +306,7 @@ contract TestSponsorshipPaymasterWithDynamicAdjustment is TestBase { uint48 validAfter = uint48(block.timestamp); PackedUserOperation memory userOp = buildUserOpWithCalldata(ALICE, "", address(VALIDATOR_MODULE)); - (userOp.paymasterAndData, ) = generateAndSignPaymasterData( + (userOp.paymasterAndData,) = generateAndSignPaymasterData( userOp, PAYMASTER_SIGNER, bicoPaymaster, 3e6, 3e6, DAPP_ACCOUNT.addr, validUntil, validAfter, 1e6 ); userOp.signature = signUserOp(ALICE, userOp); diff --git a/test/foundry/unit/fuzz/TestFuzz_TestSponsorshipPaymasterWithPremium.t.sol b/test/foundry/unit/fuzz/TestFuzz_TestSponsorshipPaymasterWithPremium.t.sol index 1567693..a71fccd 100644 --- a/test/foundry/unit/fuzz/TestFuzz_TestSponsorshipPaymasterWithPremium.t.sol +++ b/test/foundry/unit/fuzz/TestFuzz_TestSponsorshipPaymasterWithPremium.t.sol @@ -15,7 +15,7 @@ contract TestFuzz_SponsorshipPaymasterWithDynamicAdjustment is TestBase { setupTestEnvironment(); // Deploy Sponsorship Paymaster bicoPaymaster = new BiconomySponsorshipPaymaster( - PAYMASTER_OWNER.addr, ENTRYPOINT, PAYMASTER_SIGNER.addr, PAYMASTER_FEE_COLLECTOR.addr + PAYMASTER_OWNER.addr, ENTRYPOINT, PAYMASTER_SIGNER.addr, PAYMASTER_FEE_COLLECTOR.addr, 7e3 ); } @@ -108,22 +108,44 @@ contract TestFuzz_SponsorshipPaymasterWithDynamicAdjustment is TestBase { (PackedUserOperation memory userOp, bytes32 userOpHash) = createUserOp(ALICE, bicoPaymaster, dynamicAdjustment); ops[0] = userOp; + uint256 initialBundlerBalance = BUNDLER.addr.balance; uint256 initialFeeCollectorBalance = bicoPaymaster.getBalance(PAYMASTER_FEE_COLLECTOR.addr); uint256 initialDappPaymasterBalance = bicoPaymaster.getBalance(DAPP_ACCOUNT.addr); + vm.expectEmit(true, false, false, true, address(bicoPaymaster)); emit IBiconomySponsorshipPaymaster.DynamicAdjustmentCollected(DAPP_ACCOUNT.addr, 0); vm.expectEmit(true, false, true, true, address(bicoPaymaster)); emit IBiconomySponsorshipPaymaster.GasBalanceDeducted(DAPP_ACCOUNT.addr, 0, userOpHash); ENTRYPOINT.handleOps(ops, payable(BUNDLER.addr)); + uint256 resultingBundlerBalance = BUNDLER.addr.balance; + uint256 resultingDappPaymasterBalance = bicoPaymaster.getBalance(DAPP_ACCOUNT.addr); + + uint256 gasPaidByDapp = initialDappPaymasterBalance - resultingDappPaymasterBalance; + uint256 totalGasFeePaid = resultingBundlerBalance - initialBundlerBalance; (uint256 expectedDynamicAdjustment, uint256 actualDynamicAdjustment) = getDynamicAdjustments( bicoPaymaster, initialDappPaymasterBalance, initialFeeCollectorBalance, dynamicAdjustment ); + // Assert that adjustment collected (if any) is correct assertEq(expectedDynamicAdjustment, actualDynamicAdjustment); + // Gas paid by dapp is higher than paymaster + // Guarantees that EP always has sufficient deposit to pay back dapps + assertGt(gasPaidByDapp, totalGasFeePaid); + // Ensure that max 1% difference between total gas paid + the adjustment premium and gas paid by dapp (from + // paymaster) + assertApproxEqRel(totalGasFeePaid + actualDynamicAdjustment, gasPaidByDapp, 0.01e18); } - function testFuzz_ParsePaymasterAndData(address paymasterId, uint48 validUntil, uint48 validAfter, uint32 dynamicAdjustment) external view { + function testFuzz_ParsePaymasterAndData( + address paymasterId, + uint48 validUntil, + uint48 validAfter, + uint32 dynamicAdjustment + ) + external + view + { PackedUserOperation memory userOp = buildUserOpWithCalldata(ALICE, "", address(VALIDATOR_MODULE)); (bytes memory paymasterAndData, bytes memory signature) = generateAndSignPaymasterData( userOp, PAYMASTER_SIGNER, bicoPaymaster, 3e6, 3e6, paymasterId, validUntil, validAfter, dynamicAdjustment