From a478815ed19ec6b4d32f6d2e19f8122553658f52 Mon Sep 17 00:00:00 2001 From: Shivaansh Kapoor Date: Sun, 7 Jul 2024 22:06:39 +0400 Subject: [PATCH] more tests for accounting for premiums --- .../SponsorshipPaymasterWithPremium.sol | 14 +--- foundry.toml | 5 +- test/foundry/base/NexusTestBase.sol | 2 +- ...tSponsorshipPaymasterWithPremiumTest.t.sol | 66 +++++++++--------- ..._TestSponsorshipPaymasterWithPremium.t.sol | 67 ++++++++++++++++++- 5 files changed, 103 insertions(+), 51 deletions(-) diff --git a/contracts/sponsorship/SponsorshipPaymasterWithPremium.sol b/contracts/sponsorship/SponsorshipPaymasterWithPremium.sol index 5c78e91..5b06711 100644 --- a/contracts/sponsorship/SponsorshipPaymasterWithPremium.sol +++ b/contracts/sponsorship/SponsorshipPaymasterWithPremium.sol @@ -232,15 +232,7 @@ contract BiconomySponsorshipPaymaster is /// @dev This function is called after a user operation has been executed or reverted. /// @param context The context containing the token amount and user sender address. /// @param actualGasCost The actual gas cost of the transaction. - function _postOp( - PostOpMode, - bytes calldata context, - uint256 actualGasCost, - uint256 - ) - internal - override - { + function _postOp(PostOpMode, bytes calldata context, uint256 actualGasCost, uint256) internal override { unchecked { (address paymasterId, uint32 dynamicMarkup, bytes32 userOpHash) = abi.decode(context, (address, uint32, bytes32)); @@ -250,7 +242,7 @@ contract BiconomySponsorshipPaymaster is // deduct with premium paymasterIdBalances[paymasterId] -= costIncludingPremium; - if (actualGasCost < costIncludingPremium) { + if (costIncludingPremium > actualGasCost) { // "collect" premium uint256 actualPremium = costIncludingPremium - actualGasCost; paymasterIdBalances[feeCollector] += actualPremium; @@ -305,8 +297,6 @@ contract BiconomySponsorshipPaymaster is revert InvalidPriceMarkup(); } - uint256 maxFeePerGas = userOp.unpackMaxFeePerGas(); - // Send 1e6 for No markup // Send between 0 and 1e6 for discount uint256 effectiveCost = (requiredPreFund * priceMarkup) / PRICE_DENOMINATOR; diff --git a/foundry.toml b/foundry.toml index 04c3656..80fe03b 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,7 +5,6 @@ block_timestamp = 1_680_220_800 # March 31, 2023 at 00:00 GMT bytecode_hash = "none" evm_version = "paris" # See https://www.evmdiff.com/features?name=PUSH0&kind=opcode - fuzz = { runs = 1_000 } gas_reports = ["*"] optimizer = true optimizer_runs = 1_000_000 @@ -19,6 +18,10 @@ gas_reports_ignore = ["LockTest"] via_ir = true +[fuzz] + runs = 1_000 + max_test_rejects = 1_000_000 + [profile.ci] fuzz = { runs = 10_000 } verbosity = 4 diff --git a/test/foundry/base/NexusTestBase.sol b/test/foundry/base/NexusTestBase.sol index 308bf26..cd7dbaf 100644 --- a/test/foundry/base/NexusTestBase.sol +++ b/test/foundry/base/NexusTestBase.sol @@ -347,7 +347,7 @@ abstract contract NexusTestBase is CheatCodes, BaseEventsAndErrors { validationGasLimit = validationGasLimit - gasleft(); postopGasLimit = gasleft(); - paymaster.postOp(IPaymaster.PostOpMode.opSucceeded, context, 3e4, 2e9); + paymaster.postOp(IPaymaster.PostOpMode.opSucceeded, context, 1e12, 3e6); postopGasLimit = postopGasLimit - gasleft(); } diff --git a/test/foundry/unit/concrete/TestSponsorshipPaymasterWithPremiumTest.t.sol b/test/foundry/unit/concrete/TestSponsorshipPaymasterWithPremiumTest.t.sol index c4b5113..0f6ed46 100644 --- a/test/foundry/unit/concrete/TestSponsorshipPaymasterWithPremiumTest.t.sol +++ b/test/foundry/unit/concrete/TestSponsorshipPaymasterWithPremiumTest.t.sol @@ -135,31 +135,6 @@ contract TestSponsorshipPaymasterWithPremium is NexusTestBase { } function test_WithdrawTo() external prankModifier(DAPP_ACCOUNT.addr) { - uint256 depositAmount = 10 ether; - bicoPaymaster.depositFor{ value: depositAmount }(DAPP_ACCOUNT.addr); - uint256 danInitialBalance = DAN_ADDRESS.balance; - - vm.expectEmit(true, true, true, true, address(bicoPaymaster)); - emit IBiconomySponsorshipPaymaster.GasWithdrawn(DAPP_ACCOUNT.addr, DAN_ADDRESS, depositAmount); - bicoPaymaster.withdrawTo(payable(DAN_ADDRESS), depositAmount); - - uint256 dappPaymasterBalance = bicoPaymaster.getBalance(DAPP_ACCOUNT.addr); - assertEq(dappPaymasterBalance, 0 ether); - uint256 expectedDanBalance = danInitialBalance + depositAmount; - assertEq(DAN_ADDRESS.balance, expectedDanBalance); - } - - function test_RevertIf_WithdrawToZeroAddress() external prankModifier(DAPP_ACCOUNT.addr) { - vm.expectRevert(abi.encodeWithSelector(CanNotWithdrawToZeroAddress.selector)); - bicoPaymaster.withdrawTo(payable(address(0)), 0 ether); - } - - function test_RevertIf_WithdrawToExceedsBalance() external prankModifier(DAPP_ACCOUNT.addr) { - vm.expectRevert(abi.encodeWithSelector(InsufficientFundsInGasTank.selector)); - bicoPaymaster.withdrawTo(payable(DAN_ADDRESS), 1 ether); - } - - function test_ValidatePaymasterAndPostOpWithoutPremium() external { uint256 initialDappPaymasterBalance = 10 ether; bicoPaymaster.depositFor{ value: initialDappPaymasterBalance }(DAPP_ACCOUNT.addr); @@ -169,14 +144,34 @@ contract TestSponsorshipPaymasterWithPremium is NexusTestBase { uint48 validAfter = uint48(block.timestamp); PackedUserOperation memory userOp = buildUserOpWithCalldata(ALICE, "", address(VALIDATOR_MODULE)); + + // No premium + uint32 premium = 1e6; userOp.paymasterAndData = generateAndSignPaymasterData( - userOp, PAYMASTER_SIGNER, bicoPaymaster, 3e6, 3e6, DAPP_ACCOUNT.addr, validUntil, validAfter, 1e6 + userOp, PAYMASTER_SIGNER, bicoPaymaster, 3e6, 3e6, DAPP_ACCOUNT.addr, validUntil, validAfter, premium ); userOp.signature = signUserOp(ALICE, userOp); + // Estimate paymaster gas limits bytes32 userOpHash = ENTRYPOINT.getUserOpHash(userOp); + (uint256 validationGasLimit, uint256 postopGasLimit) = + estimatePaymasterGasCosts(bicoPaymaster, userOp, userOpHash, 5e4); + // Ammend the userop to have new gas limits and signature + userOp.paymasterAndData = generateAndSignPaymasterData( + userOp, + PAYMASTER_SIGNER, + bicoPaymaster, + uint128(validationGasLimit), + uint128(postopGasLimit), + DAPP_ACCOUNT.addr, + validUntil, + validAfter, + premium + ); + userOp.signature = signUserOp(ALICE, userOp); ops[0] = userOp; + userOpHash = ENTRYPOINT.getUserOpHash(userOp); vm.expectEmit(true, false, true, true, address(bicoPaymaster)); emit IBiconomySponsorshipPaymaster.GasBalanceDeducted(DAPP_ACCOUNT.addr, 0, userOpHash); @@ -189,7 +184,6 @@ contract TestSponsorshipPaymasterWithPremium is NexusTestBase { function test_ValidatePaymasterAndPostOpWithPremium() external { uint256 initialDappPaymasterBalance = 10 ether; bicoPaymaster.depositFor{ value: initialDappPaymasterBalance }(DAPP_ACCOUNT.addr); - uint256 initialBundlerBalance = BUNDLER.addr.balance; PackedUserOperation[] memory ops = new PackedUserOperation[](1); @@ -226,6 +220,8 @@ contract TestSponsorshipPaymasterWithPremium is NexusTestBase { ops[0] = userOp; userOpHash = ENTRYPOINT.getUserOpHash(userOp); + uint256 initialFeeCollectorBalance = bicoPaymaster.getBalance(PAYMASTER_FEE_COLLECTOR.addr); + initialDappPaymasterBalance = bicoPaymaster.getBalance(DAPP_ACCOUNT.addr); // submit userops vm.expectEmit(true, false, false, true, address(bicoPaymaster)); emit IBiconomySponsorshipPaymaster.PremiumCollected(DAPP_ACCOUNT.addr, 0); @@ -233,18 +229,16 @@ contract TestSponsorshipPaymasterWithPremium is NexusTestBase { emit IBiconomySponsorshipPaymaster.GasBalanceDeducted(DAPP_ACCOUNT.addr, 0, userOpHash); ENTRYPOINT.handleOps(ops, payable(BUNDLER.addr)); - // Check that gas fees ended up in the right wallets uint256 resultingDappPaymasterBalance = bicoPaymaster.getBalance(DAPP_ACCOUNT.addr); uint256 resultingFeeCollectorPaymasterBalance = bicoPaymaster.getBalance(PAYMASTER_FEE_COLLECTOR.addr); - uint256 resultingBundlerBalance = BUNDLER.addr.balance - initialBundlerBalance; + uint256 totalGasFeesCharged = initialDappPaymasterBalance - resultingDappPaymasterBalance; - console2.log(resultingDappPaymasterBalance); - console2.log(resultingFeeCollectorPaymasterBalance); - console2.log(resultingBundlerBalance); - console2.log(totalGasFeesCharged); - - // resultingDappPaymasterBalance - assertEq(resultingFeeCollectorPaymasterBalance, (totalGasFeesCharged * 1e5) / premium); + uint256 premiumCollected = resultingFeeCollectorPaymasterBalance - initialFeeCollectorBalance; + + uint256 expectedGasPayment = totalGasFeesCharged - premiumCollected; + uint256 expectedPremium = expectedGasPayment / 10; + + assertEq(premiumCollected, expectedPremium); } function test_RevertIf_ValidatePaymasterUserOpWithIncorrectSignatureLength() external { diff --git a/test/foundry/unit/fuzz/TestFuzz_TestSponsorshipPaymasterWithPremium.t.sol b/test/foundry/unit/fuzz/TestFuzz_TestSponsorshipPaymasterWithPremium.t.sol index 5fb19db..98fe11d 100644 --- a/test/foundry/unit/fuzz/TestFuzz_TestSponsorshipPaymasterWithPremium.t.sol +++ b/test/foundry/unit/fuzz/TestFuzz_TestSponsorshipPaymasterWithPremium.t.sol @@ -1,11 +1,13 @@ // SPDX-License-Identifier: Unlicensed pragma solidity ^0.8.26; -import { console2 } from "forge-std/src/Console2.sol"; +import { console2 } from "forge-std/src/console2.sol"; +import { stdMath } from "forge-std/src/Test.sol"; import { NexusTestBase } from "../../base/NexusTestBase.sol"; import { IBiconomySponsorshipPaymaster } from "../../../../contracts/interfaces/IBiconomySponsorshipPaymaster.sol"; import { BiconomySponsorshipPaymaster } from "../../../../contracts/sponsorship/SponsorshipPaymasterWithPremium.sol"; import { MockToken } from "./../../../../lib/nexus/contracts/mocks/MockToken.sol"; +import { PackedUserOperation } from "account-abstraction/contracts/interfaces/PackedUserOperation.sol"; contract TestFuzz_SponsorshipPaymasterWithPremium is NexusTestBase { BiconomySponsorshipPaymaster public bicoPaymaster; @@ -97,4 +99,67 @@ contract TestFuzz_SponsorshipPaymasterWithPremium is NexusTestBase { assertEq(token.balanceOf(address(bicoPaymaster)), 0); assertEq(token.balanceOf(ALICE_ADDRESS), mintAmount); } + + function testFuzz_ValidatePaymasterAndPostOpWithPremium(uint32 premium) external { + vm.assume(premium <= 2e6); + vm.assume(premium > 1e6); + + uint256 initialDappPaymasterBalance = 10 ether; + bicoPaymaster.depositFor{ value: initialDappPaymasterBalance }(DAPP_ACCOUNT.addr); + + PackedUserOperation[] memory ops = new PackedUserOperation[](1); + + uint48 validUntil = uint48(block.timestamp + 1 days); + uint48 validAfter = uint48(block.timestamp); + + PackedUserOperation memory userOp = buildUserOpWithCalldata(ALICE, "", address(VALIDATOR_MODULE)); + + userOp.paymasterAndData = generateAndSignPaymasterData( + userOp, PAYMASTER_SIGNER, bicoPaymaster, 3e6, 3e6, DAPP_ACCOUNT.addr, validUntil, validAfter, premium + ); + userOp.signature = signUserOp(ALICE, userOp); + + // Estimate paymaster gas limits + bytes32 userOpHash = ENTRYPOINT.getUserOpHash(userOp); + (uint256 validationGasLimit, uint256 postopGasLimit) = + estimatePaymasterGasCosts(bicoPaymaster, userOp, userOpHash, 5e4); + + // Ammend the userop to have new gas limits and signature + userOp.paymasterAndData = generateAndSignPaymasterData( + userOp, + PAYMASTER_SIGNER, + bicoPaymaster, + uint128(validationGasLimit), + uint128(postopGasLimit), + DAPP_ACCOUNT.addr, + validUntil, + validAfter, + premium + ); + userOp.signature = signUserOp(ALICE, userOp); + ops[0] = userOp; + userOpHash = ENTRYPOINT.getUserOpHash(userOp); + + uint256 initialFeeCollectorBalance = bicoPaymaster.getBalance(PAYMASTER_FEE_COLLECTOR.addr); + initialDappPaymasterBalance = bicoPaymaster.getBalance(DAPP_ACCOUNT.addr); + vm.expectEmit(true, false, false, true, address(bicoPaymaster)); + emit IBiconomySponsorshipPaymaster.PremiumCollected(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 resultingDappPaymasterBalance = bicoPaymaster.getBalance(DAPP_ACCOUNT.addr); + uint256 resultingFeeCollectorPaymasterBalance = bicoPaymaster.getBalance(PAYMASTER_FEE_COLLECTOR.addr); + + uint256 totalGasFeesCharged = initialDappPaymasterBalance - resultingDappPaymasterBalance; + uint256 premiumCollected = resultingFeeCollectorPaymasterBalance - initialFeeCollectorBalance; + + uint256 expectedPremium = totalGasFeesCharged - (totalGasFeesCharged * 1e6) / premium; + + console2.log(premiumCollected); + console2.log(expectedPremium); + console2.log(stdMath.percentDelta(premiumCollected, expectedPremium)); + // less than 0.01% difference between actual and expected values + assert(stdMath.percentDelta(premiumCollected, expectedPremium) < 1e14); + } }