From 4e2a0e54d4a8b83c9ecda7896fc4b2cd26f882fa Mon Sep 17 00:00:00 2001 From: Shivaansh Kapoor Date: Wed, 11 Sep 2024 18:17:18 +0400 Subject: [PATCH] uniswapper to perform token to weth swaps through uniswaps --- .../interfaces/IBiconomyTokenPaymaster.sol | 2 +- contracts/token/BiconomyTokenPaymaster.sol | 24 +++-- contracts/token/swaps/Uniswapper.sol | 87 +++++++++++++++++++ test/unit/concrete/TestTokenPaymaster.t.sol | 4 +- 4 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 contracts/token/swaps/Uniswapper.sol diff --git a/contracts/interfaces/IBiconomyTokenPaymaster.sol b/contracts/interfaces/IBiconomyTokenPaymaster.sol index b5e1f2f..0a3a2a5 100644 --- a/contracts/interfaces/IBiconomyTokenPaymaster.sol +++ b/contracts/interfaces/IBiconomyTokenPaymaster.sol @@ -46,7 +46,7 @@ interface IBiconomyTokenPaymaster { function setPriceExpiryDuration(uint256 _newPriceExpiryDuration) external payable; - function setNativeOracle(IOracle _oracle) external payable; + function setNativeAssetToUsdOracle(IOracle _oracle) external payable; function updateTokenDirectory(address _tokenAddress, IOracle _oracle) external payable; } diff --git a/contracts/token/BiconomyTokenPaymaster.sol b/contracts/token/BiconomyTokenPaymaster.sol index e49743f..02b836b 100644 --- a/contracts/token/BiconomyTokenPaymaster.sol +++ b/contracts/token/BiconomyTokenPaymaster.sol @@ -15,6 +15,8 @@ import { TokenPaymasterParserLib } from "../libraries/TokenPaymasterParserLib.so import { SignatureCheckerLib } from "@solady/src/utils/SignatureCheckerLib.sol"; import { ECDSA as ECDSA_solady } from "@solady/src/utils/ECDSA.sol"; import "@account-abstraction/contracts/core/Helpers.sol"; +import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; +import "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol"; /** * @title BiconomyTokenPaymaster @@ -62,8 +64,8 @@ contract BiconomyTokenPaymaster is IEntryPoint _entryPoint, uint256 _unaccountedGas, uint256 _priceMarkup, - IOracle _nativeAssetToUsdOracle, uint256 _priceExpiryDuration, + IOracle _nativeAssetToUsdOracle, address[] memory _tokens, // Array of token addresses IOracle[] memory _oracles // Array of corresponding oracle addresses ) @@ -266,18 +268,18 @@ contract BiconomyTokenPaymaster is * @param _oracle The new native asset oracle * @notice only to be called by the owner of the contract. */ - function setNativeOracle(IOracle _oracle) external payable override onlyOwner { + function setNativeAssetToUsdOracle(IOracle _oracle) external payable override onlyOwner { if (_oracle.decimals() != 8) { // Native -> USD will always have 8 decimals revert InvalidOracleDecimals(); } - IOracle oldNativeOracle = nativeAssetToUsdOracle; + IOracle oldNativeAssetToUsdOracle = nativeAssetToUsdOracle; assembly ("memory-safe") { sstore(nativeAssetToUsdOracle.slot, _oracle) } - emit UpdatedNativeAssetOracle(oldNativeOracle, _oracle); + emit UpdatedNativeAssetOracle(oldNativeAssetToUsdOracle, _oracle); } /** @@ -298,6 +300,17 @@ contract BiconomyTokenPaymaster is emit UpdatedTokenDirectory(_tokenAddress, _oracle, decimals); } + /** + * @dev Swap a token in the paymaster for ETH to increase its entry point deposit + * @param _swapRouter The address of the swap router to use to facilitate the swap + * @param _tokenAddress The token address of the token to swap for ETH + * @param _tokenAmount The amount of the token to swap + * @notice only to be called by the owner of the contract. + */ + function swapTokenAndDeposit(ISwapRouter _swapRouter, address _tokenAddress, uint256 _tokenAmount) external payable onlyOwner { + + } + /** * return the hash we're going to sign off-chain (and validate on-chain) * this method is called by the off-chain service, to sign the request. @@ -405,8 +418,7 @@ contract BiconomyTokenPaymaster is // Transfer full amount to this address. Unused amount will be refunded in postOP SafeTransferLib.safeTransferFrom(tokenAddress, userOp.sender, address(this), tokenAmount); - context = - abi.encode(userOp.sender, tokenAddress, tokenAmount, tokenPrice, externalPriceMarkup, userOpHash); + context = abi.encode(userOp.sender, tokenAddress, tokenAmount, tokenPrice, externalPriceMarkup, userOpHash); validationData = _packValidationData(false, validUntil, validAfter); } else if (mode == PaymasterMode.INDEPENDENT) { // Use only oracles for the token specified in modeSpecificData diff --git a/contracts/token/swaps/Uniswapper.sol b/contracts/token/swaps/Uniswapper.sol new file mode 100644 index 0000000..a4d88f9 --- /dev/null +++ b/contracts/token/swaps/Uniswapper.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.23; + +/* solhint-disable not-rely-on-time */ + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; +import "@uniswap/v3-periphery/contracts/interfaces/IPeripheryPayments.sol"; + +abstract contract Uniswapper { + uint256 private constant SWAP_PRICE_DENOMINATOR = 1e26; + + /// @notice The Uniswap V3 SwapRouter contract + ISwapRouter public immutable uniswapRouter; + + /// @notice The ERC-20 token that wraps the native asset for current chain + address public immutable wrappedNative; + + // Token address -> Fee tier of the pool to swap through + mapping(address => uint24) public tokenToPools; + + event UniswapReverted(address indexed tokenIn, address indexed tokenOut, uint256 amountIn); + + error TokensAndAmountsLengthMismatch(); + + constructor( + ISwapRouter _uniswapRouter, + address _wrappedNative, + address[] memory _tokens, + uint24[] memory _tokenPools + ) { + if (_tokens.length != _tokenPools.length) { + revert TokensAndAmountsLengthMismatch(); + } + + // Set router and native wrapped asset addresses + uniswapRouter = _uniswapRouter; + wrappedNative = _wrappedNative; + + for (uint256 i = 0; i < _tokens.length; ++i) { + IERC20(_tokens[i]).approve(address(_uniswapRouter), type(uint256).max); // one time max approval + tokenToPools[_tokens[i]] = _tokenPools[i]; // set mapping of token to uniswap pool to use for swap + } + } + + function _setTokenPool(address _token, uint24 _feeTier) internal { + tokenToPools[_token] = _feeTier; // set mapping of token to uniswap pool to use for swap + } + + function _swapTokenToWeth(address _tokenIn, uint256 _amountIn) internal returns (uint256 amountOut) { + uint24 poolFee = tokenToPools[_tokenIn]; + + ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({ + tokenIn: _tokenIn, + tokenOut: wrappedNative, + fee: poolFee, + recipient: address(this), + deadline: block.timestamp, + amountIn: _amountIn, + amountOutMinimum: 0, + sqrtPriceLimitX96: 0 + }); + try uniswapRouter.exactInputSingle(params) returns (uint256 _amountOut) { + amountOut = _amountOut; + } catch { + emit UniswapReverted(_tokenIn, wrappedNative, _amountIn); + amountOut = 0; + } + } + + function addSlippage(uint256 amount, uint8 slippage) private pure returns (uint256) { + return amount * (1000 - slippage) / 1000; + } + + function tokenToWei(uint256 amount, uint256 price) public pure returns (uint256) { + return amount * price / SWAP_PRICE_DENOMINATOR; + } + + function weiToToken(uint256 amount, uint256 price) public pure returns (uint256) { + return amount * SWAP_PRICE_DENOMINATOR / price; + } + + function unwrapWeth(uint256 _amount) internal { + IPeripheryPayments(address(uniswapRouter)).unwrapWETH9(_amount, address(this)); + } +} diff --git a/test/unit/concrete/TestTokenPaymaster.t.sol b/test/unit/concrete/TestTokenPaymaster.t.sol index a7de937..2c33a77 100644 --- a/test/unit/concrete/TestTokenPaymaster.t.sol +++ b/test/unit/concrete/TestTokenPaymaster.t.sol @@ -194,12 +194,12 @@ contract TestTokenPaymaster is TestBase { ); } - function test_SetNativeOracle() external prankModifier(PAYMASTER_OWNER.addr) { + function test_SetNativeAssetToUsdOracle() external prankModifier(PAYMASTER_OWNER.addr) { MockOracle newOracle = new MockOracle(100_000_000, 8); vm.expectEmit(true, true, false, true, address(tokenPaymaster)); emit IBiconomyTokenPaymaster.UpdatedNativeAssetOracle(nativeAssetToUsdOracle, newOracle); - tokenPaymaster.setNativeOracle(newOracle); + tokenPaymaster.setNativeAssetToUsdOracle(newOracle); assertEq(address(tokenPaymaster.nativeAssetToUsdOracle()), address(newOracle)); }