Skip to content

Commit 4ba9a4b

Browse files
Merge branch 'feat/paymaster-foundry-tests' of https://github.com/bcnmy/gasdaddy into feat/paymaster-foundry-tests
2 parents 38f9891 + 81abba5 commit 4ba9a4b

6 files changed

+253
-171
lines changed

contracts/common/Errors.sol

+35
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,43 @@ contract BiconomySponsorshipPaymasterErrors {
3232
*/
3333
error VerifyingSignerCanNotBeContract();
3434

35+
/**
36+
* @notice Throws when ETH withdrawal fails
37+
*/
38+
error WithdrawalFailed();
39+
40+
/**
41+
* @notice Throws when insufficient funds to withdraw
42+
*/
43+
error InsufficientFundsInGasTank();
44+
45+
/**
46+
* @notice Throws when invalid signature length in paymasterAndData
47+
*/
48+
error InvalidSignatureLength();
49+
50+
/**
51+
* @notice Throws when invalid signature length in paymasterAndData
52+
*/
53+
error InvalidPriceMarkup();
54+
55+
/**
56+
* @notice Throws when insufficient funds for paymasterid
57+
*/
58+
error InsufficientFundsForPaymasterId();
59+
60+
/**
61+
* @notice Throws when calling deposit()
62+
*/
63+
error UseDepositForInstead();
64+
3565
/**
3666
* @notice Throws when trying to withdraw to address(0)
3767
*/
3868
error CanNotWithdrawToZeroAddress();
69+
70+
/**
71+
* @notice Throws when trying postOpCost is too high
72+
*/
73+
error PostOpCostTooHigh();
3974
}

contracts/sponsorship/SponsorshipPaymasterWithPremium.sol

+51-42
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ contract BiconomySponsorshipPaymaster is
3939

4040
address public verifyingSigner;
4141
address public feeCollector;
42-
uint48 public postopCost;
42+
uint48 public postOpCost;
4343
uint32 private constant PRICE_DENOMINATOR = 1e6;
4444

4545
// note: could rename to PAYMASTER_ID_OFFSET
@@ -55,8 +55,11 @@ contract BiconomySponsorshipPaymaster is
5555
)
5656
BasePaymaster(_owner, _entryPoint)
5757
{
58-
// TODO
59-
// Check for zero address
58+
if (_verifyingSigner == address(0)) {
59+
revert VerifyingSignerCanNotBeZero();
60+
} else if (_feeCollector == address(0)) {
61+
revert FeeCollectorCanNotBeZero();
62+
}
6063
verifyingSigner = _verifyingSigner;
6164
feeCollector = _feeCollector;
6265
}
@@ -123,17 +126,19 @@ contract BiconomySponsorshipPaymaster is
123126
* @notice only to be called by the owner of the contract.
124127
*/
125128
function setPostopCost(uint48 value) external payable onlyOwner {
126-
require(value <= 200_000, "Gas overhead too high");
127-
uint256 oldValue = postopCost;
128-
postopCost = value;
129+
if (value > 200_000) {
130+
revert PostOpCostTooHigh();
131+
}
132+
uint256 oldValue = postOpCost;
133+
postOpCost = value;
129134
emit PostopCostChanged(oldValue, value);
130135
}
131136

132137
/**
133138
* @dev Override the default implementation.
134139
*/
135140
function deposit() external payable virtual override {
136-
revert("Use depositFor() instead");
141+
revert UseDepositForInstead();
137142
}
138143

