Skip to content

Commit

Permalink
charge in postop, external mode uses off-chain estimations
Browse files Browse the repository at this point in the history
  • Loading branch information
Filipp Makarov authored and Filipp Makarov committed Dec 4, 2024
1 parent 713d31d commit 3c27ca6
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 32 deletions.
6 changes: 2 additions & 4 deletions contracts/interfaces/IBiconomyTokenPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,11 @@ 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 PaidGasInTokensIndependent(
address indexed userOpSender,
address indexed token,
uint256 nativeCharge,
uint256 gasCostBeforePostOpAndPenalty,
uint256 tokenCharge,
uint32 priceMarkup,
uint256 tokenPrice,
Expand Down
31 changes: 16 additions & 15 deletions contracts/token/BiconomyTokenPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -509,12 +509,14 @@ contract BiconomyTokenPaymaster is
revert InsufficientTokenBalance(userOp.sender, tokenAddress, estimatedTokenAmount, userOpHash);
}

context = abi.encode(
mode,
userOp.sender,
tokenAddress,
estimatedTokenAmount,
userOpHash
context = abi.encodePacked(
PaymasterMode.EXTERNAL,
abi.encode(
userOp.sender,
tokenAddress,
estimatedTokenAmount,
userOpHash
)
);
validationData = _packValidationData(false, validUntil, validAfter);

Expand All @@ -529,7 +531,7 @@ contract BiconomyTokenPaymaster is
(
uint128(uint256(userOp.accountGasLimits))
+ uint128(bytes16(userOp.paymasterAndData[_PAYMASTER_POSTOP_GAS_OFFSET:_PAYMASTER_DATA_OFFSET]))
) * 10 * userOp.unpackMaxFeePerGas()
) * 10 //* userOp.unpackMaxFeePerGas()
) / 100;

// Get address for token used to pay
Expand All @@ -555,16 +557,17 @@ contract BiconomyTokenPaymaster is
revert InsufficientTokenBalance(userOp.sender, tokenAddress, tokenAmount, userOpHash);
}

context =
context = abi.encodePacked(
PaymasterMode.INDEPENDENT,
abi.encode(
mode,
userOp.sender,
tokenAddress,
maxPenalty,
tokenPrice,
priceMarkup,
userOpHash
);
)
);
validationData = 0; // Validation success and price is valid indefinetly
}
}
Expand All @@ -585,10 +588,8 @@ contract BiconomyTokenPaymaster is
internal
override
{

PaymasterMode mode = PaymasterMode(uint8(context[0]));

if (mode == PaymasterMode.EXTERNAL) {
PaymasterMode pmMode = PaymasterMode(uint8(context[0]));
if (pmMode == PaymasterMode.EXTERNAL) {
// Decode context data
(
address userOpSender,
Expand All @@ -603,7 +604,7 @@ contract BiconomyTokenPaymaster is
revert FailedToChargeTokens(userOpSender, tokenAddress, estimatedTokenAmount, userOpHash);
}

} else if (mode == PaymasterMode.INDEPENDENT) {
} else if (pmMode == PaymasterMode.INDEPENDENT) {
(
address userOpSender,
address tokenAddress,
Expand Down
7 changes: 5 additions & 2 deletions test/base/TestBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -479,10 +482,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) {
Expand Down
3 changes: 0 additions & 3 deletions test/unit/concrete/TestTokenPaymaster.Base.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,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.PaidGasInTokensIndependent(address(ALICE_ACCOUNT), address(usdc), 0, 0, 1e6, 0, bytes32(0));

Expand Down
17 changes: 9 additions & 8 deletions test/unit/concrete/TestTokenPaymaster.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,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 ?
Expand All @@ -420,7 +424,7 @@ contract TestTokenPaymaster is TestBase {
validUntil: validUntil,
validAfter: validAfter,
tokenAddress: address(testToken),
estimatedTokenAmount: 1e18
estimatedTokenAmount: 3_000_000_000_000
});

// Generate and sign the token paymaster data
Expand All @@ -437,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.PaidGasInTokensExternal(address(ALICE_ACCOUNT), address(testToken), 0, bytes32(0));

Expand Down Expand Up @@ -471,7 +472,7 @@ contract TestTokenPaymaster is TestBase {
vm.stopPrank();

vm.startPrank(PAYMASTER_OWNER.addr);
tokenPaymaster.setUnaccountedGas(20_000);
tokenPaymaster.setUnaccountedGas(40_000);
vm.stopPrank();

uint256 initialBundlerBalance = BUNDLER.addr.balance;
Expand All @@ -495,10 +496,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.PaidGasInTokensIndependent(address(ALICE_ACCOUNT), address(testToken), 0, 0, 1e6, 0, bytes32(0));

startPrank(BUNDLER.addr);
uint256 gasValue = gasleft();
ENTRYPOINT.handleOps(ops, payable(BUNDLER.addr));
Expand Down

0 comments on commit 3c27ca6

Please sign in to comment.