Skip to content

Commit 7d9fb24

Browse files
fix: remediations as per discussion
1 parent d113079 commit 7d9fb24

9 files changed

+79
-60
lines changed

contracts/interfaces/IBiconomyTokenPaymaster.sol

+9-5
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ interface IBiconomyTokenPaymaster {
1818
}
1919

2020
event UpdatedUnaccountedGas(uint256 indexed oldValue, uint256 indexed newValue);
21-
event UpdatedFixedPriceMarkup(uint256 indexed oldValue, uint256 indexed newValue);
21+
event UpdatedFixedPriceMarkup(uint32 indexed oldValue, uint32 indexed newValue);
2222
event UpdatedVerifyingSigner(address indexed oldSigner, address indexed newSigner, address indexed actor);
2323
event UpdatedFeeCollector(address indexed oldFeeCollector, address indexed newFeeCollector, address indexed actor);
2424
event UpdatedPriceExpiryDuration(uint256 indexed oldValue, uint256 indexed newValue);
@@ -30,23 +30,27 @@ interface IBiconomyTokenPaymaster {
3030
address indexed token,
3131
uint256 nativeCharge,
3232
uint256 tokenCharge,
33-
uint256 priceMarkup,
33+
uint32 priceMarkup,
34+
uint256 tokenPrice,
3435
bytes32 indexed userOpHash
3536
);
3637
event Received(address indexed sender, uint256 value);
3738
event TokensWithdrawn(address indexed token, address indexed to, uint256 indexed amount, address actor);
38-
event UpdatedTokenDirectory(address indexed tokenAddress, IOracle indexed oracle, uint8 decimals);
39+
event AddedToTokenDirectory(address indexed tokenAddress, IOracle indexed oracle, uint8 decimals);
40+
event RemovedFromTokenDirectory(address indexed tokenAddress);
3941
event UpdatedNativeAssetOracle(IOracle indexed oldOracle, IOracle indexed newOracle);
42+
event TokensSwappedAndRefilledEntryPoint(address indexed tokenAddress, uint256 indexed tokenAmount, uint256 indexed amountOut, address actor);
43+
event SwappableTokensAdded(address[] indexed tokenAddresses);
4044

4145
function setSigner(address newVerifyingSigner) external payable;
4246

4347
function setUnaccountedGas(uint256 value) external payable;
4448

45-
function setPriceMarkup(uint256 newUnaccountedGas) external payable;
49+
function setPriceMarkup(uint32 newPriceMarkup) external payable;
4650

4751
function setPriceExpiryDuration(uint256 newPriceExpiryDuration) external payable;
4852

4953
function setNativeAssetToUsdOracle(IOracle oracle) external payable;
5054

51-
function updateTokenDirectory(address tokenAddress, IOracle oracle) external payable;
55+
function addToTokenDirectory(address tokenAddress, IOracle oracle) external payable;
5256
}

contracts/libraries/TokenPaymasterParserLib.sol

+4-4
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,17 @@ library TokenPaymasterParserLib {
3131
uint48 validUntil,
3232
uint48 validAfter,
3333
address tokenAddress,
34-
uint128 tokenPrice, // Review: why uint128 and not uint256. in independent mode it is uint256
34+
uint256 tokenPrice, // Review: why uint128 and not uint256. in independent mode it is uint256
3535
uint32 externalPriceMarkup,
3636
bytes memory signature
3737
)
3838
{
3939
validUntil = uint48(bytes6(modeSpecificData[:6]));
4040
validAfter = uint48(bytes6(modeSpecificData[6:12]));
4141
tokenAddress = address(bytes20(modeSpecificData[12:32]));
42-
tokenPrice = uint128(bytes16(modeSpecificData[32:48]));
43-
externalPriceMarkup = uint32(bytes4(modeSpecificData[48:52]));
44-
signature = modeSpecificData[52:];
42+
tokenPrice = uint256(bytes32(modeSpecificData[32:64]));
43+
externalPriceMarkup = uint32(bytes4(modeSpecificData[64:68]));
44+
signature = modeSpecificData[68:];
4545
}
4646

