diff --git a/contracts/common/BiconomyTokenPaymasterErrors.sol b/contracts/common/BiconomyTokenPaymasterErrors.sol index 274999b..37602f5 100644 --- a/contracts/common/BiconomyTokenPaymasterErrors.sol +++ b/contracts/common/BiconomyTokenPaymasterErrors.sol @@ -81,8 +81,9 @@ contract BiconomyTokenPaymasterErrors { */ error WithdrawalFailed(); + /** - * @notice Emitted when ETH is withdrawn from the paymaster + * @notice Throws when PM was not able to charge user */ - event EthWithdrawn(address indexed recipient, uint256 indexed amount); + error FailedToChargeTokens(address account, address token, uint256 amount, bytes32 userOpHash); } diff --git a/contracts/interfaces/IBiconomyTokenPaymaster.sol b/contracts/interfaces/IBiconomyTokenPaymaster.sol index 9fc43e8..841d794 100644 --- a/contracts/interfaces/IBiconomyTokenPaymaster.sol +++ b/contracts/interfaces/IBiconomyTokenPaymaster.sol @@ -8,7 +8,6 @@ interface IBiconomyTokenPaymaster { enum PaymasterMode { EXTERNAL, // Price provided by external service. Authenticated using signature from verifyingSigner INDEPENDENT // Price queried from oracle. No signature needed from external service. - } // Struct for storing information about the token @@ -23,18 +22,19 @@ interface IBiconomyTokenPaymaster { event UpdatedVerifyingSigner(address indexed oldSigner, address indexed newSigner, address indexed actor); event UpdatedFeeCollector(address indexed oldFeeCollector, address indexed newFeeCollector, address indexed actor); event UpdatedPriceExpiryDuration(uint256 indexed oldValue, uint256 indexed newValue); - event TokensRefunded( - address indexed userOpSender, address indexed token, uint256 refundAmount, bytes32 indexed userOpHash - ); + event PaidGasInTokens( address indexed userOpSender, address indexed token, - uint256 nativeCharge, + uint256 gasCostBeforePostOpAndPenalty, uint256 tokenCharge, uint32 priceMarkup, uint256 tokenPrice, - bytes32 indexed userOpHash + bytes32 userOpHash ); + + event EthWithdrawn(address indexed recipient, uint256 indexed amount); + event Received(address indexed sender, uint256 value); event TokensWithdrawn(address indexed token, address indexed to, uint256 indexed amount, address actor); event AddedToTokenDirectory(address indexed tokenAddress, IOracle indexed oracle, uint8 decimals); diff --git a/contracts/libraries/TokenPaymasterParserLib.sol b/contracts/libraries/TokenPaymasterParserLib.sol index aaae02a..31a1ab0 100644 --- a/contracts/libraries/TokenPaymasterParserLib.sol +++ b/contracts/libraries/TokenPaymasterParserLib.sol @@ -32,7 +32,7 @@ library TokenPaymasterParserLib { uint48 validAfter, address tokenAddress, uint256 tokenPrice, - uint32 externalPriceMarkup, + uint32 appliedPriceMarkup, bytes calldata signature ) { @@ -40,7 +40,7 @@ library TokenPaymasterParserLib { validAfter = uint48(bytes6(modeSpecificData[6:12])); tokenAddress = address(bytes20(modeSpecificData[12:32])); tokenPrice = uint256(bytes32(modeSpecificData[32:64])); - externalPriceMarkup = uint32(bytes4(modeSpecificData[64:68])); + appliedPriceMarkup = uint32(bytes4(modeSpecificData[64:68])); signature = modeSpecificData[68:]; } diff --git a/contracts/token/BiconomyTokenPaymaster.sol b/contracts/token/BiconomyTokenPaymaster.sol index eb868ad..6a37e0e 100644 --- a/contracts/token/BiconomyTokenPaymaster.sol +++ b/contracts/token/BiconomyTokenPaymaster.sol @@ -15,8 +15,8 @@ import { IOracle } from "../interfaces/oracles/IOracle.sol"; import { TokenPaymasterParserLib } from "../libraries/TokenPaymasterParserLib.sol"; import { SignatureCheckerLib } from "solady/utils/SignatureCheckerLib.sol"; import { ECDSA as ECDSA_solady } from "solady/utils/ECDSA.sol"; -import "account-abstraction/core/Helpers.sol"; import { Uniswapper, IV3SwapRouter } from "./swaps/Uniswapper.sol"; +import "account-abstraction/core/Helpers.sol"; /** * @title BiconomyTokenPaymaster @@ -392,7 +392,7 @@ contract BiconomyTokenPaymaster is uint48 validAfter, address tokenAddress, uint256 tokenPrice, - uint32 externalPriceMarkup + uint32 appliedPriceMarkup ) public view @@ -416,7 +416,7 @@ contract BiconomyTokenPaymaster is validAfter, tokenAddress, tokenPrice, - externalPriceMarkup + appliedPriceMarkup ) ); } @@ -479,13 +479,10 @@ contract BiconomyTokenPaymaster is revert InvalidPaymasterMode(); } - // callGasLimit + paymasterPostOpGas uint256 maxPenalty = ( - ( - uint128(uint256(userOp.accountGasLimits)) + ( uint128(uint256(userOp.accountGasLimits)) + uint128(bytes16(userOp.paymasterAndData[_PAYMASTER_POSTOP_GAS_OFFSET:_PAYMASTER_DATA_OFFSET])) - ) * 10 * userOp.unpackMaxFeePerGas() - ) / 100; + ) * 10 ) / 100; if (mode == PaymasterMode.EXTERNAL) { // Use the price and other params specified in modeSpecificData by the verifyingSigner @@ -495,7 +492,7 @@ contract BiconomyTokenPaymaster is uint48 validUntil, uint48 validAfter, address tokenAddress, - uint256 tokenPrice, // Note: what backend should pass is nativeTokenPriceInUsd/tokenPriceInUsd * 10^token decimals + uint256 tokenPrice, uint32 externalPriceMarkup, bytes memory signature ) = modeSpecificData.parseExternalModeSpecificData(); @@ -516,66 +513,33 @@ contract BiconomyTokenPaymaster is return ("", _packValidationData(true, validUntil, validAfter)); } - if (externalPriceMarkup > _MAX_PRICE_MARKUP || externalPriceMarkup < _PRICE_DENOMINATOR) { - revert InvalidPriceMarkup(); - } - - - uint256 tokenAmount; - { - uint256 maxFeePerGas = UserOperationLib.unpackMaxFeePerGas(userOp); - tokenAmount = ((maxCost + maxPenalty + (unaccountedGas * maxFeePerGas)) * externalPriceMarkup * tokenPrice) - / (_NATIVE_TOKEN_DECIMALS * _PRICE_DENOMINATOR); - } - - // Transfer full amount to this address. Unused amount will be refunded in postOP - SafeTransferLib.safeTransferFrom(tokenAddress, userOp.sender, address(this), tokenAmount); - - // deduct max penalty from the token amount we pass to the postOp - // so we don't refund it at postOp context = abi.encode( userOp.sender, tokenAddress, - tokenAmount-((maxPenalty*tokenPrice*externalPriceMarkup)/(_NATIVE_TOKEN_DECIMALS*_PRICE_DENOMINATOR)), + maxPenalty, tokenPrice, externalPriceMarkup, - userOpHash + userOpHash ); validationData = _packValidationData(false, validUntil, validAfter); + + /// INDEPENDENT MODE } else if (mode == PaymasterMode.INDEPENDENT) { + + address tokenAddress = modeSpecificData.parseIndependentModeSpecificData(); + // Use only oracles for the token specified in modeSpecificData if (modeSpecificData.length != 20) { revert InvalidTokenAddress(); } - // Get address for token used to pay - address tokenAddress = modeSpecificData.parseIndependentModeSpecificData(); - uint256 tokenPrice = _getPrice(tokenAddress); - - if(tokenPrice == 0) { - revert TokenNotSupported(); - } - uint256 tokenAmount; - uint32 priceMarkup = independentTokenDirectory[tokenAddress].priceMarkup; - - { - // Calculate token amount to precharge - uint256 maxFeePerGas = UserOperationLib.unpackMaxFeePerGas(userOp); - tokenAmount = ((maxCost + maxPenalty + (unaccountedGas * maxFeePerGas)) * priceMarkup * tokenPrice) - / (_NATIVE_TOKEN_DECIMALS * _PRICE_DENOMINATOR); - } - - // Transfer full amount to this address. Unused amount will be refunded in postOP - SafeTransferLib.safeTransferFrom(tokenAddress, userOp.sender, address(this), tokenAmount); - - context = - abi.encode( - userOp.sender, - tokenAddress, - tokenAmount-((maxPenalty*tokenPrice*priceMarkup)/(_NATIVE_TOKEN_DECIMALS*_PRICE_DENOMINATOR)), - tokenPrice, - priceMarkup, - userOpHash + context = abi.encode( + userOp.sender, + tokenAddress, + maxPenalty, + uint256(0), // pass 0, so we can check the price in _postOp and be 4337 compliant + independentTokenDirectory[tokenAddress].priceMarkup, + userOpHash ); validationData = 0; // Validation success and price is valid indefinetly } @@ -596,31 +560,39 @@ contract BiconomyTokenPaymaster is ) internal override - { - // Decode context data + { ( address userOpSender, address tokenAddress, - uint256 prechargedAmount, + uint256 maxPenalty, uint256 tokenPrice, uint32 appliedPriceMarkup, bytes32 userOpHash ) = abi.decode(context, (address, address, uint256, uint256, uint32, bytes32)); - // Calculate the actual cost in tokens based on the actual gas cost and the token price - uint256 actualTokenAmount = ( - (actualGasCost + (unaccountedGas * actualUserOpFeePerGas)) * appliedPriceMarkup * tokenPrice - ) / (_NATIVE_TOKEN_DECIMALS * _PRICE_DENOMINATOR); - if (prechargedAmount > actualTokenAmount) { - // If the user was overcharged, refund the excess tokens - uint256 refundAmount = prechargedAmount - actualTokenAmount; - SafeTransferLib.safeTransfer(tokenAddress, userOpSender, refundAmount); - emit TokensRefunded(userOpSender, tokenAddress, refundAmount, userOpHash); + // If tokenPrice is 0, it means it was not set in the validatePaymasterUserOp => independent mode + // So we need to get the price of the token from the oracle now + if(tokenPrice == 0) { + tokenPrice = _getPrice(tokenAddress); + // if tokenPrice is still 0, it means the token is not supported + if(tokenPrice == 0) { + revert TokenNotSupported(); + } } - emit PaidGasInTokens( - userOpSender, tokenAddress, actualGasCost, actualTokenAmount, appliedPriceMarkup, tokenPrice, userOpHash - ); + // Calculate the amount to charge. unaccountedGas and maxPenalty are used, + // as we do not know the exact gas spent for postop and actual penalty at this point + // this is obviously overcharge, however, the excess amount can be refunded by backend, + // when we know the exact gas spent (emitted by EP after executing UserOp) + uint256 tokenAmount = ( + (actualGasCost + ((unaccountedGas + maxPenalty)) * actualUserOpFeePerGas)) * appliedPriceMarkup * tokenPrice + / (_NATIVE_TOKEN_DECIMALS * _PRICE_DENOMINATOR); + + if (SafeTransferLib.trySafeTransferFrom(tokenAddress, userOpSender, address(this), tokenAmount)) { + emit PaidGasInTokens(userOpSender, tokenAddress, actualGasCost, tokenAmount, appliedPriceMarkup, tokenPrice, userOpHash); + } else { + revert FailedToChargeTokens(userOpSender, tokenAddress, tokenAmount, userOpHash); + } } function _validateTokenInfo(TokenInfo memory tokenInfo) internal view { diff --git a/test/base/TestBase.sol b/test/base/TestBase.sol index efe7333..671083e 100644 --- a/test/base/TestBase.sol +++ b/test/base/TestBase.sol @@ -14,6 +14,9 @@ import { Exec } from "account-abstraction/utils/Exec.sol"; import { IPaymaster } from "account-abstraction/interfaces/IPaymaster.sol"; import { Nexus } from "@nexus/contracts/Nexus.sol"; +import { INexus } from "@nexus/contracts/interfaces/INexus.sol"; +import { IERC7579Account } from "@nexus/contracts/interfaces/IERC7579Account.sol"; +import { IExecutionHelper } from "@nexus/contracts/interfaces/base/IExecutionHelper.sol"; import { CheatCodes } from "@nexus/test/foundry/utils/CheatCodes.sol"; import { BaseEventsAndErrors } from "./BaseEventsAndErrors.sol"; import { MockToken } from "@nexus/contracts/mocks/MockToken.sol"; @@ -64,7 +67,7 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors { uint48 validAfter; address tokenAddress; uint256 tokenPrice; - uint32 externalPriceMarkup; + uint32 appliedPriceMarkup; } // Used to buffer user op gas limits @@ -343,7 +346,7 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors { pmData.validAfter, pmData.tokenAddress, pmData.tokenPrice, - pmData.externalPriceMarkup, + pmData.appliedPriceMarkup, new bytes(65) // Zero signature ); @@ -352,7 +355,7 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors { // Generate hash to be signed bytes32 paymasterHash = - paymaster.getHash(userOp, pmData.validUntil, pmData.validAfter, pmData.tokenAddress, pmData.tokenPrice, pmData.externalPriceMarkup); + paymaster.getHash(userOp, pmData.validUntil, pmData.validAfter, pmData.tokenAddress, pmData.tokenPrice, pmData.appliedPriceMarkup); // Sign the hash signature = signMessage(signer, paymasterHash); @@ -368,7 +371,7 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors { pmData.validAfter, pmData.tokenAddress, pmData.tokenPrice, - pmData.externalPriceMarkup, + pmData.appliedPriceMarkup, signature ); } @@ -482,10 +485,10 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors { // Assert we never undercharge assertGe(gasPaidBySAInNativeTokens, BUNDLER.addr.balance - initialBundlerBalance); - // Ensure that max 2% difference between what is should have been charged and what was charged + // Ensure that max 3% difference between what should have been charged and what was charged // this difference comes from difference of postop gas and estimated postop gas (paymaster.unaccountedGas) // and from estimation of real penalty which is not emitted by EP :( - assertApproxEqRel((totalGasFeePaid + maxPenalty - realPenalty) * priceMarkup / _PRICE_MARKUP_DENOMINATOR, gasPaidBySAInNativeTokens, 0.02e18, "If this fails, check the test case inline comments"); + assertApproxEqRel((totalGasFeePaid + maxPenalty - realPenalty) * priceMarkup / _PRICE_MARKUP_DENOMINATOR, gasPaidBySAInNativeTokens, 0.03e18, "If this fails, check the test case inline comments"); } function _toSingletonArray(address addr) internal pure returns (address[] memory) { diff --git a/test/mocks/PaymasterParserLibExposed.sol b/test/mocks/PaymasterParserLibExposed.sol index 41207ce..aa29a92 100644 --- a/test/mocks/PaymasterParserLibExposed.sol +++ b/test/mocks/PaymasterParserLibExposed.sol @@ -16,7 +16,7 @@ library PaymasterParserLibExposed { uint48 validAfter, address tokenAddress, uint256 tokenPrice, - uint32 externalPriceMarkup, + uint32 appliedPriceMarkup, bytes calldata signature ) { return modeSpecificData.parseExternalModeSpecificData(); diff --git a/test/mocks/PaymasterParserLibWrapper.sol b/test/mocks/PaymasterParserLibWrapper.sol index 72c3206..e882e86 100644 --- a/test/mocks/PaymasterParserLibWrapper.sol +++ b/test/mocks/PaymasterParserLibWrapper.sol @@ -15,11 +15,11 @@ contract PaymasterParserLibWrapper { uint48 validUntil, uint48 validAfter, address tokenAddress, - uint256 tokenPrice, - uint32 externalPriceMarkup, + uint256 tokenPrice, + uint32 appliedPriceMarkup, bytes memory signature ) { - (validUntil, validAfter, tokenAddress, tokenPrice, externalPriceMarkup, signature) = modeSpecificData.parseExternalModeSpecificData(); + (validUntil, validAfter, tokenAddress, tokenPrice, appliedPriceMarkup, signature) = modeSpecificData.parseExternalModeSpecificData(); } function parseIndependentModeSpecificData(bytes calldata modeSpecificData) external pure returns (address tokenAddress) { diff --git a/test/unit/concrete/TestTokenPaymaster.Base.t.sol b/test/unit/concrete/TestTokenPaymaster.Base.t.sol index e5e6347..95de2af 100644 --- a/test/unit/concrete/TestTokenPaymaster.Base.t.sol +++ b/test/unit/concrete/TestTokenPaymaster.Base.t.sol @@ -81,12 +81,9 @@ contract TestTokenPaymasterBase is TestBase { function test_BaseFork_Success_TokenPaymaster_IndependentMode_WithoutPremium() external { tokenPaymaster.deposit{ value: 10 ether }(); deal(address(usdc), address(ALICE_ACCOUNT), 100e6); - vm.startPrank(address(ALICE_ACCOUNT)); - usdc.approve(address(tokenPaymaster), usdc.balanceOf(address(ALICE_ACCOUNT))); - vm.stopPrank(); vm.startPrank(PAYMASTER_OWNER.addr); - tokenPaymaster.setUnaccountedGas(40_000); + tokenPaymaster.setUnaccountedGas(80_000); vm.stopPrank(); uint256 initialBundlerBalance = BUNDLER.addr.balance; @@ -94,7 +91,11 @@ contract TestTokenPaymasterBase is TestBase { uint256 initialUserTokenBalance = usdc.balanceOf(address(ALICE_ACCOUNT)); uint256 initialPaymasterTokenBalance = usdc.balanceOf(address(tokenPaymaster)); - PackedUserOperation memory userOp = buildUserOpWithCalldata(ALICE, "", address(VALIDATOR_MODULE)); + PackedUserOperation memory userOp = buildUserOpWithCalldata( + ALICE, + abi.encodeWithSelector(IExecutionHelper.execute.selector, bytes32(0), abi.encodePacked(address(usdc), uint256(0), abi.encodeWithSelector(IERC20.approve.selector, address(tokenPaymaster), 1_000*1e18))), + address(VALIDATOR_MODULE) + ); // Encode paymasterAndData for independent mode bytes memory paymasterAndData = abi.encodePacked( @@ -111,9 +112,6 @@ contract TestTokenPaymasterBase is TestBase { PackedUserOperation[] memory ops = new PackedUserOperation[](1); ops[0] = userOp; - vm.expectEmit(true, true, false, false, address(tokenPaymaster)); - emit IBiconomyTokenPaymaster.TokensRefunded(address(ALICE_ACCOUNT), address(usdc), 0, bytes32(0)); - vm.expectEmit(true, true, false, false, address(tokenPaymaster)); emit IBiconomyTokenPaymaster.PaidGasInTokens(address(ALICE_ACCOUNT), address(usdc), 0, 0, 1e6, 0, bytes32(0)); diff --git a/test/unit/concrete/TestTokenPaymaster.t.sol b/test/unit/concrete/TestTokenPaymaster.t.sol index 44a8140..0e25bc1 100644 --- a/test/unit/concrete/TestTokenPaymaster.t.sol +++ b/test/unit/concrete/TestTokenPaymaster.t.sol @@ -309,7 +309,6 @@ contract TestTokenPaymaster is TestBase { vm.stopPrank(); PackedUserOperation memory userOp = buildUserOpWithCalldata(ALICE, "", address(VALIDATOR_MODULE)); - uint128 tokenPrice = 1e18; // Assume 1 token = 1 native token = 1 native? TokenPaymasterData memory pmData = TokenPaymasterData({ paymasterValGasLimit: 3e6, @@ -318,8 +317,8 @@ contract TestTokenPaymaster is TestBase { validUntil: uint48(block.timestamp + 1 days), validAfter: uint48(block.timestamp), tokenAddress: address(testToken), - tokenPrice: tokenPrice, - externalPriceMarkup: 1e6 + tokenPrice: 1e18, + appliedPriceMarkup: 1e6 }); (bytes memory paymasterAndData,) = generateAndSignTokenPaymasterData( @@ -354,7 +353,6 @@ contract TestTokenPaymaster is TestBase { vm.stopPrank(); PackedUserOperation memory userOp = buildUserOpWithCalldata(ALICE, "", address(VALIDATOR_MODULE)); - uint128 tokenPrice = 1e18; TokenPaymasterData memory pmData = TokenPaymasterData({ paymasterValGasLimit: 3e6, @@ -363,8 +361,8 @@ contract TestTokenPaymaster is TestBase { validUntil: uint48(block.timestamp + 1 days), validAfter: uint48(block.timestamp), tokenAddress: address(testToken), - tokenPrice: tokenPrice, - externalPriceMarkup: 1e6 + tokenPrice: 1e18, + appliedPriceMarkup: 1e6 }); // Create a valid paymasterAndData @@ -393,9 +391,6 @@ contract TestTokenPaymaster is TestBase { function test_Success_TokenPaymaster_ExternalMode_WithoutPremium() external { tokenPaymaster.deposit{ value: 10 ether }(); testToken.mint(address(ALICE_ACCOUNT), 100_000 * (10 ** testToken.decimals())); - vm.startPrank(address(ALICE_ACCOUNT)); - testToken.approve(address(tokenPaymaster), testToken.balanceOf(address(ALICE_ACCOUNT))); - vm.stopPrank(); vm.startPrank(PAYMASTER_OWNER.addr); tokenPaymaster.setUnaccountedGas(22_000); @@ -411,7 +406,11 @@ contract TestTokenPaymaster is TestBase { uint256 initialPaymasterTokenBalance = testToken.balanceOf(address(tokenPaymaster)); // Build the user operation for external mode - PackedUserOperation memory userOp = buildUserOpWithCalldata(ALICE, "", address(VALIDATOR_MODULE)); + PackedUserOperation memory userOp = buildUserOpWithCalldata( + ALICE, + abi.encodeWithSelector(IExecutionHelper.execute.selector, bytes32(0), abi.encodePacked(address(testToken), uint256(0), abi.encodeWithSelector(IERC20.approve.selector, address(tokenPaymaster), 1_000*1e18))), + address(VALIDATOR_MODULE) + ); uint48 validUntil = uint48(block.timestamp + 1 days); uint48 validAfter = uint48(block.timestamp); uint256 tokenPrice = 1e18; // Assume 1 token = 1 native token = 1 USD ? @@ -424,8 +423,8 @@ contract TestTokenPaymaster is TestBase { validUntil: validUntil, validAfter: validAfter, tokenAddress: address(testToken), - tokenPrice: tokenPrice, - externalPriceMarkup: externalPriceMarkup + tokenPrice: 1e18, + appliedPriceMarkup: 1e6 }); // Generate and sign the token paymaster data @@ -442,9 +441,6 @@ contract TestTokenPaymaster is TestBase { PackedUserOperation[] memory ops = new PackedUserOperation[](1); ops[0] = userOp; - vm.expectEmit(true, true, false, false, address(tokenPaymaster)); - emit IBiconomyTokenPaymaster.TokensRefunded(address(ALICE_ACCOUNT), address(testToken), 0, bytes32(0)); - vm.expectEmit(true, true, false, false, address(tokenPaymaster)); emit IBiconomyTokenPaymaster.PaidGasInTokens(address(ALICE_ACCOUNT), address(testToken), 0, 0, 1e6, 0, bytes32(0)); @@ -471,12 +467,9 @@ contract TestTokenPaymaster is TestBase { function test_Success_TokenPaymaster_IndependentMode_WithoutPremium() external { tokenPaymaster.deposit{ value: 10 ether }(); testToken.mint(address(ALICE_ACCOUNT), 100_000 * (10 ** testToken.decimals())); - vm.startPrank(address(ALICE_ACCOUNT)); - testToken.approve(address(tokenPaymaster), testToken.balanceOf(address(ALICE_ACCOUNT))); - vm.stopPrank(); vm.startPrank(PAYMASTER_OWNER.addr); - tokenPaymaster.setUnaccountedGas(20_000); + tokenPaymaster.setUnaccountedGas(55_000); vm.stopPrank(); uint256 initialBundlerBalance = BUNDLER.addr.balance; @@ -484,7 +477,11 @@ contract TestTokenPaymaster is TestBase { uint256 initialUserTokenBalance = testToken.balanceOf(address(ALICE_ACCOUNT)); uint256 initialPaymasterTokenBalance = testToken.balanceOf(address(tokenPaymaster)); - PackedUserOperation memory userOp = buildUserOpWithCalldata(ALICE, "", address(VALIDATOR_MODULE)); + PackedUserOperation memory userOp = buildUserOpWithCalldata( + ALICE, + abi.encodeWithSelector(IExecutionHelper.execute.selector, bytes32(0), abi.encodePacked(address(testToken), uint256(0), abi.encodeWithSelector(IERC20.approve.selector, address(tokenPaymaster), 1_000*1e18))), + address(VALIDATOR_MODULE) + ); // Encode paymasterAndData for independent mode bytes memory paymasterAndData = abi.encodePacked( @@ -500,10 +497,10 @@ contract TestTokenPaymaster is TestBase { PackedUserOperation[] memory ops = new PackedUserOperation[](1); ops[0] = userOp; - vm.expectEmit(true, true, false, false, address(tokenPaymaster)); - emit IBiconomyTokenPaymaster.TokensRefunded(address(ALICE_ACCOUNT), address(testToken), 0, bytes32(0)); + vm.expectEmit(true, true, false, false, address(tokenPaymaster)); emit IBiconomyTokenPaymaster.PaidGasInTokens(address(ALICE_ACCOUNT), address(testToken), 0, 0, 1e6, 0, bytes32(0)); + startPrank(BUNDLER.addr); uint256 gasValue = gasleft(); ENTRYPOINT.handleOps(ops, payable(BUNDLER.addr)); diff --git a/test/unit/concrete/TestTokenPaymasterParserLib.t.sol b/test/unit/concrete/TestTokenPaymasterParserLib.t.sol index 97aa904..7263b04 100644 --- a/test/unit/concrete/TestTokenPaymasterParserLib.t.sol +++ b/test/unit/concrete/TestTokenPaymasterParserLib.t.sol @@ -70,8 +70,8 @@ contract TestTokenPaymasterParserLib is Test { uint48 expectedValidUntil = uint48(block.timestamp + 1 days); uint48 expectedValidAfter = uint48(block.timestamp); address expectedTokenAddress = address(0x1234567890AbcdEF1234567890aBcdef12345678); - uint256 expectedTokenPrice = 1e8; - uint32 expectedExternalPriceMarkup = 1e6; + uint256 expectedTokenPrice = 1e18; + uint32 expectedAppliedPriceMarkup = 1e6; bytes memory expectedSignature = hex"abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef"; // Construct external mode specific data @@ -80,7 +80,7 @@ contract TestTokenPaymasterParserLib is Test { bytes6(abi.encodePacked(expectedValidAfter)), bytes20(expectedTokenAddress), bytes32(abi.encodePacked(expectedTokenPrice)), - bytes4(abi.encodePacked(expectedExternalPriceMarkup)), + bytes4(abi.encodePacked(expectedAppliedPriceMarkup)), expectedSignature ); @@ -90,7 +90,7 @@ contract TestTokenPaymasterParserLib is Test { uint48 parsedValidAfter, address parsedTokenAddress, uint256 parsedTokenPrice, - uint32 parsedExternalPriceMarkup, + uint32 parsedAppliedPriceMarkup, bytes memory parsedSignature ) = parser.parseExternalModeSpecificData(externalModeSpecificData); @@ -99,7 +99,7 @@ contract TestTokenPaymasterParserLib is Test { assertEq(parsedValidAfter, expectedValidAfter, "ValidAfter should match"); assertEq(parsedTokenAddress, expectedTokenAddress, "Token address should match"); assertEq(parsedTokenPrice, expectedTokenPrice, "Token price should match"); - assertEq(parsedExternalPriceMarkup, expectedExternalPriceMarkup, "Dynamic adjustment should match"); + assertEq(parsedAppliedPriceMarkup, expectedAppliedPriceMarkup, "Applied price markup should match"); assertEq(parsedSignature, expectedSignature, "Signature should match"); }