Skip to content

Commit

Permalink
more tests for accounting for premiums
Browse files Browse the repository at this point in the history
  • Loading branch information
ShivaanshK committed Jul 7, 2024
1 parent ff05364 commit a478815
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 51 deletions.
14 changes: 2 additions & 12 deletions contracts/sponsorship/SponsorshipPaymasterWithPremium.sol
Original file line number Diff line number Diff line change
Expand Up @@ -232,15 +232,7 @@ 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.
function _postOp(
PostOpMode,
bytes calldata context,
uint256 actualGasCost,
uint256
)
internal
override
{
function _postOp(PostOpMode, bytes calldata context, uint256 actualGasCost, uint256) internal override {
unchecked {
(address paymasterId, uint32 dynamicMarkup, bytes32 userOpHash) =
abi.decode(context, (address, uint32, bytes32));
Expand All @@ -250,7 +242,7 @@ contract BiconomySponsorshipPaymaster is
// deduct with premium
paymasterIdBalances[paymasterId] -= costIncludingPremium;

if (actualGasCost < costIncludingPremium) {
if (costIncludingPremium > actualGasCost) {
// "collect" premium
uint256 actualPremium = costIncludingPremium - actualGasCost;
paymasterIdBalances[feeCollector] += actualPremium;
Expand Down Expand Up @@ -305,8 +297,6 @@ contract BiconomySponsorshipPaymaster is
revert InvalidPriceMarkup();
}

uint256 maxFeePerGas = userOp.unpackMaxFeePerGas();

// Send 1e6 for No markup
// Send between 0 and 1e6 for discount
uint256 effectiveCost = (requiredPreFund * priceMarkup) / PRICE_DENOMINATOR;
Expand Down
5 changes: 4 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
block_timestamp = 1_680_220_800 # March 31, 2023 at 00:00 GMT
bytecode_hash = "none"
evm_version = "paris" # See https://www.evmdiff.com/features?name=PUSH0&kind=opcode
fuzz = { runs = 1_000 }
gas_reports = ["*"]
optimizer = true
optimizer_runs = 1_000_000
Expand All @@ -19,6 +18,10 @@
gas_reports_ignore = ["LockTest"]
via_ir = true

[fuzz]
runs = 1_000
max_test_rejects = 1_000_000

[profile.ci]
fuzz = { runs = 10_000 }
verbosity = 4
Expand Down
2 changes: 1 addition & 1 deletion test/foundry/base/NexusTestBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ abstract contract NexusTestBase is CheatCodes, BaseEventsAndErrors {
validationGasLimit = validationGasLimit - gasleft();

postopGasLimit = gasleft();
paymaster.postOp(IPaymaster.PostOpMode.opSucceeded, context, 3e4, 2e9);
paymaster.postOp(IPaymaster.PostOpMode.opSucceeded, context, 1e12, 3e6);
postopGasLimit = postopGasLimit - gasleft();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,31 +135,6 @@ contract TestSponsorshipPaymasterWithPremium is NexusTestBase {
}

function test_WithdrawTo() external prankModifier(DAPP_ACCOUNT.addr) {
uint256 depositAmount = 10 ether;
bicoPaymaster.depositFor{ value: depositAmount }(DAPP_ACCOUNT.addr);
uint256 danInitialBalance = DAN_ADDRESS.balance;

vm.expectEmit(true, true, true, true, address(bicoPaymaster));
emit IBiconomySponsorshipPaymaster.GasWithdrawn(DAPP_ACCOUNT.addr, DAN_ADDRESS, depositAmount);
bicoPaymaster.withdrawTo(payable(DAN_ADDRESS), depositAmount);

uint256 dappPaymasterBalance = bicoPaymaster.getBalance(DAPP_ACCOUNT.addr);
assertEq(dappPaymasterBalance, 0 ether);
uint256 expectedDanBalance = danInitialBalance + depositAmount;
assertEq(DAN_ADDRESS.balance, expectedDanBalance);
}

function test_RevertIf_WithdrawToZeroAddress() external prankModifier(DAPP_ACCOUNT.addr) {
vm.expectRevert(abi.encodeWithSelector(CanNotWithdrawToZeroAddress.selector));
bicoPaymaster.withdrawTo(payable(address(0)), 0 ether);
}

function test_RevertIf_WithdrawToExceedsBalance() external prankModifier(DAPP_ACCOUNT.addr) {
vm.expectRevert(abi.encodeWithSelector(InsufficientFundsInGasTank.selector));
bicoPaymaster.withdrawTo(payable(DAN_ADDRESS), 1 ether);
}

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

Expand All @@ -169,14 +144,34 @@ contract TestSponsorshipPaymasterWithPremium is NexusTestBase {
uint48 validAfter = uint48(block.timestamp);

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

// No premium
uint32 premium = 1e6;
userOp.paymasterAndData = generateAndSignPaymasterData(
userOp, PAYMASTER_SIGNER, bicoPaymaster, 3e6, 3e6, DAPP_ACCOUNT.addr, validUntil, validAfter, 1e6
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);

vm.expectEmit(true, false, true, true, address(bicoPaymaster));
emit IBiconomySponsorshipPaymaster.GasBalanceDeducted(DAPP_ACCOUNT.addr, 0, userOpHash);
Expand All @@ -189,7 +184,6 @@ contract TestSponsorshipPaymasterWithPremium is NexusTestBase {
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);

Expand Down Expand Up @@ -226,25 +220,25 @@ contract TestSponsorshipPaymasterWithPremium is NexusTestBase {
ops[0] = userOp;
userOpHash = ENTRYPOINT.getUserOpHash(userOp);

uint256 initialFeeCollectorBalance = bicoPaymaster.getBalance(PAYMASTER_FEE_COLLECTOR.addr);
initialDappPaymasterBalance = bicoPaymaster.getBalance(DAPP_ACCOUNT.addr);
// 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);
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);
uint256 premiumCollected = resultingFeeCollectorPaymasterBalance - initialFeeCollectorBalance;

uint256 expectedGasPayment = totalGasFeesCharged - premiumCollected;
uint256 expectedPremium = expectedGasPayment / 10;

assertEq(premiumCollected, expectedPremium);
}

function test_RevertIf_ValidatePaymasterUserOpWithIncorrectSignatureLength() external {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.26;

import { console2 } from "forge-std/src/Console2.sol";
import { console2 } from "forge-std/src/console2.sol";
import { stdMath } from "forge-std/src/Test.sol";
import { NexusTestBase } from "../../base/NexusTestBase.sol";
import { IBiconomySponsorshipPaymaster } from "../../../../contracts/interfaces/IBiconomySponsorshipPaymaster.sol";
import { BiconomySponsorshipPaymaster } from "../../../../contracts/sponsorship/SponsorshipPaymasterWithPremium.sol";
import { MockToken } from "./../../../../lib/nexus/contracts/mocks/MockToken.sol";
import { PackedUserOperation } from "account-abstraction/contracts/interfaces/PackedUserOperation.sol";

contract TestFuzz_SponsorshipPaymasterWithPremium is NexusTestBase {
BiconomySponsorshipPaymaster public bicoPaymaster;
Expand Down Expand Up @@ -97,4 +99,67 @@ contract TestFuzz_SponsorshipPaymasterWithPremium is NexusTestBase {
assertEq(token.balanceOf(address(bicoPaymaster)), 0);
assertEq(token.balanceOf(ALICE_ADDRESS), mintAmount);
}

function testFuzz_ValidatePaymasterAndPostOpWithPremium(uint32 premium) external {
vm.assume(premium <= 2e6);
vm.assume(premium > 1e6);

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

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

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

uint256 initialFeeCollectorBalance = bicoPaymaster.getBalance(PAYMASTER_FEE_COLLECTOR.addr);
initialDappPaymasterBalance = bicoPaymaster.getBalance(DAPP_ACCOUNT.addr);
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));

uint256 resultingDappPaymasterBalance = bicoPaymaster.getBalance(DAPP_ACCOUNT.addr);
uint256 resultingFeeCollectorPaymasterBalance = bicoPaymaster.getBalance(PAYMASTER_FEE_COLLECTOR.addr);

uint256 totalGasFeesCharged = initialDappPaymasterBalance - resultingDappPaymasterBalance;
uint256 premiumCollected = resultingFeeCollectorPaymasterBalance - initialFeeCollectorBalance;

uint256 expectedPremium = totalGasFeesCharged - (totalGasFeesCharged * 1e6) / premium;

console2.log(premiumCollected);
console2.log(expectedPremium);
console2.log(stdMath.percentDelta(premiumCollected, expectedPremium));
// less than 0.01% difference between actual and expected values
assert(stdMath.percentDelta(premiumCollected, expectedPremium) < 1e14);
}
}

0 comments on commit a478815

Please sign in to comment.