4747
function parseIndependentModeSpecificData(

contracts/token/BiconomyTokenPaymaster.sol

+47-40
Original file line numberDiff line numberDiff line change
@@ -51,30 +51,25 @@ contract BiconomyTokenPaymaster is
5151
// State variables
5252
address public verifyingSigner; // entity used to provide external token price and markup
5353
uint256 public unaccountedGas;
54-
uint256 public independentPriceMarkup; // price markup used for independent mode
54+
uint32 public independentPriceMarkup; // price markup used for independent mode
5555
uint256 public priceExpiryDuration; // oracle price expiry duration
5656
IOracle public nativeAssetToUsdOracle; // ETH -> USD price oracle
5757
mapping(address => TokenInfo) public independentTokenDirectory; // mapping of token address => info for tokens
5858
// supported in // independent mode
5959

60-
// PAYMASTER_ID_OFFSET
61-
// Note: Temp
6260
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;
7064

7165
constructor(
7266
address owner,
7367
address verifyingSignerArg,
7468
IEntryPoint entryPoint,
7569
uint256 unaccountedGasArg,
76-
uint256 independentPriceMarkupArg, // price markup used for independent mode
70+
uint32 independentPriceMarkupArg, // price markup used for independent mode
7771
uint256 priceExpiryDurationArg,
72+
uint256 nativeAssetDecimalsArg,
7873
IOracle nativeAssetToUsdOracleArg,
7974
ISwapRouter uniswapRouterArg,
8075
address wrappedNativeArg,
@@ -87,6 +82,7 @@ contract BiconomyTokenPaymaster is
8782
BasePaymaster(owner, entryPoint)
8883
Uniswapper(uniswapRouterArg, wrappedNativeArg, swappableTokens, swappableTokenPoolFeeTiers)
8984
{
85+
_NATIVE_TOKEN_DECIMALS = nativeAssetDecimalsArg;
9086
if (_isContract(verifyingSignerArg)) {
9187
revert VerifyingSignerCanNotBeContract();
9288
}
@@ -107,6 +103,7 @@ contract BiconomyTokenPaymaster is
107103
// ETH -> USD will always have 8 decimals for Chainlink and TWAP
108104
revert InvalidOracleDecimals();
109105
}
106+
require(block.timestamp >= priceExpiryDurationArg, "Price expiry duration cannot be in the past");
110107

111108
// Set state variables
112109
assembly ("memory-safe") {
@@ -126,11 +123,6 @@ contract BiconomyTokenPaymaster is
126123
independentTokenDirectory[independentTokensArg[i]] =
127124
TokenInfo(oraclesArg[i], 10 ** IERC20Metadata(independentTokensArg[i]).decimals());
128125
}
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-
}
134126
}
135127

