From 91dbdaa20fa10890bf9e79907eac56bba686a78f Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Wed, 30 Oct 2024 22:40:36 +0700 Subject: [PATCH 1/7] [TokenPaymaster] Support chains without WETH9 For chains like Celo we can swap directly to it without unwrapping --- contracts/prebuilts/account/utils/UniswapHelper.sol | 4 ++++ src/test/smart-wallet/token-paymaster/TokenPaymaster.t.sol | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/contracts/prebuilts/account/utils/UniswapHelper.sol b/contracts/prebuilts/account/utils/UniswapHelper.sol index 39b541621..7063ce3f0 100644 --- a/contracts/prebuilts/account/utils/UniswapHelper.sol +++ b/contracts/prebuilts/account/utils/UniswapHelper.sol @@ -19,6 +19,7 @@ abstract contract UniswapHelper { uint256 minSwapAmount; uint24 uniswapPoolFee; uint8 slippage; + bool wethIsNativeAsset; } /// @notice The Uniswap V3 SwapRouter contract @@ -85,6 +86,9 @@ abstract contract UniswapHelper { } function unwrapWeth(uint256 amount) internal { + if (uniswapHelperConfig.wethIsNativeAsset) { + return; + } IPeripheryPayments(address(uniswap)).unwrapWETH9(amount, address(this)); } diff --git a/src/test/smart-wallet/token-paymaster/TokenPaymaster.t.sol b/src/test/smart-wallet/token-paymaster/TokenPaymaster.t.sol index fed3bf3d5..4fcbe9907 100644 --- a/src/test/smart-wallet/token-paymaster/TokenPaymaster.t.sol +++ b/src/test/smart-wallet/token-paymaster/TokenPaymaster.t.sol @@ -115,7 +115,8 @@ contract TokenPaymasterTest is BaseTest { UniswapHelper.UniswapHelperConfig memory uniswapHelperConfig = UniswapHelper.UniswapHelperConfig({ minSwapAmount: 1, slippage: 5, - uniswapPoolFee: 3 + uniswapPoolFee: 3, + wethIsNativeAsset: false }); paymaster = new TokenPaymaster( From b2337c2ac29391b9a22d91c3c913980dc9b1450f Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Thu, 31 Oct 2024 03:01:19 +0700 Subject: [PATCH 2/7] Ability to update Oracle config & better cache price updates --- .../prebuilts/account/token-paymaster/TokenPaymaster.sol | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/contracts/prebuilts/account/token-paymaster/TokenPaymaster.sol b/contracts/prebuilts/account/token-paymaster/TokenPaymaster.sol index f2705f6f4..1dbd091fd 100644 --- a/contracts/prebuilts/account/token-paymaster/TokenPaymaster.sol +++ b/contracts/prebuilts/account/token-paymaster/TokenPaymaster.sol @@ -98,6 +98,10 @@ contract TokenPaymaster is BasePaymaster, UniswapHelper, OracleHelper { _setUniswapHelperConfiguration(_uniswapHelperConfig); } + function setOracleConfiguration(OracleHelperConfig memory _oracleHelperConfig) external onlyOwner { + _setOracleConfiguration(_oracleHelperConfig); + } + /// @notice Allows the contract owner to withdraw a specified amount of tokens from the contract. /// @param to The address to transfer the tokens to. /// @param amount The amount of tokens to transfer. @@ -161,7 +165,8 @@ contract TokenPaymaster is BasePaymaster, UniswapHelper, OracleHelper { unchecked { uint256 priceMarkup = tokenPaymasterConfig.priceMarkup; (uint256 preCharge, address userOpSender) = abi.decode(context, (uint256, address)); - uint256 _cachedPrice = updateCachedPrice(false); + bool forceUpdate = (block.timestamp - cachedPriceTimestamp) > tokenPaymasterConfig.priceMaxAge; + uint256 _cachedPrice = updateCachedPrice(forceUpdate); // note: as price is in native-asset-per-token and we want more tokens increasing it means dividing it by markup uint256 cachedPriceWithMarkup = (_cachedPrice * PRICE_DENOMINATOR) / priceMarkup; // Refund tokens based on actual gas cost From 98787ee544caaf267824758bac1daef1fb3a4405 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Thu, 31 Oct 2024 03:20:10 +0700 Subject: [PATCH 3/7] expose _setOracleConfiguration --- contracts/prebuilts/account/utils/OracleHelper.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/prebuilts/account/utils/OracleHelper.sol b/contracts/prebuilts/account/utils/OracleHelper.sol index b7fa334d1..8a895a4c4 100644 --- a/contracts/prebuilts/account/utils/OracleHelper.sol +++ b/contracts/prebuilts/account/utils/OracleHelper.sol @@ -54,7 +54,7 @@ abstract contract OracleHelper { _setOracleConfiguration(_oracleHelperConfig); } - function _setOracleConfiguration(OracleHelperConfig memory _oracleHelperConfig) private { + function _setOracleConfiguration(OracleHelperConfig memory _oracleHelperConfig) internal { oracleHelperConfig = _oracleHelperConfig; require(_oracleHelperConfig.priceUpdateThreshold <= PRICE_DENOMINATOR, "TPM: update threshold too high"); tokenOracleDecimalPower = uint128(10 ** oracleHelperConfig.tokenOracle.decimals()); From aa873b4d87f26f3902522571d5c17c3bbfd1c3e3 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Wed, 20 Nov 2024 04:42:09 +0700 Subject: [PATCH 4/7] make configs public --- contracts/prebuilts/account/utils/OracleHelper.sol | 2 +- contracts/prebuilts/account/utils/UniswapHelper.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/prebuilts/account/utils/OracleHelper.sol b/contracts/prebuilts/account/utils/OracleHelper.sol index 8a895a4c4..89ce08843 100644 --- a/contracts/prebuilts/account/utils/OracleHelper.sol +++ b/contracts/prebuilts/account/utils/OracleHelper.sol @@ -41,7 +41,7 @@ abstract contract OracleHelper { /// @notice The timestamp of a block when the cached price was updated uint48 public cachedPriceTimestamp; - OracleHelperConfig private oracleHelperConfig; + OracleHelperConfig public oracleHelperConfig; /// @notice The "10^(tokenOracle.decimals)" value used for the price calculation uint128 private tokenOracleDecimalPower; diff --git a/contracts/prebuilts/account/utils/UniswapHelper.sol b/contracts/prebuilts/account/utils/UniswapHelper.sol index 7063ce3f0..86574bb13 100644 --- a/contracts/prebuilts/account/utils/UniswapHelper.sol +++ b/contracts/prebuilts/account/utils/UniswapHelper.sol @@ -31,7 +31,7 @@ abstract contract UniswapHelper { /// @notice The ERC-20 token that wraps the native asset for current chain IERC20 public immutable wrappedNative; - UniswapHelperConfig private uniswapHelperConfig; + UniswapHelperConfig public uniswapHelperConfig; constructor( IERC20Metadata _token, From 99123653de5d0f02a6835caa0f1fe4d69ffce8ff Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Wed, 20 Nov 2024 05:14:06 +0700 Subject: [PATCH 5/7] Switch to swap-router-contracts interface --- .gitmodules | 3 +++ .../prebuilts/account/token-paymaster/TokenPaymaster.sol | 2 +- contracts/prebuilts/account/utils/UniswapHelper.sol | 9 ++++----- foundry.toml | 1 + .../smart-wallet/token-paymaster/TokenPaymaster.t.sol | 4 ++-- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.gitmodules b/.gitmodules index 43557e2ee..db5ccba93 100644 --- a/.gitmodules +++ b/.gitmodules @@ -52,3 +52,6 @@ [submodule "lib/v3-core"] path = lib/v3-core url = https://github.com/uniswap/v3-core +[submodule "lib/swap-router-contracts"] + path = lib/swap-router-contracts + url = https://github.com/Uniswap/swap-router-contracts diff --git a/contracts/prebuilts/account/token-paymaster/TokenPaymaster.sol b/contracts/prebuilts/account/token-paymaster/TokenPaymaster.sol index 1dbd091fd..21763889f 100644 --- a/contracts/prebuilts/account/token-paymaster/TokenPaymaster.sol +++ b/contracts/prebuilts/account/token-paymaster/TokenPaymaster.sol @@ -68,7 +68,7 @@ contract TokenPaymaster is BasePaymaster, UniswapHelper, OracleHelper { IERC20Metadata _token, IEntryPoint _entryPoint, IERC20 _wrappedNative, - ISwapRouter _uniswap, + IV3SwapRouter _uniswap, TokenPaymasterConfig memory _tokenPaymasterConfig, OracleHelperConfig memory _oracleHelperConfig, UniswapHelperConfig memory _uniswapHelperConfig, diff --git a/contracts/prebuilts/account/utils/UniswapHelper.sol b/contracts/prebuilts/account/utils/UniswapHelper.sol index 86574bb13..30f5031ae 100644 --- a/contracts/prebuilts/account/utils/UniswapHelper.sol +++ b/contracts/prebuilts/account/utils/UniswapHelper.sol @@ -6,7 +6,7 @@ pragma solidity ^0.8.23; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; +import "@uniswap/swap-router-contracts/contracts/interfaces/IV3SwapRouter.sol"; import "@uniswap/v3-periphery/contracts/interfaces/IPeripheryPayments.sol"; abstract contract UniswapHelper { @@ -23,7 +23,7 @@ abstract contract UniswapHelper { } /// @notice The Uniswap V3 SwapRouter contract - ISwapRouter public immutable uniswap; + IV3SwapRouter public immutable uniswap; /// @notice The ERC20 token used for transaction fee payments IERC20Metadata public immutable token; @@ -36,7 +36,7 @@ abstract contract UniswapHelper { constructor( IERC20Metadata _token, IERC20 _wrappedNative, - ISwapRouter _uniswap, + IV3SwapRouter _uniswap, UniswapHelperConfig memory _uniswapHelperConfig ) { _token.approve(address(_uniswap), type(uint256).max); @@ -100,12 +100,11 @@ abstract contract UniswapHelper { uint256 amountOutMin, uint24 fee ) internal returns (uint256 amountOut) { - ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams( + IV3SwapRouter.ExactInputSingleParams memory params = IV3SwapRouter.ExactInputSingleParams( tokenIn, //tokenIn tokenOut, //tokenOut fee, address(uniswap), - block.timestamp, //deadline amountIn, amountOutMin, 0 diff --git a/foundry.toml b/foundry.toml index 13a9e8619..464eeb8d6 100644 --- a/foundry.toml +++ b/foundry.toml @@ -31,6 +31,7 @@ out = 'artifacts_forge' remappings = [ '@uniswap/v3-core/contracts=lib/v3-core/contracts', '@uniswap/v3-periphery/contracts=lib/v3-periphery/contracts', + '@uniswap/swap-router-contracts/contracts=lib/swap-router-contracts/contracts', '@chainlink/=lib/chainlink/', '@openzeppelin/contracts=lib/openzeppelin-contracts/contracts', '@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/', diff --git a/src/test/smart-wallet/token-paymaster/TokenPaymaster.t.sol b/src/test/smart-wallet/token-paymaster/TokenPaymaster.t.sol index 4fcbe9907..e6b3507ef 100644 --- a/src/test/smart-wallet/token-paymaster/TokenPaymaster.t.sol +++ b/src/test/smart-wallet/token-paymaster/TokenPaymaster.t.sol @@ -18,7 +18,7 @@ import { AccountFactory } from "contracts/prebuilts/account/non-upgradeable/Acco import { Account as SimpleAccount } from "contracts/prebuilts/account/non-upgradeable/Account.sol"; import { TokenPaymaster, IERC20Metadata } from "contracts/prebuilts/account/token-paymaster/TokenPaymaster.sol"; import { OracleHelper, IOracle } from "contracts/prebuilts/account/utils/OracleHelper.sol"; -import { UniswapHelper, ISwapRouter } from "contracts/prebuilts/account/utils/UniswapHelper.sol"; +import { UniswapHelper, IV3SwapRouter } from "contracts/prebuilts/account/utils/UniswapHelper.sol"; /// @dev This is a dummy contract to test contract interactions with Account. contract Number { @@ -123,7 +123,7 @@ contract TokenPaymasterTest is BaseTest { IERC20Metadata(address(token)), entrypoint, weth, - ISwapRouter(address(testUniswap)), + IV3SwapRouter(address(testUniswap)), tokenPaymasterConfig, oracleHelperConfig, uniswapHelperConfig, From 3f709a109c8dc15fc0b7c489b778a33aa07354b0 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Wed, 20 Nov 2024 05:42:59 +0700 Subject: [PATCH 6/7] Create swap-router-contracts --- lib/swap-router-contracts | 1 + 1 file changed, 1 insertion(+) create mode 160000 lib/swap-router-contracts diff --git a/lib/swap-router-contracts b/lib/swap-router-contracts new file mode 160000 index 000000000..c696aada4 --- /dev/null +++ b/lib/swap-router-contracts @@ -0,0 +1 @@ +Subproject commit c696aada49b33c8e764e6f0bd0a0a56bd8aa455f From 5f9a67410c269adae1836897bcf814f09b6623a7 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Fri, 22 Nov 2024 05:43:34 +0700 Subject: [PATCH 7/7] move price update to validate fn --- .../prebuilts/account/token-paymaster/TokenPaymaster.sol | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/contracts/prebuilts/account/token-paymaster/TokenPaymaster.sol b/contracts/prebuilts/account/token-paymaster/TokenPaymaster.sol index 21763889f..c7104c763 100644 --- a/contracts/prebuilts/account/token-paymaster/TokenPaymaster.sol +++ b/contracts/prebuilts/account/token-paymaster/TokenPaymaster.sol @@ -127,6 +127,10 @@ contract TokenPaymaster is BasePaymaster, UniswapHelper, OracleHelper { uint256 refundPostopCost = tokenPaymasterConfig.refundPostopCost; require(refundPostopCost < userOp.unpackPostOpGasLimit(), "TPM: postOpGasLimit too low"); uint256 preChargeNative = requiredPreFund + (refundPostopCost * maxFeePerGas); + + bool forceUpdate = (block.timestamp - cachedPriceTimestamp) > tokenPaymasterConfig.priceMaxAge; + updateCachedPrice(forceUpdate); + // note: as price is in native-asset-per-token and we want more tokens increasing it means dividing it by markup uint256 cachedPriceWithMarkup = (cachedPrice * PRICE_DENOMINATOR) / priceMarkup; if (dataLength == 32) { @@ -165,8 +169,7 @@ contract TokenPaymaster is BasePaymaster, UniswapHelper, OracleHelper { unchecked { uint256 priceMarkup = tokenPaymasterConfig.priceMarkup; (uint256 preCharge, address userOpSender) = abi.decode(context, (uint256, address)); - bool forceUpdate = (block.timestamp - cachedPriceTimestamp) > tokenPaymasterConfig.priceMaxAge; - uint256 _cachedPrice = updateCachedPrice(forceUpdate); + uint256 _cachedPrice = cachedPrice; // note: as price is in native-asset-per-token and we want more tokens increasing it means dividing it by markup uint256 cachedPriceWithMarkup = (_cachedPrice * PRICE_DENOMINATOR) / priceMarkup; // Refund tokens based on actual gas cost