Skip to content

Commit

Permalink
comprehensive accounting tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ShivaanshK committed Jul 10, 2024
1 parent f5acd1a commit ebf05c1
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 51 deletions.
2 changes: 1 addition & 1 deletion contracts/interfaces/IBiconomySponsorshipPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity ^0.8.26;

interface IBiconomySponsorshipPaymaster {
event PostopCostChanged(uint256 indexed oldValue, uint256 indexed newValue);
event UnaccountedGasChanged(uint256 indexed oldValue, uint256 indexed newValue);
event FixedDynamicAdjustmentChanged(uint32 indexed oldValue, uint32 indexed newValue);

event VerifyingSignerChanged(address indexed oldSigner, address indexed newSigner, address indexed actor);
Expand Down
31 changes: 18 additions & 13 deletions contracts/sponsorship/SponsorshipPaymasterWithDynamicAdjustment.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,21 @@ contract BiconomySponsorshipPaymaster is
address _owner,
IEntryPoint _entryPoint,
address _verifyingSigner,
address _feeCollector
address _feeCollector,
uint48 _unaccountedGas
)
BasePaymaster(_owner, _entryPoint)
{
if (_verifyingSigner == address(0)) {
revert VerifyingSignerCanNotBeZero();
} else if (_feeCollector == address(0)) {
revert FeeCollectorCanNotBeZero();
} else if (_unaccountedGas > 200_000) {
revert UnaccountedGasTooHigh();
}
verifyingSigner = _verifyingSigner;
feeCollector = _feeCollector;
unaccountedGas = _unaccountedGas;
}

