Skip to content

Commit

Permalink
Livepeer: Fix Uniswap interaction, use TWAP oracle
Browse files Browse the repository at this point in the history
  • Loading branch information
kyriediculous committed Sep 26, 2023
1 parent 805ebcb commit 59a7a1b
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 29 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@
[submodule "lib/openzeppelin-contracts-upgradeable"]
path = lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
[submodule "lib/uniswap-v3-core"]
path = lib/uniswap-v3-core
url = https://github.com/tenderize/uniswap-v3-core
1 change: 1 addition & 0 deletions lib/uniswap-v3-core
Submodule uniswap-v3-core added at f55f3a
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ solmate/=lib/solmate/src/
openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/
openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/
clones/=lib/clones-with-immutable-args/src/
@uniswap/v3-core=lib/uniswap-v3-core/contracts/
test/=test/
55 changes: 26 additions & 29 deletions src/adapters/LivepeerAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { ILivepeerBondingManager, ILivepeerRoundsManager } from "core/adapters/i
import { ISwapRouter } from "core/adapters/interfaces/ISwapRouter.sol";
import { IWETH9 } from "core/adapters/interfaces/IWETH9.sol";
import { IERC165 } from "core/interfaces/IERC165.sol";
import { TWAP } from "core/utils/TWAP.sol";

contract LivepeerAdapter is Adapter {
using SafeTransferLib for ERC20;
Expand Down Expand Up @@ -125,34 +126,30 @@ contract LivepeerAdapter is Adapter {
function _livepeerClaimFees() internal {
// get pending fees
uint256 pendingFees;
if ((pendingFees = LIVEPEER.pendingFees(address(this), 0)) >= ETH_THRESHOLD) {
// withdraw fees
LIVEPEER.withdrawFees(payable(address(this)), pendingFees);
// convert fees to WETH
WETH.deposit{ value: address(this).balance }();
ERC20(address(WETH)).safeApprove(address(UNISWAP_ROUTER), address(this).balance);
// Create initial params for swap
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
tokenIn: address(WETH),
tokenOut: address(address(LPT)),
fee: UNISWAP_POOL_FEE,
recipient: address(this),
deadline: block.timestamp,
amountIn: address(this).balance,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
});

(bool success, bytes memory returnData) =
address(UNISWAP_ROUTER).staticcall(abi.encodeCall(UNISWAP_ROUTER.exactInputSingle, (params)));

if (!success) return;

// set return value of staticcall to minimum LPT value to receive from swap
params.amountOutMinimum = abi.decode(returnData, (uint256));

// execute swap
UNISWAP_ROUTER.exactInputSingle(params);
}
if ((pendingFees = LIVEPEER.pendingFees(address(this), 0)) < ETH_THRESHOLD) return;

// withdraw fees
LIVEPEER.withdrawFees(payable(address(this)), pendingFees);
// get ETH balance
uint256 ethBalance = address(this).balance;
// convert fees to WETH
WETH.deposit{ value: ethBalance }();
ERC20(address(WETH)).safeApprove(address(UNISWAP_ROUTER), address(this).balance);
// Calculate Slippage Threshold
uint256 twapPrice = TWAP.getPriceX96FromSqrtPriceX96(TWAP.getSqrtTwapX96(UNI_POOL, TWAP_INTERVAL));
// Create initial params for swap
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
tokenIn: address(WETH),
tokenOut: address(address(LPT)),
fee: UNISWAP_POOL_FEE,
recipient: address(this),
deadline: block.timestamp,
amountIn: ethBalance,
amountOutMinimum: ethBalance * twapPrice * 90 / 100, // 10% slippage threshold
sqrtPriceLimitX96: 0
});

// execute swap
UNISWAP_ROUTER.exactInputSingle(params);
}
}
37 changes: 37 additions & 0 deletions src/utils/TWAP.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: MIT
//
// _____ _ _
// |_ _| | | (_)
// | | ___ _ __ __| | ___ _ __ _ _______
// | |/ _ \ '_ \ / _` |/ _ \ '__| |_ / _ \
// | | __/ | | | (_| | __/ | | |/ / __/
// \_/\___|_| |_|\__,_|\___|_| |_/___\___|
//
// Copyright (c) Tenderize Labs Ltd

import "@uniswap/v3-core/interfaces/IUniswapV3Pool.sol";

Check failure on line 12 in src/utils/TWAP.sol

View workflow job for this annotation

GitHub Actions / lint

Compiler version must be declared

Check warning on line 12 in src/utils/TWAP.sol

View workflow job for this annotation

GitHub Actions / lint

global import of path @uniswap/v3-core/interfaces/IUniswapV3Pool.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
import "@uniswap/v3-core/libraries/TickMath.sol";

Check warning on line 13 in src/utils/TWAP.sol

View workflow job for this annotation

GitHub Actions / lint

global import of path @uniswap/v3-core/libraries/TickMath.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
import "@uniswap/v3-core/libraries/FixedPoint96.sol";

Check warning on line 14 in src/utils/TWAP.sol

View workflow job for this annotation

GitHub Actions / lint

global import of path @uniswap/v3-core/libraries/FixedPoint96.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
import "@uniswap/v3-core/libraries/FullMath.sol";

library TWAP {
function getSqrtTwapX96(address uniswapV3Pool, uint32 twapInterval) internal view returns (uint160 sqrtPriceX96) {
if (twapInterval == 0) {
// return the current price if twapInterval == 0
(sqrtPriceX96,,,,,,) = IUniswapV3Pool(uniswapV3Pool).slot0();
} else {
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = twapInterval; // from (before)
secondsAgos[1] = 0; // to (now)

(int56[] memory tickCumulatives,) = IUniswapV3Pool(uniswapV3Pool).observe(secondsAgos);

// tick(imprecise as it's an integer) to price
sqrtPriceX96 = TickMath.getSqrtRatioAtTick(int24((tickCumulatives[1] - tickCumulatives[0]) / int32(twapInterval)));
}
}

function getPriceX96FromSqrtPriceX96(uint160 sqrtPriceX96) internal pure returns (uint256 priceX96) {
return FullMath.mulDiv(sqrtPriceX96, sqrtPriceX96, FixedPoint96.Q96);
}
}

0 comments on commit 59a7a1b

Please sign in to comment.