136128
/**
@@ -207,8 +199,8 @@ contract BiconomyTokenPaymaster is
207199
* @dev Set a new verifying signer address.
208200
* Can only be called by the owner of the contract.
209201
* @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.
212204
*/
213205
function setSigner(address newVerifyingSigner) external payable onlyOwner {
214206
if (_isContract(newVerifyingSigner)) revert VerifyingSignerCanNotBeContract();
@@ -243,24 +235,25 @@ contract BiconomyTokenPaymaster is
243235
* @param newIndependentPriceMarkup The new value to be set as the price markup
244236
* @notice only to be called by the owner of the contract.
245237
*/
246-
function setPriceMarkup(uint256 newIndependentPriceMarkup) external payable onlyOwner {
238+
function setPriceMarkup(uint32 newIndependentPriceMarkup) external payable onlyOwner {
247239
if (newIndependentPriceMarkup > _MAX_PRICE_MARKUP || newIndependentPriceMarkup < _PRICE_DENOMINATOR) {
248240
// Not between 0% and 100% markup
249241
revert InvalidPriceMarkup();
250242
}
251-
uint256 oldIndependentPriceMarkup = independentPriceMarkup;
243+
uint32 oldIndependentPriceMarkup = independentPriceMarkup;
252244
assembly ("memory-safe") {
253245
sstore(independentPriceMarkup.slot, newIndependentPriceMarkup)
254246
}
255247
emit UpdatedFixedPriceMarkup(oldIndependentPriceMarkup, newIndependentPriceMarkup);
256248
}
257249

258250
/**
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
261253
* @notice only to be called by the owner of the contract.
262254
*/
263255
function setPriceExpiryDuration(uint256 newPriceExpiryDuration) external payable onlyOwner {
256+
require(block.timestamp >= newPriceExpiryDuration, "Price expiry duration cannot be in the past");
264257
uint256 oldPriceExpiryDuration = priceExpiryDuration;
265258
assembly ("memory-safe") {
266259
sstore(priceExpiryDuration.slot, newPriceExpiryDuration)
@@ -293,7 +286,7 @@ contract BiconomyTokenPaymaster is
293286
* @param oracle The oracle to use for the specified token
294287
* @notice only to be called by the owner of the contract.
295288
*/
296-
function updateTokenDirectory(address tokenAddress, IOracle oracle) external payable onlyOwner {
289+
function addToTokenDirectory(address tokenAddress, IOracle oracle) external payable onlyOwner {
297290
if (oracle.decimals() != 8) {
298291
// Token -> USD will always have 8 decimals
299292
revert InvalidOracleDecimals();
@@ -302,7 +295,17 @@ contract BiconomyTokenPaymaster is
302295
uint8 decimals = IERC20Metadata(tokenAddress).decimals();
303296
independentTokenDirectory[tokenAddress] = TokenInfo(oracle, 10 ** decimals);
304297

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 );
306309
}
307310

308311
/**
@@ -326,6 +329,7 @@ contract BiconomyTokenPaymaster is
326329
for (uint256 i = 0; i < tokenAddresses.length; ++i) {
327330
_setTokenPool(tokenAddresses[i], poolFeeTiers[i]);
328331
}
332+
emit SwappableTokensAdded(tokenAddresses);
329333
}
330334

331335
/**
@@ -342,7 +346,7 @@ contract BiconomyTokenPaymaster is
342346
)
343347
external
344348
payable
345-
onlyOwner
349+
nonReentrant
346350
{
347351
// Swap tokens for WETH
348352
uint256 amountOut = _swapTokenToWeth(tokenAddress, tokenAmount, minEthAmountRecevied);
@@ -352,6 +356,7 @@ contract BiconomyTokenPaymaster is
352356
// Deposit ETH into EP
353357
entryPoint.depositTo{ value: amountOut }(address(this));
354358
}
359+
emit TokensSwappedAndRefilledEntryPoint(tokenAddress, tokenAmount, amountOut, msg.sender);
355360
}
356361

357362
/**
@@ -385,7 +390,7 @@ contract BiconomyTokenPaymaster is
385390
uint48 validUntil,
386391
uint48 validAfter,
387392
address tokenAddress,
388-
uint128 tokenPrice,
393+
uint256 tokenPrice,
389394
uint32 externalPriceMarkup
390395
)
391396
public
@@ -453,8 +458,7 @@ contract BiconomyTokenPaymaster is
453458
uint48 validUntil,
454459
uint48 validAfter,
455460
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
458462
uint32 externalPriceMarkup,
459463
bytes memory signature
460464
) = modeSpecificData.parseExternalModeSpecificData();
@@ -485,15 +489,15 @@ contract BiconomyTokenPaymaster is
485489
{
486490
uint256 maxFeePerGas = UserOperationLib.unpackMaxFeePerGas(userOp);
487491
tokenAmount = ((maxCost + maxPenalty + (unaccountedGas * maxFeePerGas)) * externalPriceMarkup * tokenPrice)
488-
/ (1e18 * _PRICE_DENOMINATOR);
492+
/ (_NATIVE_TOKEN_DECIMALS * _PRICE_DENOMINATOR);
489493
}
490494

491495
// Transfer full amount to this address. Unused amount will be refunded in postOP
492496
SafeTransferLib.safeTransferFrom(tokenAddress, userOp.sender, address(this), tokenAmount);
493497

494498
// deduct max penalty from the token amount we pass to the postOp
495499
// 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);
497501
validationData = _packValidationData(false, validUntil, validAfter);
498502
} else if (mode == PaymasterMode.INDEPENDENT) {
499503
// Use only oracles for the token specified in modeSpecificData
@@ -504,21 +508,24 @@ contract BiconomyTokenPaymaster is
504508
// Get address for token used to pay
505509
address tokenAddress = modeSpecificData.parseIndependentModeSpecificData();
506510
uint256 tokenPrice = _getPrice(tokenAddress);
511+
if(tokenPrice == 0) {
512+
revert TokenNotSupported();
513+
}
507514
uint256 tokenAmount;
508515

509516
// TODO: Account for penalties here
510517
{
511518
// Calculate token amount to precharge
512519
uint256 maxFeePerGas = UserOperationLib.unpackMaxFeePerGas(userOp);
513520
tokenAmount = ((maxCost + maxPenalty + (unaccountedGas * maxFeePerGas)) * independentPriceMarkup * tokenPrice)
514-
/ (1e18 * _PRICE_DENOMINATOR);
521+
/ (_NATIVE_TOKEN_DECIMALS * _PRICE_DENOMINATOR);
515522
}
516523

517524
// Transfer full amount to this address. Unused amount will be refunded in postOP
518525
SafeTransferLib.safeTransferFrom(tokenAddress, userOp.sender, address(this), tokenAmount);
519526

520527
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);
522529
validationData = 0; // Validation success and price is valid indefinetly
523530
}
524531
}
@@ -545,14 +552,14 @@ contract BiconomyTokenPaymaster is
545552
address tokenAddress,
546553
uint256 prechargedAmount,
547554
uint192 tokenPrice,
548-
uint256 appliedPriceMarkup,
555+
uint32 appliedPriceMarkup,
549556
bytes32 userOpHash
550-
) = abi.decode(context, (address, address, uint256, uint192, uint256, bytes32));
557+
) = abi.decode(context, (address, address, uint256, uint192, uint32, bytes32));
551558

552559
// Calculate the actual cost in tokens based on the actual gas cost and the token price
553560
uint256 actualTokenAmount = (
554561
(actualGasCost + (unaccountedGas * actualUserOpFeePerGas)) * appliedPriceMarkup * tokenPrice
555-
) / (1e18 * _PRICE_DENOMINATOR);
562+
) / (_NATIVE_TOKEN_DECIMALS * _PRICE_DENOMINATOR);
556563
if (prechargedAmount > actualTokenAmount) {
557564
// If the user was overcharged, refund the excess tokens
558565
uint256 refundAmount = prechargedAmount - actualTokenAmount;
@@ -562,7 +569,7 @@ contract BiconomyTokenPaymaster is
562569

563570
// Todo: Review events and what we need to emit.
564571
emit PaidGasInTokens(
565-
userOpSender, tokenAddress, actualGasCost, actualTokenAmount, appliedPriceMarkup, userOpHash
572+
userOpSender, tokenAddress, actualGasCost, actualTokenAmount, appliedPriceMarkup, tokenPrice, userOpHash
566573
);
567574
}
568575

@@ -578,8 +585,8 @@ contract BiconomyTokenPaymaster is
578585
}
579586

580587
// 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);
583590

584591
// Adjust to token decimals
585592
price = (nativeAssetPrice * tokenInfo.decimals) / tokenPrice;
@@ -590,15 +597,15 @@ contract BiconomyTokenPaymaster is
590597
/// @param oracle The oracle contract to fetch the price from.
591598
/// @return price The latest price fetched from the oracle.
592599
/// 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) {
594601
(, int256 answer,, uint256 updatedAt,) = oracle.latestRoundData();
595602
if (answer <= 0) {
596603
revert OraclePriceNotPositive();
597604
}
598605
if (updatedAt < block.timestamp - priceExpiryDuration) {
599606
revert OraclePriceExpired();
600607
}
601-
price = uint192(int192(answer));
608+
price = uint256(answer);
602609
}
603610

604611
function _withdrawERC20(IERC20 token, address target, uint256 amount) private {

contracts/token/swaps/Uniswapper.sol

-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ import "@uniswap/v3-periphery/contracts/interfaces/IPeripheryPayments.sol";
1212
* @notice Based on Infinitism's Uniswap Helper contract
1313
*/
1414
abstract contract Uniswapper {
15-
uint256 private constant _SWAP_PRICE_DENOMINATOR = 1e26;
16-
1715
/// @notice The Uniswap V3 SwapRouter contract
1816
ISwapRouter public immutable uniswapRouter;
1917

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/bcnmy"
88
},
99
"dependencies": {
10-
"@openzeppelin/contracts": "5.0.2",
10+
"@openzeppelin/contracts": "5.1.0",
1111
"@rhinestone/modulekit": "^0.4.10",
1212
"@uniswap/v3-core": "https://github.com/Uniswap/v3-core#0.8",
1313
"@uniswap/v3-periphery": "https://github.com/Uniswap/v3-periphery#0.8",

test/base/TestBase.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {
6161
uint48 validUntil;
6262
uint48 validAfter;
6363
address tokenAddress;
64-
uint128 tokenPrice;
64+
uint256 tokenPrice;
6565
uint32 externalPriceMarkup;
6666
}
6767

test/unit/concrete/TestTokenPaymaster.Base.t.sol

+3-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ contract TestTokenPaymasterBase is TestBase {
4242
50000, // unaccounted gas
4343
1e6, // price markup (for independent mode)
4444
1 days, // price expiry duration
45+
1e18, // native token decimals
4546
nativeOracle,
4647
swapRouter,
4748
WRAPPED_NATIVE_ADDRESS,
@@ -61,6 +62,7 @@ contract TestTokenPaymasterBase is TestBase {
6162
50000, // unaccounted gas
6263
1e6, // price markup
6364
1 days, // price expiry duration
65+
1e18, // native token decimals
6466
nativeOracle,
6567
swapRouter,
6668
WRAPPED_NATIVE_ADDRESS,
@@ -115,7 +117,7 @@ contract TestTokenPaymasterBase is TestBase {
115117
emit IBiconomyTokenPaymaster.TokensRefunded(address(ALICE_ACCOUNT), address(usdc), 0, bytes32(0));
116118

117119
vm.expectEmit(true, true, false, false, address(tokenPaymaster));
118-
emit IBiconomyTokenPaymaster.PaidGasInTokens(address(ALICE_ACCOUNT), address(usdc), 0, 0, 1e6, bytes32(0));
120+
emit IBiconomyTokenPaymaster.PaidGasInTokens(address(ALICE_ACCOUNT), address(usdc), 0, 0, 1e6, 0, bytes32(0));
119121

120122
startPrank(BUNDLER.addr);
121123
ENTRYPOINT.handleOps(ops, payable(BUNDLER.addr));

0 commit comments

Comments
 (0)