receive() external payable {
Expand Down Expand Up @@ -119,13 +123,13 @@ contract BiconomySponsorshipPaymaster is
* @param value The new value to be set as the unaccountedEPGasOverhead.
* @notice only to be called by the owner of the contract.
*/
function setPostopCost(uint48 value) external payable onlyOwner {
function setUnaccountedGas(uint48 value) external payable onlyOwner {
if (value > 200_000) {
revert UnaccountedGasTooHigh();
}
uint256 oldValue = unaccountedGas;
unaccountedGas = value;
emit PostopCostChanged(oldValue, value);
emit UnaccountedGasChanged(oldValue, value);
}

/**
Expand Down Expand Up @@ -254,18 +258,21 @@ contract BiconomySponsorshipPaymaster is
(address paymasterId, uint32 dynamicAdjustment, bytes32 userOpHash) =
abi.decode(context, (address, uint32, bytes32));

uint256 totalGasCost = actualGasCost + (unaccountedGas * actualUserOpFeePerGas);
uint256 adjustedGasCost = (totalGasCost * dynamicAdjustment) / PRICE_DENOMINATOR;
// Include unaccountedGas since EP doesn't include this in actualGasCost
// unaccountedGas = postOpGas + EP overhead gas + estimated penalty
actualGasCost = actualGasCost + (unaccountedGas * actualUserOpFeePerGas);
// Apply the dynamic adjustment
uint256 adjustedGasCost = (actualGasCost * dynamicAdjustment) / PRICE_DENOMINATOR;

// Deduct the adjusted cost
paymasterIdBalances[paymasterId] -= adjustedGasCost;

if (adjustedGasCost > actualGasCost) {
// Add dynamicAdjustment to fee
uint256 dynamicAdjustment = adjustedGasCost - actualGasCost;
paymasterIdBalances[feeCollector] += dynamicAdjustment;
// Apply dynamicAdjustment to fee collector balance
uint256 premium = adjustedGasCost - actualGasCost;
paymasterIdBalances[feeCollector] += premium;
// Review if we should emit adjustedGasCost as well
emit DynamicAdjustmentCollected(paymasterId, dynamicAdjustment);
emit DynamicAdjustmentCollected(paymasterId, premium);
}

emit GasBalanceDeducted(paymasterId, adjustedGasCost, userOpHash);
Expand All @@ -292,8 +299,8 @@ contract BiconomySponsorshipPaymaster is
override
returns (bytes memory context, uint256 validationData)
{
(address paymasterId, uint48 validUntil, uint48 validAfter, uint32 dynamicAdjustment, bytes calldata signature) =
parsePaymasterAndData(userOp.paymasterAndData);
(address paymasterId, uint48 validUntil, uint48 validAfter, uint32 dynamicAdjustment, bytes calldata signature)
= parsePaymasterAndData(userOp.paymasterAndData);
//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"
Expand Down Expand Up @@ -325,8 +332,6 @@ contract BiconomySponsorshipPaymaster is

context = abi.encode(paymasterId, dynamicAdjustment, userOpHash);

context = abi.encode(paymasterId, dynamicAdjustment, userOpHash);

//no need for other on-chain validation: entire UserOp should have been checked
// by the external service prior to signing it.
return (context, _packValidationData(false, validUntil, validAfter));
Expand Down
67 changes: 53 additions & 14 deletions test/foundry/base/TestBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ 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 { IAccount } from "account-abstraction/contracts/interfaces/IAccount.sol";
import { Exec } from "account-abstraction/contracts/utils/Exec.sol";
import { IPaymaster } from "account-abstraction/contracts/interfaces/IPaymaster.sol";
import { PackedUserOperation } from "account-abstraction/contracts/interfaces/PackedUserOperation.sol";

Expand All @@ -20,7 +22,8 @@ import { Bootstrap, BootstrapConfig } from "nexus/contracts/utils/Bootstrap.sol"
import { CheatCodes } from "nexus/test/foundry/utils/CheatCodes.sol";
import { BaseEventsAndErrors } from "./BaseEventsAndErrors.sol";

import { BiconomySponsorshipPaymaster } from "../../../contracts/sponsorship/SponsorshipPaymasterWithDynamicAdjustment.sol";
import { BiconomySponsorshipPaymaster } from
"../../../contracts/sponsorship/SponsorshipPaymasterWithDynamicAdjustment.sol";

abstract contract TestBase is CheatCodes, BaseEventsAndErrors {
// -----------------------------------------
Expand Down Expand Up @@ -59,9 +62,12 @@ abstract contract TestBase is CheatCodes, BaseEventsAndErrors {
BiconomyMetaFactory internal META_FACTORY;
MockValidator internal VALIDATOR_MODULE;
Nexus internal ACCOUNT_IMPLEMENTATION;

Bootstrap internal BOOTSTRAPPER;

// Used to buffer user op gas limits
// GAS_LIMIT = (ESTIMATED_GAS * GAS_BUFFER_RATIO) / 100
uint8 private constant GAS_BUFFER_RATIO = 110;

// -----------------------------------------
// Modifiers
// -----------------------------------------
Expand Down Expand Up @@ -332,23 +338,50 @@ abstract contract TestBase is CheatCodes, BaseEventsAndErrors {
assertTrue(res, "Pre-funding account should succeed");
}

function estimateUserOpGasCosts(PackedUserOperation memory userOp)
internal
prankModifier(ENTRYPOINT_ADDRESS)
returns (uint256 verificationGasUsed, uint256 callGasUsed, uint256 verificationGasLimit, uint256 callGasLimit)
{
bytes32 userOpHash = ENTRYPOINT.getUserOpHash(userOp);
verificationGasUsed = gasleft();
IAccount(userOp.sender).validateUserOp(userOp, userOpHash, 0);
verificationGasUsed = verificationGasUsed - gasleft();

callGasUsed = gasleft();
bool success = Exec.call(userOp.sender, 0, userOp.callData, 3e6);
callGasUsed = callGasUsed - gasleft();
assert(success);

verificationGasLimit = (verificationGasUsed * GAS_BUFFER_RATIO) / 100;
callGasLimit = (callGasUsed * GAS_BUFFER_RATIO) / 100;
}

function estimatePaymasterGasCosts(
BiconomySponsorshipPaymaster paymaster,
PackedUserOperation memory userOp,
bytes32 userOpHash,
uint256 requiredPreFund
)
internal
prankModifier(ENTRYPOINT_ADDRESS)
returns (uint256 validationGasLimit, uint256 postopGasLimit)
returns (uint256 validationGasUsed, uint256 postopGasUsed, uint256 validationGasLimit, uint256 postopGasLimit)
{
validationGasLimit = gasleft();
bytes32 userOpHash = ENTRYPOINT.getUserOpHash(userOp);
// Warm up accounts to get more accurate gas estimations
(bytes memory context,) = paymaster.validatePaymasterUserOp(userOp, userOpHash, requiredPreFund);
validationGasLimit = validationGasLimit - gasleft();
paymaster.postOp(IPaymaster.PostOpMode.opSucceeded, context, 1e12, 3e6);

// Estimate gas used
validationGasUsed = gasleft();
(context,) = paymaster.validatePaymasterUserOp(userOp, userOpHash, requiredPreFund);
validationGasUsed = validationGasUsed - gasleft();

postopGasLimit = gasleft();
postopGasUsed = gasleft();
paymaster.postOp(IPaymaster.PostOpMode.opSucceeded, context, 1e12, 3e6);
postopGasLimit = postopGasLimit - gasleft();
postopGasUsed = (postopGasUsed - gasleft());

validationGasLimit = (validationGasUsed * GAS_BUFFER_RATIO) / 100;
postopGasLimit = (postopGasUsed * GAS_BUFFER_RATIO) / 100;
}

function createUserOp(
Expand All @@ -359,24 +392,30 @@ abstract contract TestBase is CheatCodes, BaseEventsAndErrors {
internal
returns (PackedUserOperation memory userOp, bytes32 userOpHash)
{
// Create userOp with no paymaster gas estimates
// Create userOp with no gas estimates
uint48 validUntil = uint48(block.timestamp + 1 days);
uint48 validAfter = uint48(block.timestamp);

userOp = buildUserOpWithCalldata(sender, "", address(VALIDATOR_MODULE));

(userOp.paymasterAndData, ) = generateAndSignPaymasterData(
(userOp.paymasterAndData,) = generateAndSignPaymasterData(
userOp, PAYMASTER_SIGNER, paymaster, 3e6, 3e6, DAPP_ACCOUNT.addr, validUntil, validAfter, dynamicAdjustment
);
userOp.signature = signUserOp(sender, userOp);

(,, uint256 verificationGasLimit, uint256 callGasLimit) = estimateUserOpGasCosts(userOp);
// Estimate paymaster gas limits
userOpHash = ENTRYPOINT.getUserOpHash(userOp);
(uint256 validationGasLimit, uint256 postopGasLimit) =
estimatePaymasterGasCosts(paymaster, userOp, userOpHash, 5e4);
(, uint256 postopGasUsed, uint256 validationGasLimit, uint256 postopGasLimit) =
estimatePaymasterGasCosts(paymaster, userOp, 5e4);

vm.startPrank(paymaster.owner());
// Set unaccounted gas to be gas used in postop + 1000 for EP overhead and penalty
paymaster.setUnaccountedGas(uint48(postopGasUsed + 1000));
vm.stopPrank();

// Ammend the userop to have new gas limits and signature
(userOp.paymasterAndData, ) = generateAndSignPaymasterData(
userOp.accountGasLimits = bytes32(abi.encodePacked(uint128(verificationGasLimit), uint128(callGasLimit)));
(userOp.paymasterAndData,) = generateAndSignPaymasterData(
userOp,
PAYMASTER_SIGNER,
paymaster,
Expand Down
Loading

0 comments on commit ebf05c1

Please sign in to comment.