Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/paymaster foundry tests #1

Merged
merged 45 commits into from
Jul 11, 2024
Merged
Changes from 1 commit
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
55d967d
Setup nexus submodule and fix remappings for foundry test
ShivaanshK Jun 26, 2024
659b7e3
setup imports, accounts, and align versions
ShivaanshK Jun 27, 2024
967379c
Deployment test for BiconomySponsorshipPaymaster
ShivaanshK Jun 27, 2024
2e32ee2
basic state tests
ShivaanshK Jun 28, 2024
46d8c89
quick fix
ShivaanshK Jun 28, 2024
87c8c69
deposit tests
ShivaanshK Jun 28, 2024
6a85282
rename tests according to naming conventions
ShivaanshK Jun 28, 2024
0eb2f81
Check events being emitted
ShivaanshK Jun 28, 2024
502ee33
tests for withdrawing from paymaster
ShivaanshK Jun 28, 2024
d7efb30
tests for validating paymaster and postop
ShivaanshK Jul 1, 2024
03ff890
tests for receiving and withdrawing ether to/from paymaster
ShivaanshK Jul 1, 2024
404b493
commit unit tests
ShivaanshK Jul 1, 2024
4ba40d4
fuzz tests
ShivaanshK Jul 1, 2024
5868a5c
all fuzz tests
ShivaanshK Jul 1, 2024
2fd121c
get rid of nonfuzz test in fuzz file
ShivaanshK Jul 1, 2024
45c20eb
forge install: nexus
livingrockrises Jul 1, 2024
d01ace0
build dep fixes
livingrockrises Jul 2, 2024
d97036f
Merge pull request #2 from bcnmy/fix/project-build
ShivaanshK Jul 3, 2024
5da6fbe
add adam's requested changes
ShivaanshK Jul 3, 2024
b4276e4
fixed linting and changed visibility where applicable
ShivaanshK Jul 3, 2024
9e72f5c
yarn lint runs successfully
ShivaanshK Jul 3, 2024
c62faf2
Merge pull request #5 from bcnmy/feat/fix-linting
livingrockrises Jul 3, 2024
199d312
setPostOpCost tests
ShivaanshK Jul 4, 2024
58ebf22
merge
ShivaanshK Jul 4, 2024
de84d99
tests for withdrawErc20
ShivaanshK Jul 4, 2024
38f9891
fix forge build
livingrockrises Jul 4, 2024
3ac0568
add adam's requested changes
ShivaanshK Jul 3, 2024
de22654
setPostOpCost tests
ShivaanshK Jul 4, 2024
3972d14
fixed linting and changed visibility where applicable
ShivaanshK Jul 3, 2024
f4f341c
yarn lint runs successfully
ShivaanshK Jul 3, 2024
8326d90
tests for withdrawErc20
ShivaanshK Jul 4, 2024
1ee053e
fix forge build
livingrockrises Jul 4, 2024
e1ba22e
switched to custom errors and fixed postop gas calc
ShivaanshK Jul 4, 2024
10b3892
fixed a few things with gas calc and wrote tests
ShivaanshK Jul 6, 2024
2817e44
more tests for accounting for premiums
ShivaanshK Jul 7, 2024
1d96053
some cleanup
ShivaanshK Jul 7, 2024
40aed7e
make tests more modular
ShivaanshK Jul 8, 2024
81abba5
postop cost added back and more tests added
ShivaanshK Jul 9, 2024
4ba9a4b
Merge branch 'feat/paymaster-foundry-tests' of https://github.com/bcn…
livingrockrises Jul 9, 2024
abff5d2
fix postOpCost
livingrockrises Jul 9, 2024
f5acd1a
parse paymaster data test
ShivaanshK Jul 10, 2024
ebf05c1
comprehensive accounting tests
ShivaanshK Jul 10, 2024
36d854b
fix-lint
ShivaanshK Jul 10, 2024
d59cfd5
make UNACCOUNTED_GAS_LIMIT a constant
ShivaanshK Jul 10, 2024
358aeb1
added check for bundler balance change equal to paymaster EP balance …
ShivaanshK Jul 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fixed a few things with gas calc and wrote tests
ShivaanshK committed Jul 9, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit 10b389270d43088eb8d7818e79a11fb96d714bac
19 changes: 9 additions & 10 deletions contracts/sponsorship/SponsorshipPaymasterWithPremium.sol
Original file line number Diff line number Diff line change
@@ -232,14 +232,11 @@ 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.
/// @param actualUserOpFeePerGas - the gas price this UserOp pays. This value is based on the UserOp's maxFeePerGas
// and maxPriorityFee (and basefee)
// It is not the same as tx.gasprice, which is what the bundler pays.
function _postOp(
PostOpMode,
bytes calldata context,
uint256 actualGasCost,
uint256 actualUserOpFeePerGas
uint256
)
internal
override
@@ -253,13 +250,15 @@ contract BiconomySponsorshipPaymaster is
// deduct with premium
paymasterIdBalances[paymasterId] -= costIncludingPremium;

