Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
livingrockrises committed Oct 25, 2024
1 parent beb94ae commit ecd920a
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 57 deletions.
119 changes: 93 additions & 26 deletions test/base/TestBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {
uint32 priceMarkup;
}

struct TokenPaymasterData {
uint128 paymasterValGasLimit;
uint128 paymasterPostOpGasLimit;
IBiconomyTokenPaymaster.PaymasterMode mode;
uint48 validUntil;
uint48 validAfter;
address tokenAddress;
uint128 tokenPrice;
uint32 externalPriceMarkup;
}

// Used to buffer user op gas limits
// GAS_LIMIT = (ESTIMATED_GAS * GAS_BUFFER_RATIO) / 100
uint8 private constant GAS_BUFFER_RATIO = 110;
Expand Down Expand Up @@ -184,6 +195,69 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {
userOpHash = ENTRYPOINT.getUserOpHash(userOp);
}

/// @notice Prepares a packed user operation with specified parameters
/// @param signer The wallet to sign the operation
/// @param account The Nexus account
/// @param execType The execution type
/// @param executions The executions to include
/// @param validator The validator address
/// @param paymaster The paymaster contract
/// @param priceMarkup The price markup
/// @param postOpGasLimitOverride The post-operation gas limit override
/// @return userOps The prepared packed user operations
function buildPackedUserOperationWithSponsorPaymaster(
Vm.Wallet memory signer,
Nexus account,
ExecType execType,
Execution[] memory executions,
address validator,
BiconomySponsorshipPaymaster paymaster,
uint32 priceMarkup,
uint128 postOpGasLimitOverride
// Note: Should allow to pass callGasLimit as well
)
internal
view
returns (PackedUserOperation[] memory userOps)
{
// Validate execType
require(execType == EXECTYPE_DEFAULT || execType == EXECTYPE_TRY, "Invalid ExecType");

// Initialize the userOps array with one operation
userOps = new PackedUserOperation[](1);

// Build the UserOperation
userOps[0] = buildPackedUserOp(address(account), getNonce(address(account), MODE_VALIDATION, validator));
userOps[0].callData = prepareERC7579ExecuteCallData(execType, executions);

PaymasterData memory pmData = PaymasterData({
validationGasLimit: 100_000,
postOpGasLimit: uint128(postOpGasLimitOverride),
paymasterId: DAPP_ACCOUNT.addr,
validUntil: uint48(block.timestamp + 1 days),
validAfter: uint48(block.timestamp),
priceMarkup: priceMarkup
});
(userOps[0].paymasterAndData,) = generateAndSignPaymasterData(userOps[0], PAYMASTER_SIGNER, paymaster, pmData);
userOps[0].signature = signUserOp(signer, userOps[0]);


userOps[0].accountGasLimits = bytes32(abi.encodePacked(uint128(100_000), uint128(100_000)));
PaymasterData memory pmDataNew = PaymasterData(
uint128(100_000),
uint128(postOpGasLimitOverride),
DAPP_ACCOUNT.addr,
uint48(block.timestamp + 1 days),
uint48(block.timestamp),
priceMarkup
);

(userOps[0].paymasterAndData,) = generateAndSignPaymasterData(userOps[0], PAYMASTER_SIGNER, paymaster, pmDataNew);
userOps[0].signature = signUserOp(signer, userOps[0]);

return userOps;
}

