Skip to content

Commit

Permalink
fixed a few things with gas calc and wrote tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ShivaanshK committed Jul 6, 2024
1 parent 95b996d commit ff05364
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 89 deletions.
19 changes: 9 additions & 10 deletions contracts/sponsorship/SponsorshipPaymasterWithPremium.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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();
}

Expand Down
91 changes: 14 additions & 77 deletions test/foundry/base/NexusTestBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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.
Expand Down
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";
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand All @@ -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 {
Expand Down

0 comments on commit ff05364

Please sign in to comment.