Skip to content

Commit

Permalink
feat: update test + dev notes + more tests WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
livingrockrises committed Oct 4, 2024
1 parent acc0dca commit f515d0d
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 80 deletions.
8 changes: 7 additions & 1 deletion contracts/token/BiconomyTokenPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ contract BiconomyTokenPaymaster is
uint256 private constant PRICE_DENOMINATOR = 1e6; // Denominator used when calculating cost with price markup
uint256 private constant MAX_PRICE_MARKUP = 2e6; // 100% premium on price (2e6/PRICE_DENOMINATOR)

// Note: _priceExpiryDuration is common for all the feeds.
// Note: _independentPriceMarkup is common for all the independent tokens.
// Todo: add cases when uniswap is not available
// Note: swapTokenAndDeposit: we may not need to keep this onlyOwner


constructor(
address _owner,
address _verifyingSigner,
Expand Down Expand Up @@ -521,7 +527,7 @@ contract BiconomyTokenPaymaster is

// Calculate the actual cost in tokens based on the actual gas cost and the token price
uint256 actualTokenAmount = (
(actualGasCost + (unaccountedGas) * actualUserOpFeePerGas) * appliedPriceMarkup * tokenPrice
(actualGasCost + (unaccountedGas * actualUserOpFeePerGas)) * appliedPriceMarkup * tokenPrice
) / (1e18 * PRICE_DENOMINATOR);

if (prechargedAmount > actualTokenAmount) {
Expand Down
167 changes: 88 additions & 79 deletions test/unit/concrete/TestTokenPaymaster.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.26;

import "../../base/TestBase.sol";
import { console2 } from "forge-std/src/console2.sol";
import {
BiconomyTokenPaymaster,
IBiconomyTokenPaymaster,
Expand Down Expand Up @@ -256,84 +257,6 @@ contract TestTokenPaymaster is TestBase {
assertEq(address(tokenPaymaster.nativeAssetToUsdOracle()), address(newOracle));
}

function test_ValidatePaymasterUserOp_ExternalMode() 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();

// Build the user operation for external mode
PackedUserOperation memory userOp = buildUserOpWithCalldata(ALICE, "", address(VALIDATOR_MODULE));
uint48 validUntil = uint48(block.timestamp + 1 days);
uint48 validAfter = uint48(block.timestamp);
uint128 tokenPrice = 1e8; // Assume 1 token = 1 USD
uint32 externalPriceMarkup = 1e6;

// Generate and sign the token paymaster data
(bytes memory paymasterAndData,) = generateAndSignTokenPaymasterData(
userOp,
PAYMASTER_SIGNER,
tokenPaymaster,
3e6, // assumed gas limit for test
3e6, // assumed verification gas for test
IBiconomyTokenPaymaster.PaymasterMode.EXTERNAL,
validUntil,
validAfter,
address(testToken),
tokenPrice,
externalPriceMarkup
);

userOp.paymasterAndData = paymasterAndData;
userOp.signature = signUserOp(ALICE, userOp);

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, bytes32(0));

// Execute the operation
ENTRYPOINT.handleOps(ops, payable(BUNDLER.addr));
}

function test_ValidatePaymasterUserOp_IndependentMode() 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();

PackedUserOperation memory userOp = buildUserOpWithCalldata(ALICE, "", address(VALIDATOR_MODULE));

// Encode paymasterAndData for independent mode
bytes memory paymasterAndData = abi.encodePacked(
address(tokenPaymaster),
uint128(3e6), // assumed gas limit for test
uint128(3e6), // assumed verification gas for test
uint8(IBiconomyTokenPaymaster.PaymasterMode.INDEPENDENT),
address(testToken)
);

userOp.paymasterAndData = paymasterAndData;
userOp.signature = signUserOp(ALICE, userOp);

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, bytes32(0));

ENTRYPOINT.handleOps(ops, payable(BUNDLER.addr));
}

// Test multiple ERC20 token withdrawals
function test_WithdrawMultipleERC20Tokens() external prankModifier(PAYMASTER_OWNER.addr) {
// Mint tokens to paymaster
Expand Down Expand Up @@ -441,10 +364,96 @@ contract TestTokenPaymaster is TestBase {
paymasterAndData[paymasterAndData.length - 1] = bytes1(uint8(paymasterAndData[paymasterAndData.length - 1]) + 1);
userOp.paymasterAndData = paymasterAndData;

userOp.signature = signUserOp(ALICE, userOp);

PackedUserOperation[] memory ops = new PackedUserOperation[](1);
ops[0] = userOp;

bytes memory expectedRevertReason = abi.encodeWithSelector(
FailedOp.selector,
0,
"AA34 signature error"
);

vm.expectRevert(expectedRevertReason);
ENTRYPOINT.handleOps(ops, payable(BUNDLER.addr));
}

function test_ValidatePaymasterUserOp_ExternalMode() 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();

// Build the user operation for external mode
PackedUserOperation memory userOp = buildUserOpWithCalldata(ALICE, "", address(VALIDATOR_MODULE));
uint48 validUntil = uint48(block.timestamp + 1 days);
uint48 validAfter = uint48(block.timestamp);
uint128 tokenPrice = 1e8; // Assume 1 token = 1 USD
uint32 externalPriceMarkup = 1e6;

// Generate and sign the token paymaster data
(bytes memory paymasterAndData,) = generateAndSignTokenPaymasterData(
userOp,
PAYMASTER_SIGNER,
tokenPaymaster,
3e6, // assumed gas limit for test
3e6, // assumed verification gas for test
IBiconomyTokenPaymaster.PaymasterMode.EXTERNAL,
validUntil,
validAfter,
address(testToken),
tokenPrice,
externalPriceMarkup
);

userOp.paymasterAndData = paymasterAndData;
userOp.signature = signUserOp(ALICE, userOp);

PackedUserOperation[] memory ops = new PackedUserOperation[](1);
ops[0] = userOp;

vm.expectRevert();
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, bytes32(0));

// Execute the operation
ENTRYPOINT.handleOps(ops, payable(BUNDLER.addr));
}

function test_ValidatePaymasterUserOp_IndependentMode() 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();

PackedUserOperation memory userOp = buildUserOpWithCalldata(ALICE, "", address(VALIDATOR_MODULE));

// Encode paymasterAndData for independent mode
bytes memory paymasterAndData = abi.encodePacked(
address(tokenPaymaster),
uint128(3e6), // assumed gas limit for test
uint128(3e6), // assumed verification gas for test
uint8(IBiconomyTokenPaymaster.PaymasterMode.INDEPENDENT),
address(testToken)
);

userOp.paymasterAndData = paymasterAndData;
userOp.signature = signUserOp(ALICE, userOp);

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, bytes32(0));

ENTRYPOINT.handleOps(ops, payable(BUNDLER.addr));
}
}

0 comments on commit f515d0d

Please sign in to comment.