139144
/**
@@ -155,15 +160,19 @@ contract BiconomySponsorshipPaymaster is
155160
function withdrawTo(address payable withdrawAddress, uint256 amount) external override nonReentrant {
156161
if (withdrawAddress == address(0)) revert CanNotWithdrawToZeroAddress();
157162
uint256 currentBalance = paymasterIdBalances[msg.sender];
158-
require(amount <= currentBalance, "Sponsorship Paymaster: Insufficient funds to withdraw from gas tank");
163+
if (amount > currentBalance) {
164+
revert InsufficientFundsInGasTank();
165+
}
159166
paymasterIdBalances[msg.sender] = currentBalance - amount;
160167
entryPoint.withdrawTo(withdrawAddress, amount);
161168
emit GasWithdrawn(msg.sender, withdrawAddress, amount);
162169
}
163170

164-
function withdrawEth(address payable recipient, uint256 amount) external onlyOwner {
171+
function withdrawEth(address payable recipient, uint256 amount) external onlyOwner nonReentrant {
165172
(bool success,) = recipient.call{ value: amount }("");
166-
require(success, "withdraw failed");
173+
if (!success) {
174+
revert WithdrawalFailed();
175+
}
167176
}
168177

169178
/**
@@ -225,20 +234,19 @@ contract BiconomySponsorshipPaymaster is
225234
bytes calldata signature
226235
)
227236
{
228-
paymasterId = address(bytes20(paymasterAndData[VALID_PND_OFFSET:VALID_PND_OFFSET + 20]));
229-
validUntil = uint48(bytes6(paymasterAndData[VALID_PND_OFFSET + 20:VALID_PND_OFFSET + 26]));
230-
validAfter = uint48(bytes6(paymasterAndData[VALID_PND_OFFSET + 26:VALID_PND_OFFSET + 32]));
231-
priceMarkup = uint32(bytes4(paymasterAndData[VALID_PND_OFFSET + 32:VALID_PND_OFFSET + 36]));
232-
signature = paymasterAndData[VALID_PND_OFFSET + 36:];
237+
unchecked {
238+
paymasterId = address(bytes20(paymasterAndData[VALID_PND_OFFSET:VALID_PND_OFFSET + 20]));
239+
validUntil = uint48(bytes6(paymasterAndData[VALID_PND_OFFSET + 20:VALID_PND_OFFSET + 26]));
240+
validAfter = uint48(bytes6(paymasterAndData[VALID_PND_OFFSET + 26:VALID_PND_OFFSET + 32]));
241+
priceMarkup = uint32(bytes4(paymasterAndData[VALID_PND_OFFSET + 32:VALID_PND_OFFSET + 36]));
242+
signature = paymasterAndData[VALID_PND_OFFSET + 36:];
243+
}
233244
}
234245

235246
/// @notice Performs post-operation tasks, such as deducting the sponsored gas cost from the paymasterId's balance
236247
/// @dev This function is called after a user operation has been executed or reverted.
237248
/// @param context The context containing the token amount and user sender address.
238249
/// @param actualGasCost The actual gas cost of the transaction.
239-
/// @param actualUserOpFeePerGas - the gas price this UserOp pays. This value is based on the UserOp's maxFeePerGas
240-
// and maxPriorityFee (and basefee)
241-
// It is not the same as tx.gasprice, which is what the bundler pays.
242250
function _postOp(
243251
PostOpMode,
244252
bytes calldata context,
@@ -249,23 +257,24 @@ contract BiconomySponsorshipPaymaster is
249257
override
250258
{
251259
unchecked {
252-
(address paymasterId, uint32 dynamicMarkup, bytes32 userOpHash) =
260+
(address paymasterId, uint32 dynamicAdjustment, bytes32 userOpHash) =
253261
abi.decode(context, (address, uint32, bytes32));
254262

255-
uint256 balToDeduct = actualGasCost + postopCost * actualUserOpFeePerGas;
256-
257-
uint256 costIncludingPremium = (balToDeduct * dynamicMarkup) / PRICE_DENOMINATOR;
263+
uint256 totalGasCost = actualGasCost + (postOpCost * actualUserOpFeePerGas);
264+
uint256 adjustedGasCost = (totalGasCost * dynamicAdjustment) / PRICE_DENOMINATOR;
258265

259-
// deduct with premium
260-
paymasterIdBalances[paymasterId] -= costIncludingPremium;
266+
// Deduct the adjusted cost
267+
paymasterIdBalances[paymasterId] -= adjustedGasCost;
261268

262-
uint256 actualPremium = costIncludingPremium - balToDeduct;
263-
// "collect" premium
264-
paymasterIdBalances[feeCollector] += actualPremium;
269+
if (adjustedGasCost > actualGasCost) {
270+
// Add premium to fee
271+
uint256 premium = adjustedGasCost - actualGasCost;
272+
paymasterIdBalances[feeCollector] += premium;
273+
// Review if we should emit adjustedGasCost as well
274+
emit PremiumCollected(paymasterId, premium);
275+
}
265276

266-
emit GasBalanceDeducted(paymasterId, costIncludingPremium, userOpHash);
267-
// Review if we should emit balToDeduct as well
268-
emit PremiumCollected(paymasterId, actualPremium);
277+
emit GasBalanceDeducted(paymasterId, adjustedGasCost, userOpHash);
269278
}
270279
}
271280

@@ -294,10 +303,9 @@ contract BiconomySponsorshipPaymaster is
294303
//ECDSA library supports both 64 and 65-byte long signatures.
295304
// we only "require" it here so that the revert reason on invalid signature will be of "VerifyingPaymaster", and
296305
// not "ECDSA"
297-
require(
298-
signature.length == 64 || signature.length == 65,
299-
"VerifyingPaymaster: invalid signature length in paymasterAndData"
300-
);
306+
if (signature.length != 64 && signature.length != 65) {
307+
revert InvalidSignatureLength();
308+
}
301309

302310
bool validSig = verifyingSigner.isValidSignatureNow(
303311
ECDSA_solady.toEthSignedMessageHash(getHash(userOp, paymasterId, validUntil, validAfter, priceMarkup)),
@@ -309,18 +317,19 @@ contract BiconomySponsorshipPaymaster is
309317
return ("", _packValidationData(true, validUntil, validAfter));
310318
}
311319

312-
require(priceMarkup <= 2e6 && priceMarkup > 0, "Sponsorship Paymaster: Invalid markup %");
313-
314-
uint256 maxFeePerGas = userOp.unpackMaxFeePerGas();
320+
if (priceMarkup > 2e6 || priceMarkup == 0) {
321+
revert InvalidPriceMarkup();
322+
}
315323

316324
// Send 1e6 for No markup
317325
// Send between 0 and 1e6 for discount
318-
uint256 effectiveCost = ((requiredPreFund + (postopCost * maxFeePerGas)) * priceMarkup) / PRICE_DENOMINATOR;
326+
uint256 effectiveCost = (requiredPreFund * priceMarkup) / PRICE_DENOMINATOR;
319327

320-
require(
321-
effectiveCost <= paymasterIdBalances[paymasterId],
322-
"Sponsorship Paymaster: paymasterId does not have enough deposit"
323-
);
328+
if (effectiveCost > paymasterIdBalances[paymasterId]) {
329+
revert InsufficientFundsForPaymasterId();
330+
}
331+
332+
context = abi.encode(paymasterId, priceMarkup, userOpHash);
324333

325334
context = abi.encode(paymasterId, priceMarkup, userOpHash);
326335

foundry.toml

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
block_timestamp = 1_680_220_800 # March 31, 2023 at 00:00 GMT
66
bytecode_hash = "none"
77
evm_version = "paris" # See https://www.evmdiff.com/features?name=PUSH0&kind=opcode
8-
fuzz = { runs = 1_000 }
98
gas_reports = ["*"]
109
optimizer = true
1110
optimizer_runs = 1_000_000
@@ -19,6 +18,10 @@
1918
gas_reports_ignore = ["LockTest"]
2019
via_ir = true
2120

21+
[fuzz]
22+
runs = 1_000
23+
max_test_rejects = 1_000_000
24+
2225
[profile.ci]
2326
fuzz = { runs = 10_000 }
2427
verbosity = 4

0 commit comments

Comments
 (0)