@@ -51,30 +51,25 @@ contract BiconomyTokenPaymaster is
51
51
// State variables
52
52
address public verifyingSigner; // entity used to provide external token price and markup
53
53
uint256 public unaccountedGas;
54
- uint256 public independentPriceMarkup; // price markup used for independent mode
54
+ uint32 public independentPriceMarkup; // price markup used for independent mode
55
55
uint256 public priceExpiryDuration; // oracle price expiry duration
56
56
IOracle public nativeAssetToUsdOracle; // ETH -> USD price oracle
57
57
mapping (address => TokenInfo) public independentTokenDirectory; // mapping of token address => info for tokens
58
58
// supported in // independent mode
59
59
60
- // PAYMASTER_ID_OFFSET
61
- // Note: Temp
62
60
uint256 private constant _UNACCOUNTED_GAS_LIMIT = 200_000 ; // Limit for unaccounted gas cost
63
- uint256 private constant _PRICE_DENOMINATOR = 1e6 ; // Denominator used when calculating cost with price markup
64
- uint256 private constant _MAX_PRICE_MARKUP = 2e6 ; // 100% premium on price (2e6/PRICE_DENOMINATOR)
65
-
66
- // Note: _priceExpiryDuration is common for all the feeds.
67
- // Note: _independentPriceMarkup is common for all the independent tokens.
68
- // Todo: add cases when uniswap is not available
69
- // Note: swapTokenAndDeposit: we may not need to keep this onlyOwner
61
+ uint32 private constant _PRICE_DENOMINATOR = 1e6 ; // Denominator used when calculating cost with price markup
62
+ uint32 private constant _MAX_PRICE_MARKUP = 2e6 ; // 100% premium on price (2e6/PRICE_DENOMINATOR)
63
+ uint256 private immutable _NATIVE_TOKEN_DECIMALS;
70
64
71
65
constructor (
72
66
address owner ,
73
67
address verifyingSignerArg ,
74
68
IEntryPoint entryPoint ,
75
69
uint256 unaccountedGasArg ,
76
- uint256 independentPriceMarkupArg , // price markup used for independent mode
70
+ uint32 independentPriceMarkupArg , // price markup used for independent mode
77
71
uint256 priceExpiryDurationArg ,
72
+ uint256 nativeAssetDecimalsArg ,
78
73
IOracle nativeAssetToUsdOracleArg ,
79
74
ISwapRouter uniswapRouterArg ,
80
75
address wrappedNativeArg ,
@@ -87,6 +82,7 @@ contract BiconomyTokenPaymaster is
87
82
BasePaymaster (owner, entryPoint)
88
83
Uniswapper (uniswapRouterArg, wrappedNativeArg, swappableTokens, swappableTokenPoolFeeTiers)
89
84
{
85
+ _NATIVE_TOKEN_DECIMALS = nativeAssetDecimalsArg;
90
86
if (_isContract (verifyingSignerArg)) {
91
87
revert VerifyingSignerCanNotBeContract ();
92
88
}
@@ -107,6 +103,7 @@ contract BiconomyTokenPaymaster is
107
103
// ETH -> USD will always have 8 decimals for Chainlink and TWAP
108
104
revert InvalidOracleDecimals ();
109
105
}
106
+ require (block .timestamp >= priceExpiryDurationArg, "Price expiry duration cannot be in the past " );
110
107
111
108
// Set state variables
112
109
assembly ("memory-safe" ) {
@@ -126,11 +123,6 @@ contract BiconomyTokenPaymaster is
126
123
independentTokenDirectory[independentTokensArg[i]] =
127
124
TokenInfo (oraclesArg[i], 10 ** IERC20Metadata (independentTokensArg[i]).decimals ());
128
125
}
129
- // Approve swappable tokens for max amount
130
- uint256 length = swappableTokens.length ;
131
- for (uint256 i; i < length; i++ ) {
132
- IERC20 (swappableTokens[i]).approve (address (uniswapRouterArg), type (uint256 ).max);
133
- }
134
126
}
135
127
136
128
/**
@@ -207,8 +199,8 @@ contract BiconomyTokenPaymaster is
207
199
* @dev Set a new verifying signer address.
208
200
* Can only be called by the owner of the contract.
209
201
* @param newVerifyingSigner The new address to be set as the verifying signer.
210
- * @notice If _newVerifyingSigner is set to zero address, it will revert with an error.
211
- * After setting the new signer address, it will emit an event VerifyingSignerChanged .
202
+ * @notice If newVerifyingSigner is set to zero address, it will revert with an error.
203
+ * After setting the new signer address, it will emit an event UpdatedVerifyingSigner .
212
204
*/
213
205
function setSigner (address newVerifyingSigner ) external payable onlyOwner {
214
206
if (_isContract (newVerifyingSigner)) revert VerifyingSignerCanNotBeContract ();
@@ -243,24 +235,25 @@ contract BiconomyTokenPaymaster is
243
235
* @param newIndependentPriceMarkup The new value to be set as the price markup
244
236
* @notice only to be called by the owner of the contract.
245
237
*/
246
- function setPriceMarkup (uint256 newIndependentPriceMarkup ) external payable onlyOwner {
238
+ function setPriceMarkup (uint32 newIndependentPriceMarkup ) external payable onlyOwner {
247
239
if (newIndependentPriceMarkup > _MAX_PRICE_MARKUP || newIndependentPriceMarkup < _PRICE_DENOMINATOR) {
248
240
// Not between 0% and 100% markup
249
241
revert InvalidPriceMarkup ();
250
242
}
251
- uint256 oldIndependentPriceMarkup = independentPriceMarkup;
243
+ uint32 oldIndependentPriceMarkup = independentPriceMarkup;
252
244
assembly ("memory-safe" ) {
253
245
sstore (independentPriceMarkup.slot, newIndependentPriceMarkup)
254
246
}
255
247
emit UpdatedFixedPriceMarkup (oldIndependentPriceMarkup, newIndependentPriceMarkup);
256
248
}
257
249
258
250
/**
259
- * @dev Set a new priceMarkup value .
260
- * @param newPriceExpiryDuration The new value to be set as the unaccounted gas value
251
+ * @dev Set a new price expiry duration .
252
+ * @param newPriceExpiryDuration The new value to be set as the price expiry duration
261
253
* @notice only to be called by the owner of the contract.
262
254
*/
263
255
function setPriceExpiryDuration (uint256 newPriceExpiryDuration ) external payable onlyOwner {
256
+ require (block .timestamp >= newPriceExpiryDuration, "Price expiry duration cannot be in the past " );
264
257
uint256 oldPriceExpiryDuration = priceExpiryDuration;
265
258
assembly ("memory-safe" ) {
266
259
sstore (priceExpiryDuration.slot, newPriceExpiryDuration)
@@ -293,7 +286,7 @@ contract BiconomyTokenPaymaster is
293
286
* @param oracle The oracle to use for the specified token
294
287
* @notice only to be called by the owner of the contract.
295
288
*/
296
- function updateTokenDirectory (address tokenAddress , IOracle oracle ) external payable onlyOwner {
289
+ function addToTokenDirectory (address tokenAddress , IOracle oracle ) external payable onlyOwner {
297
290
if (oracle.decimals () != 8 ) {
298
291
// Token -> USD will always have 8 decimals
299
292
revert InvalidOracleDecimals ();
@@ -302,7 +295,17 @@ contract BiconomyTokenPaymaster is
302
295
uint8 decimals = IERC20Metadata (tokenAddress).decimals ();
303
296
independentTokenDirectory[tokenAddress] = TokenInfo (oracle, 10 ** decimals);
304
297
305
- emit UpdatedTokenDirectory (tokenAddress, oracle, decimals);
298
+ emit AddedToTokenDirectory (tokenAddress, oracle, decimals);
299
+ }
300
+
301
+ /**
302
+ * @dev Remove a token from the independentTokenDirectory mapping.
303
+ * @param tokenAddress The token address to remove from directory
304
+ * @notice only to be called by the owner of the contract.
305
+ */
306
+ function removeFromTokenDirectory (address tokenAddress ) external payable onlyOwner {
307
+ delete independentTokenDirectory[tokenAddress];
308
+ emit RemovedFromTokenDirectory (tokenAddress );
306
309
}
307
310
308
311
/**
@@ -326,6 +329,7 @@ contract BiconomyTokenPaymaster is
326
329
for (uint256 i = 0 ; i < tokenAddresses.length ; ++ i) {
327
330
_setTokenPool (tokenAddresses[i], poolFeeTiers[i]);
328
331
}
332
+ emit SwappableTokensAdded (tokenAddresses);
329
333
}
330
334
331
335
/**
@@ -342,7 +346,7 @@ contract BiconomyTokenPaymaster is
342
346
)
343
347
external
344
348
payable
345
- onlyOwner
349
+ nonReentrant
346
350
{
347
351
// Swap tokens for WETH
348
352
uint256 amountOut = _swapTokenToWeth (tokenAddress, tokenAmount, minEthAmountRecevied);
@@ -352,6 +356,7 @@ contract BiconomyTokenPaymaster is
352
356
// Deposit ETH into EP
353
357
entryPoint.depositTo { value: amountOut }(address (this ));
354
358
}
359
+ emit TokensSwappedAndRefilledEntryPoint (tokenAddress, tokenAmount, amountOut, msg .sender );
355
360
}
356
361
357
362
/**
@@ -385,7 +390,7 @@ contract BiconomyTokenPaymaster is
385
390
uint48 validUntil ,
386
391
uint48 validAfter ,
387
392
address tokenAddress ,
388
- uint128 tokenPrice ,
393
+ uint256 tokenPrice ,
389
394
uint32 externalPriceMarkup
390
395
)
391
396
public
@@ -453,8 +458,7 @@ contract BiconomyTokenPaymaster is
453
458
uint48 validUntil ,
454
459
uint48 validAfter ,
455
460
address tokenAddress ,
456
- // Review if uint128 is enough
457
- uint128 tokenPrice , // NotE: what backend should pass is token/native * 10^token decimals
461
+ uint256 tokenPrice , // NotE: what backend should pass is token/native * 10^token decimals
458
462
uint32 externalPriceMarkup ,
459
463
bytes memory signature
460
464
) = modeSpecificData.parseExternalModeSpecificData ();
@@ -485,15 +489,15 @@ contract BiconomyTokenPaymaster is
485
489
{
486
490
uint256 maxFeePerGas = UserOperationLib.unpackMaxFeePerGas (userOp);
487
491
tokenAmount = ((maxCost + maxPenalty + (unaccountedGas * maxFeePerGas)) * externalPriceMarkup * tokenPrice)
488
- / (1e18 * _PRICE_DENOMINATOR);
492
+ / (_NATIVE_TOKEN_DECIMALS * _PRICE_DENOMINATOR);
489
493
}
490
494
491
495
// Transfer full amount to this address. Unused amount will be refunded in postOP
492
496
SafeTransferLib.safeTransferFrom (tokenAddress, userOp.sender, address (this ), tokenAmount);
493
497
494
498
// deduct max penalty from the token amount we pass to the postOp
495
499
// so we don't refund it at postOp
496
- context = abi.encode (userOp.sender, tokenAddress, tokenAmount- ((maxPenalty* tokenPrice)/ 1e18 ), tokenPrice, externalPriceMarkup, userOpHash);
500
+ context = abi.encode (userOp.sender, tokenAddress, tokenAmount- ((maxPenalty* tokenPrice)/ _NATIVE_TOKEN_DECIMALS ), tokenPrice, externalPriceMarkup, userOpHash);
497
501
validationData = _packValidationData (false , validUntil, validAfter);
498
502
} else if (mode == PaymasterMode.INDEPENDENT) {
499
503
// Use only oracles for the token specified in modeSpecificData
@@ -504,21 +508,24 @@ contract BiconomyTokenPaymaster is
504
508
// Get address for token used to pay
505
509
address tokenAddress = modeSpecificData.parseIndependentModeSpecificData ();
506
510
uint256 tokenPrice = _getPrice (tokenAddress);
511
+ if (tokenPrice == 0 ) {
512
+ revert TokenNotSupported ();
513
+ }
507
514
uint256 tokenAmount;
508
515
509
516
// TODO: Account for penalties here
510
517
{
511
518
// Calculate token amount to precharge
512
519
uint256 maxFeePerGas = UserOperationLib.unpackMaxFeePerGas (userOp);
513
520
tokenAmount = ((maxCost + maxPenalty + (unaccountedGas * maxFeePerGas)) * independentPriceMarkup * tokenPrice)
514
- / (1e18 * _PRICE_DENOMINATOR);
521
+ / (_NATIVE_TOKEN_DECIMALS * _PRICE_DENOMINATOR);
515
522
}
516
523
517
524
// Transfer full amount to this address. Unused amount will be refunded in postOP
518
525
SafeTransferLib.safeTransferFrom (tokenAddress, userOp.sender, address (this ), tokenAmount);
519
526
520
527
context =
521
- abi.encode (userOp.sender, tokenAddress, tokenAmount- ((maxPenalty* tokenPrice)/ 1e18 ), tokenPrice, independentPriceMarkup, userOpHash);
528
+ abi.encode (userOp.sender, tokenAddress, tokenAmount- ((maxPenalty* tokenPrice)/ _NATIVE_TOKEN_DECIMALS ), tokenPrice, independentPriceMarkup, userOpHash);
522
529
validationData = 0 ; // Validation success and price is valid indefinetly
523
530
}
524
531
}
@@ -545,14 +552,14 @@ contract BiconomyTokenPaymaster is
545
552
address tokenAddress ,
546
553
uint256 prechargedAmount ,
547
554
uint192 tokenPrice ,
548
- uint256 appliedPriceMarkup ,
555
+ uint32 appliedPriceMarkup ,
549
556
bytes32 userOpHash
550
- ) = abi.decode (context, (address , address , uint256 , uint192 , uint256 , bytes32 ));
557
+ ) = abi.decode (context, (address , address , uint256 , uint192 , uint32 , bytes32 ));
551
558
552
559
// Calculate the actual cost in tokens based on the actual gas cost and the token price
553
560
uint256 actualTokenAmount = (
554
561
(actualGasCost + (unaccountedGas * actualUserOpFeePerGas)) * appliedPriceMarkup * tokenPrice
555
- ) / (1e18 * _PRICE_DENOMINATOR);
562
+ ) / (_NATIVE_TOKEN_DECIMALS * _PRICE_DENOMINATOR);
556
563
if (prechargedAmount > actualTokenAmount) {
557
564
// If the user was overcharged, refund the excess tokens
558
565
uint256 refundAmount = prechargedAmount - actualTokenAmount;
@@ -562,7 +569,7 @@ contract BiconomyTokenPaymaster is
562
569
563
570
// Todo: Review events and what we need to emit.
564
571
emit PaidGasInTokens (
565
- userOpSender, tokenAddress, actualGasCost, actualTokenAmount, appliedPriceMarkup, userOpHash
572
+ userOpSender, tokenAddress, actualGasCost, actualTokenAmount, appliedPriceMarkup, tokenPrice, userOpHash
566
573
);
567
574
}
568
575
@@ -578,8 +585,8 @@ contract BiconomyTokenPaymaster is
578
585
}
579
586
580
587
// Calculate price by using token and native oracle
581
- uint192 tokenPrice = _fetchPrice (tokenInfo.oracle);
582
- uint192 nativeAssetPrice = _fetchPrice (nativeAssetToUsdOracle);
588
+ uint256 tokenPrice = _fetchPrice (tokenInfo.oracle);
589
+ uint256 nativeAssetPrice = _fetchPrice (nativeAssetToUsdOracle);
583
590
584
591
// Adjust to token decimals
585
592
price = (nativeAssetPrice * tokenInfo.decimals) / tokenPrice;
@@ -590,15 +597,15 @@ contract BiconomyTokenPaymaster is
590
597
/// @param oracle The oracle contract to fetch the price from.
591
598
/// @return price The latest price fetched from the oracle.
592
599
/// Note: We could do this using oracle aggregator, so we can also use Pyth. or Twap based oracle and just not chainlink.
593
- function _fetchPrice (IOracle oracle ) internal view returns (uint192 price ) {
600
+ function _fetchPrice (IOracle oracle ) internal view returns (uint256 price ) {
594
601
(, int256 answer ,, uint256 updatedAt ,) = oracle.latestRoundData ();
595
602
if (answer <= 0 ) {
596
603
revert OraclePriceNotPositive ();
597
604
}
598
605
if (updatedAt < block .timestamp - priceExpiryDuration) {
599
606
revert OraclePriceExpired ();
600
607
}
601
- price = uint192 ( int192 ( answer) );
608
+ price = uint256 ( answer);
602
609
}
603
610
604
611
function _withdrawERC20 (IERC20 token , address target , uint256 amount ) private {
0 commit comments