uint256 actualPremium = costIncludingPremium - actualGasCost;
// "collect" premium
paymasterIdBalances[feeCollector] += actualPremium;
if (actualGasCost < costIncludingPremium) {
// "collect" premium
uint256 actualPremium = costIncludingPremium - actualGasCost;
paymasterIdBalances[feeCollector] += actualPremium;
// Review if we should emit balToDeduct as well
emit PremiumCollected(paymasterId, actualPremium);
}

emit GasBalanceDeducted(paymasterId, costIncludingPremium, userOpHash);
// Review if we should emit balToDeduct as well
emit PremiumCollected(paymasterId, actualPremium);
}
}

@@ -288,7 +287,7 @@ contract BiconomySponsorshipPaymaster is
//ECDSA library supports both 64 and 65-byte long signatures.
// we only "require" it here so that the revert reason on invalid signature will be of "VerifyingPaymaster", and
// not "ECDSA"
if(signature.length != 64 && signature.length != 65){
if (signature.length != 64 && signature.length != 65) {
revert InvalidSignatureLength();
}

91 changes: 14 additions & 77 deletions test/foundry/base/NexusTestBase.sol
livingrockrises marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ import "solady/src/utils/ECDSA.sol";

import { EntryPoint } from "account-abstraction/contracts/core/EntryPoint.sol";
import { IEntryPoint } from "account-abstraction/contracts/interfaces/IEntryPoint.sol";
import { IPaymaster } from "account-abstraction/contracts/interfaces/IPaymaster.sol";
import { PackedUserOperation } from "account-abstraction/contracts/interfaces/PackedUserOperation.sol";

import { Nexus } from "nexus/contracts/Nexus.sol";
@@ -331,87 +332,23 @@ abstract contract NexusTestBase is CheatCodes, BaseEventsAndErrors {
assertTrue(res, "Pre-funding account should succeed");
}

/// @notice Calculates the gas cost of the calldata
/// @param data The calldata
/// @return calldataGas The gas cost of the calldata
function calculateCalldataCost(bytes memory data) internal pure returns (uint256 calldataGas) {
for (uint256 i = 0; i < data.length; i++) {
if (uint8(data[i]) == 0) {
calldataGas += 4;
} else {
calldataGas += 16;
}
}
}

/// @notice Helper function to measure and log gas for simple EOA calls
/// @param description The description for the log
/// @param target The target contract address
/// @param value The value to be sent with the call
/// @param callData The calldata for the call
function measureAndLogGasEOA(
string memory description,
address target,
uint256 value,
bytes memory callData
function estimatePaymasterGasCosts(
BiconomySponsorshipPaymaster paymaster,
PackedUserOperation memory userOp,
bytes32 userOpHash,
uint256 requiredPreFund
)
internal
prankModifier(ENTRYPOINT_ADDRESS)
returns (uint256 validationGasLimit, uint256 postopGasLimit)
{
uint256 calldataCost = 0;
for (uint256 i = 0; i < callData.length; i++) {
if (uint8(callData[i]) == 0) {
calldataCost += 4;
} else {
calldataCost += 16;
}
}

uint256 baseGas = 21_000;

uint256 initialGas = gasleft();
(bool res,) = target.call{ value: value }(callData);
uint256 gasUsed = initialGas - gasleft() + baseGas + calldataCost;
assertTrue(res);
emit log_named_uint(description, gasUsed);
}
validationGasLimit = gasleft();
(bytes memory context,) = paymaster.validatePaymasterUserOp(userOp, userOpHash, requiredPreFund);
validationGasLimit = validationGasLimit - gasleft();

/// @notice Helper function to calculate calldata cost and log gas usage
/// @param description The description for the log
/// @param userOps The user operations to be executed
function measureAndLogGas(string memory description, PackedUserOperation[] memory userOps) internal {
bytes memory callData = abi.encodeWithSelector(ENTRYPOINT.handleOps.selector, userOps, payable(BUNDLER.addr));

uint256 calldataCost = 0;
for (uint256 i = 0; i < callData.length; i++) {
if (uint8(callData[i]) == 0) {
calldataCost += 4;
} else {
calldataCost += 16;
}
}

uint256 baseGas = 21_000;

uint256 initialGas = gasleft();
ENTRYPOINT.handleOps(userOps, payable(BUNDLER.addr));
uint256 gasUsed = initialGas - gasleft() + baseGas + calldataCost;
emit log_named_uint(description, gasUsed);
}

/// @notice Handles a user operation and measures gas usage
/// @param userOps The user operations to handle
/// @param refundReceiver The address to receive the gas refund
/// @return gasUsed The amount of gas used
function handleUserOpAndMeasureGas(
PackedUserOperation[] memory userOps,
address refundReceiver
)
internal
returns (uint256 gasUsed)
{
uint256 gasStart = gasleft();
ENTRYPOINT.handleOps(userOps, payable(refundReceiver));
gasUsed = gasStart - gasleft();
postopGasLimit = gasleft();
paymaster.postOp(IPaymaster.PostOpMode.opSucceeded, context, 3e4, 2e9);
postopGasLimit = postopGasLimit - gasleft();
}

/// @notice Generates and signs the paymaster data for a user operation.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.26;

import { console2 } from "forge-std/src/console2.sol";
import { NexusTestBase } from "../../base/NexusTestBase.sol";
import { IBiconomySponsorshipPaymaster } from "../../../../contracts/interfaces/IBiconomySponsorshipPaymaster.sol";
import { BiconomySponsorshipPaymaster } from "../../../../contracts/sponsorship/SponsorshipPaymasterWithPremium.sol";
@@ -28,6 +29,16 @@ contract TestSponsorshipPaymasterWithPremium is NexusTestBase {
assertEq(testArtifact.feeCollector(), PAYMASTER_FEE_COLLECTOR.addr);
}

function test_RevertIf_DeployWithSignerSetToZero() external {
vm.expectRevert(abi.encodeWithSelector(VerifyingSignerCanNotBeZero.selector));
new BiconomySponsorshipPaymaster(PAYMASTER_OWNER.addr, ENTRYPOINT, address(0), PAYMASTER_FEE_COLLECTOR.addr);
}

function test_RevertIf_DeployWithFeeCollectorSetToZero() external {
vm.expectRevert(abi.encodeWithSelector(FeeCollectorCanNotBeZero.selector));
new BiconomySponsorshipPaymaster(PAYMASTER_OWNER.addr, ENTRYPOINT, PAYMASTER_SIGNER.addr, address(0));
}

function test_CheckInitialPaymasterState() external view {
assertEq(bicoPaymaster.owner(), PAYMASTER_OWNER.addr);
assertEq(address(bicoPaymaster.entryPoint()), ENTRYPOINT_ADDRESS);
@@ -148,7 +159,7 @@ contract TestSponsorshipPaymasterWithPremium is NexusTestBase {
bicoPaymaster.withdrawTo(payable(DAN_ADDRESS), 1 ether);
}

function test_ValidatePaymasterAndPostOp() external {
function test_ValidatePaymasterAndPostOpWithoutPremium() external {
uint256 initialDappPaymasterBalance = 10 ether;
bicoPaymaster.depositFor{ value: initialDappPaymasterBalance }(DAPP_ACCOUNT.addr);

@@ -169,12 +180,71 @@ contract TestSponsorshipPaymasterWithPremium is NexusTestBase {

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);
assertNotEq(initialDappPaymasterBalance, resultingDappPaymasterBalance);
}

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

uint48 validUntil = uint48(block.timestamp + 1 days);
uint48 validAfter = uint48(block.timestamp);

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

// Charge a 10% premium
uint32 premium = 1e6 + 1e5;
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);

// submit userops
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));

// Check that gas fees ended up in the right wallets
uint256 resultingDappPaymasterBalance = bicoPaymaster.getBalance(DAPP_ACCOUNT.addr);
assertNotEq(initialDappPaymasterBalance, resultingDappPaymasterBalance);
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);
}

function test_RevertIf_ValidatePaymasterUserOpWithIncorrectSignatureLength() external {