/// @notice Generates and signs the paymaster data for a user operation.
/// @dev This function prepares the `paymasterAndData` field for a `PackedUserOperation` with the correct signature.
/// @param userOp The user operation to be signed.
Expand Down Expand Up @@ -248,14 +322,7 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {
PackedUserOperation memory userOp,
Vm.Wallet memory signer,
BiconomyTokenPaymaster paymaster,
uint128 paymasterValGasLimit,
uint128 paymasterPostOpGasLimit,
IBiconomyTokenPaymaster.PaymasterMode mode,
uint48 validUntil,
uint48 validAfter,
address tokenAddress,
uint128 tokenPrice,
uint32 externalPriceMarkup
TokenPaymasterData memory pmData
)
internal
view
Expand All @@ -264,14 +331,14 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {
// Initial paymaster data with zero signature
bytes memory initialPmData = abi.encodePacked(
address(paymaster),
paymasterValGasLimit,
paymasterPostOpGasLimit,
uint8(mode),
validUntil,
validAfter,
tokenAddress,
tokenPrice,
externalPriceMarkup,
pmData.paymasterValGasLimit,
pmData.paymasterPostOpGasLimit,
uint8(pmData.mode),
pmData.validUntil,
pmData.validAfter,
pmData.tokenAddress,
pmData.tokenPrice,
pmData.externalPriceMarkup,
new bytes(65) // Zero signature
);

Expand All @@ -280,7 +347,7 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {

// Generate hash to be signed
bytes32 paymasterHash =
paymaster.getHash(userOp, validUntil, validAfter, tokenAddress, tokenPrice, externalPriceMarkup);
paymaster.getHash(userOp, pmData.validUntil, pmData.validAfter, pmData.tokenAddress, pmData.tokenPrice, pmData.externalPriceMarkup);

// Sign the hash
signature = signMessage(signer, paymasterHash);
Expand All @@ -289,14 +356,14 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {
// Final paymaster data with the actual signature
finalPmData = abi.encodePacked(
address(paymaster),
paymasterValGasLimit,
paymasterPostOpGasLimit,
uint8(mode),
validUntil,
validAfter,
tokenAddress,
tokenPrice,
externalPriceMarkup,
pmData.paymasterValGasLimit,
pmData.paymasterPostOpGasLimit,
uint8(pmData.mode),
pmData.validUntil,
pmData.validAfter,
pmData.tokenAddress,
pmData.tokenPrice,
pmData.externalPriceMarkup,
signature
);
}
Expand Down Expand Up @@ -366,7 +433,7 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {
assertGt(gasPaidByDapp, BUNDLER.addr.balance - initialBundlerBalance);
// Ensure that max 2% difference between total gas paid + the adjustment premium and gas paid by dapp (from
// paymaster)
assertApproxEqRel(totalGasFeePaid + actualPriceMarkup + maxPenalty, gasPaidByDapp, 0.02e18);
assertApproxEqRel(totalGasFeePaid + actualPriceMarkup + maxPenalty, gasPaidByDapp, 0.02e18);
}

function _toSingletonArray(address addr) internal pure returns (address[] memory) {
Expand Down
60 changes: 55 additions & 5 deletions test/unit/concrete/TestSponsorshipPaymaster.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ contract TestSponsorshipPaymasterWithPriceMarkup is TestBase {

//use balance of the paymaster
PackedUserOperation[] memory ops = new PackedUserOperation[](1);
(PackedUserOperation memory userOp, bytes32 userOpHash) = createUserOp(ALICE, bicoPaymaster, 1e6, 55_000);
(PackedUserOperation memory userOp, ) = createUserOp(ALICE, bicoPaymaster, 1e6, 55_000);
ops[0] = userOp;
startPrank(BUNDLER.addr);
ENTRYPOINT.handleOps(ops, payable(BUNDLER.addr));
Expand All @@ -259,10 +259,6 @@ contract TestSponsorshipPaymasterWithPriceMarkup is TestBase {
assertLt(dappPaymasterBalanceAfter, depositAmount);
uint256 bobBalanceBeforeWithdrawal = BOB_ADDRESS.balance;

IStakeManager.DepositInfo memory depositInfo = ENTRYPOINT.getDepositInfo(address(bicoPaymaster));
uint256 PAYMASTER_POSTOP_GAS_OFFSET = 36;
uint256 PAYMASTER_DATA_OFFSET = 52;

vm.warp(block.timestamp + WITHDRAWAL_DELAY + 1);
bicoPaymaster.executeWithdrawalRequest(DAPP_ACCOUNT.addr);
uint256 bobBalanceAfterWithdrawal = BOB_ADDRESS.balance;
Expand Down Expand Up @@ -356,6 +352,60 @@ contract TestSponsorshipPaymasterWithPriceMarkup is TestBase {
);
}

function test_ValidatePaymasterAndPostOpWithPriceMarkup_NonEmptyCalldata() external {
bicoPaymaster.depositFor{ value: 10 ether }(DAPP_ACCOUNT.addr);

startPrank(PAYMASTER_OWNER.addr);
bicoPaymaster.setUnaccountedGas(37_000);
stopPrank();

MockToken token = new MockToken("Token", "TKN");
uint256 mintAmount = 100000 * (10 ** token.decimals());
token.mint(address(ALICE_ACCOUNT), mintAmount);
uint256 transferAmount = 100 * (10 ** token.decimals());

Execution[] memory execution = new Execution[](1);
execution[0] = Execution(address(token), 0, abi.encodeWithSelector(token.transfer.selector, CHARLIE.addr, transferAmount));

// Prepare and execute the UserOperation
PackedUserOperation[] memory userOps = buildPackedUserOperationWithSponsorPaymaster(
ALICE, // Sender of the operation
ALICE_ACCOUNT, // Nexus executing the operation
EXECTYPE_TRY,
execution,
address(VALIDATOR_MODULE),
bicoPaymaster,
1_100_000,
100_000
);

uint256 initialBundlerBalance = BUNDLER.addr.balance;
uint256 initialPaymasterEpBalance = bicoPaymaster.getDeposit();
uint256 initialDappPaymasterBalance = bicoPaymaster.getBalance(DAPP_ACCOUNT.addr);
uint256 initialFeeCollectorBalance = bicoPaymaster.getBalance(PAYMASTER_FEE_COLLECTOR.addr);

// submit userops
vm.expectEmit(true, false, false, false, address(bicoPaymaster));
emit IBiconomySponsorshipPaymaster.GasBalanceDeducted(DAPP_ACCOUNT.addr, 0, 0);

startPrank(BUNDLER.addr);
ENTRYPOINT.handleOps(userOps, payable(BUNDLER.addr));
stopPrank();

assertEq(token.balanceOf(CHARLIE.addr), transferAmount);

// Calculate and assert price markups and gas payments
calculateAndAssertAdjustments(
bicoPaymaster,
initialDappPaymasterBalance,
initialFeeCollectorBalance,
initialBundlerBalance,
initialPaymasterEpBalance,
1_100_000, //price markup, +10% paymaster fee
this.getMaxPenalty(userOps[0])
);
}

function test_RevertIf_ValidatePaymasterUserOpWithIncorrectSignatureLength() external {
PackedUserOperation[] memory ops = new PackedUserOperation[](1);

Expand Down
Empty file.
76 changes: 50 additions & 26 deletions test/unit/concrete/TestTokenPaymaster.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -303,18 +303,22 @@ contract TestTokenPaymaster is TestBase {
PackedUserOperation memory userOp = buildUserOpWithCalldata(ALICE, "", address(VALIDATOR_MODULE));
uint128 tokenPrice = 1e8; // Assume 1 token = 1 USD

TokenPaymasterData memory pmData = TokenPaymasterData({
paymasterValGasLimit: 3e6,
paymasterPostOpGasLimit: 3e6,
mode: IBiconomyTokenPaymaster.PaymasterMode.INDEPENDENT,
validUntil: uint48(block.timestamp + 1 days),
validAfter: uint48(block.timestamp),
tokenAddress: address(testToken),
tokenPrice: tokenPrice,
externalPriceMarkup: 1e6
});

(bytes memory paymasterAndData,) = generateAndSignTokenPaymasterData(
userOp,
PAYMASTER_SIGNER,
tokenPaymaster,
3e6,
3e6,
IBiconomyTokenPaymaster.PaymasterMode.INDEPENDENT,
uint48(block.timestamp + 1 days),
uint48(block.timestamp),
address(testToken),
tokenPrice,
1e6
pmData
);

userOp.paymasterAndData = paymasterAndData;
Expand Down Expand Up @@ -344,19 +348,23 @@ contract TestTokenPaymaster is TestBase {
PackedUserOperation memory userOp = buildUserOpWithCalldata(ALICE, "", address(VALIDATOR_MODULE));
uint128 tokenPrice = 1e8;

TokenPaymasterData memory pmData = TokenPaymasterData({
paymasterValGasLimit: 3e6,
paymasterPostOpGasLimit: 3e6,
mode: IBiconomyTokenPaymaster.PaymasterMode.EXTERNAL,
validUntil: uint48(block.timestamp + 1 days),
validAfter: uint48(block.timestamp),
tokenAddress: address(testToken),
tokenPrice: tokenPrice,
externalPriceMarkup: 1e6
});

// Create a valid paymasterAndData
(bytes memory paymasterAndData,) = generateAndSignTokenPaymasterData(
userOp,
PAYMASTER_SIGNER,
tokenPaymaster,
3e6,
3e6,
IBiconomyTokenPaymaster.PaymasterMode.EXTERNAL,
uint48(block.timestamp + 1 days),
uint48(block.timestamp),
address(testToken),
tokenPrice,
1e6
pmData
);

// Tamper the signature by altering the last byte
Expand All @@ -374,33 +382,40 @@ contract TestTokenPaymaster is TestBase {
ENTRYPOINT.handleOps(ops, payable(BUNDLER.addr));
}

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

uint256 aliceBalanceBefore = testToken.balanceOf(address(ALICE_ACCOUNT));
uint256 tokenPaymasterBalanceBefore = testToken.balanceOf(address(tokenPaymaster));

// 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;

TokenPaymasterData memory pmData = TokenPaymasterData({
paymasterValGasLimit: 3e6,
paymasterPostOpGasLimit: 3e6,
mode: IBiconomyTokenPaymaster.PaymasterMode.EXTERNAL,
validUntil: validUntil,
validAfter: validAfter,
tokenAddress: address(testToken),
tokenPrice: tokenPrice,
externalPriceMarkup: externalPriceMarkup
});

// 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
pmData
);

userOp.paymasterAndData = paymasterAndData;
Expand All @@ -417,15 +432,21 @@ contract TestTokenPaymaster is TestBase {

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

assertLt(testToken.balanceOf(address(ALICE_ACCOUNT)), aliceBalanceBefore);
assertGt(testToken.balanceOf(address(tokenPaymaster)), tokenPaymasterBalanceBefore);
}

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

uint256 aliceBalanceBefore = testToken.balanceOf(address(ALICE_ACCOUNT));
uint256 tokenPaymasterBalanceBefore = testToken.balanceOf(address(tokenPaymaster));

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

// Encode paymasterAndData for independent mode
Expand All @@ -450,5 +471,8 @@ contract TestTokenPaymaster is TestBase {
emit IBiconomyTokenPaymaster.PaidGasInTokens(address(ALICE_ACCOUNT), address(testToken), 0, 0, 1e6, bytes32(0));

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

assertLt(testToken.balanceOf(address(ALICE_ACCOUNT)), aliceBalanceBefore);
assertGt(testToken.balanceOf(address(tokenPaymaster)), tokenPaymasterBalanceBefore);
}
}

0 comments on commit ecd920a

Please sign in to comment.