diff --git a/contracts/Compound3Liquidator.sol b/contracts/Compound3Liquidator.sol index 206c33a..52f232b 100644 --- a/contracts/Compound3Liquidator.sol +++ b/contracts/Compound3Liquidator.sol @@ -5,7 +5,6 @@ import { Ownable } from '@openzeppelin/contracts/access/Ownable.sol'; import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import { IComet } from './compound3/interfaces/IComet.sol'; -import { IUniswapV3FlashCallback } from './uniswap/v3-core/contracts/interfaces/callback/IUniswapV3FlashCallback.sol'; import { IUniswapV3SwapCallback } from './uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol'; import { IUniswapV3Pool } from './uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol'; import { TickMath } from './uniswap/v3-core/contracts/libraries/TickMath.sol'; @@ -16,10 +15,11 @@ import { IWETH9 } from './uniswap/v3-periphery/contracts/interfaces/external/IWE import { ISwapRouter02 } from './uniswap/swap-router-contracts/contracts/interfaces/ISwapRouter02.sol'; import { IV3SwapRouter } from './uniswap/swap-router-contracts/contracts/interfaces/IV3SwapRouter.sol'; import { IApi3ServerV1OevExtension } from './api3-contracts/api3-server-v1/interfaces/IApi3ServerV1OevExtension.sol'; +import { IApi3ServerV1OevExtensionPayOevBidCallback } from './api3-contracts/api3-server-v1/interfaces/IApi3ServerV1OevExtensionPayOevBidCallback.sol'; event AbsorbFailed(address indexed borrower); -contract Compound3Liquidator is Ownable, IUniswapV3SwapCallback, IUniswapV3FlashCallback { +contract Compound3Liquidator is Ownable, IUniswapV3SwapCallback, IApi3ServerV1OevExtensionPayOevBidCallback { address public profitReceiver; uint24 public constant DEFAULT_POOL_FEE = 500; // 0.05% uint256 public constant DAPP_ID = 1; @@ -29,7 +29,7 @@ contract Compound3Liquidator is Ownable, IUniswapV3SwapCallback, IUniswapV3Flash address public constant WSTETH = 0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452; address public constant SWAP_ROUTER = 0x2626664c2603336E57B271c5C0b26F421741e481; address public constant UNISWAP_FACTORY = 0x33128a8fC17869897dcE68Ed026d694621f6FDfD; - address public constant API3_SERVER_V1_OEV_EXTENSION = 0xF930D1E37098128326F8731a476347f0840337cA; + address public constant API3_SERVER_V1_OEV_EXTENSION = 0x6a6F4b90ac94Df292fAe521b24b94cE8E58EB91e; mapping(address => mapping(address => uint24)) public uniswapPoolFees; @@ -45,16 +45,15 @@ contract Compound3Liquidator is Ownable, IUniswapV3SwapCallback, IUniswapV3Flash } struct PayBidAndUpdateFeedsAndLiquidateParams { - LiquidateParams liquidateParams; uint32 signedDataTimestampCutoff; bytes signature; uint256 bidAmount; - bytes[][] signedDataArray; + PayOevBidCallbackData payOevBidCallbackData; } - struct FlashCallbackData { - PoolAddress.PoolKey poolKey; - PayBidAndUpdateFeedsAndLiquidateParams params; + struct PayOevBidCallbackData { + LiquidateParams liquidateParams; + bytes[][] signedDataArray; } struct SwapCallbackData { @@ -140,50 +139,43 @@ contract Compound3Liquidator is Ownable, IUniswapV3SwapCallback, IUniswapV3Flash /// @notice This serves for simulating profit from liquidations via staticcall function liquidate(LiquidateParams calldata params) external returns (uint256, uint256) { - uint256 wethBalanceBefore = weth.balanceOf(address(this)); + uint256 ethBalanceBefore = address(this).balance; _liquidate(params); - return _withdrawWethAndReturnProfit(wethBalanceBefore); + _withdrawWeth(); + + return _transferProfit(ethBalanceBefore); } /// @notice Pays OEV bid, updates OEV data feeds, performs liquidations accounts and withdraws ETH to `profitReceiver` function payBidAndUpdateFeedsAndLiquidate( PayBidAndUpdateFeedsAndLiquidateParams calldata params ) external returns (uint256, uint256) { - uint256 wethBalanceBefore = weth.balanceOf(address(this)); + uint256 ethBalanceBefore = address(this).balance; - PoolAddress.PoolKey memory poolKey = _getFlashSwapPoolKey(USDC, WETH); - IUniswapV3Pool pool = _getFlashSwapPool(poolKey); - - pool.flash( - address(this), - params.bidAmount, // token0 = WETH - 0, // token1 = USDC - abi.encode(FlashCallbackData({ poolKey: poolKey, params: params })) + oevExtension.payOevBid( + DAPP_ID, + params.signedDataTimestampCutoff, + params.signature, + params.bidAmount, + abi.encode(params.payOevBidCallbackData) ); - return _withdrawWethAndReturnProfit(wethBalanceBefore); + return _transferProfit(ethBalanceBefore); } - /// @notice Callback for flash loans through Uniswap V3 - function uniswapV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata _data) external override { - FlashCallbackData memory data = abi.decode(_data, (FlashCallbackData)); - CallbackValidation.verifyCallback(UNISWAP_FACTORY, data.poolKey); - - weth.withdraw(data.params.bidAmount); + /// @notice Callback triggered by calling `payOevBid` on the OEV server extension. Updates data feeds, + /// liquidates accounts, and pays back the payment amount owed for the OEV bid. + function api3ServerV1OevExtensionPayOevBidCallback(uint256 amountOwed, bytes calldata _data) external override { + require(msg.sender == API3_SERVER_V1_OEV_EXTENSION, 'Unauthorized'); - oevExtension.payOevBid{ value: data.params.bidAmount }( - DAPP_ID, - data.params.signedDataTimestampCutoff, - data.params.signature - ); + PayOevBidCallbackData memory data = abi.decode(_data, (PayOevBidCallbackData)); + _updateDataFeeds(data.signedDataArray); + _liquidate(data.liquidateParams); - _updateDataFeeds(data.params.signedDataArray); - - _liquidate(data.params.liquidateParams); - - weth.transfer(msg.sender, data.params.bidAmount + fee0); + _withdrawWeth(); + API3_SERVER_V1_OEV_EXTENSION.call{ value: amountOwed }(''); } /// @notice Callback for flash swaps through Uniswap V3 @@ -277,11 +269,15 @@ contract Compound3Liquidator is Ownable, IUniswapV3SwapCallback, IUniswapV3Flash } } - function _withdrawWethAndReturnProfit(uint256 wethBalanceBefore) internal returns (uint256, uint256) { - uint256 wethBalanceAfter = weth.balanceOf(address(this)); - if (wethBalanceAfter > 0) { - weth.withdraw(wethBalanceAfter); + function _withdrawWeth() internal { + uint256 wethBalance = weth.balanceOf(address(this)); + if (wethBalance > 0) { + weth.withdraw(wethBalance); } + } + + function _transferProfit(uint256 ethBalanceBefore) internal returns (uint256, uint256) { + uint256 ethBalanceAfter = address(this).balance; uint8 numberOfAssets = comet.numAssets(); IComet.AssetInfo memory wethAsset; @@ -292,7 +288,7 @@ contract Compound3Liquidator is Ownable, IUniswapV3SwapCallback, IUniswapV3Flash } } - uint256 profit = wethBalanceAfter - wethBalanceBefore; + uint256 profit = ethBalanceAfter - ethBalanceBefore; uint256 profitUsd = (profit * comet.getPrice(wethAsset.priceFeed)) / wethAsset.scale; profitReceiver.call{ value: profit }(''); diff --git a/contracts/api3-contracts/api3-server-v1/interfaces/IApi3ServerV1OevExtension.sol b/contracts/api3-contracts/api3-server-v1/interfaces/IApi3ServerV1OevExtension.sol index bc147d8..ceb5d34 100644 --- a/contracts/api3-contracts/api3-server-v1/interfaces/IApi3ServerV1OevExtension.sol +++ b/contracts/api3-contracts/api3-server-v1/interfaces/IApi3ServerV1OevExtension.sol @@ -31,8 +31,10 @@ interface IApi3ServerV1OevExtension is function payOevBid( uint256 dappId, uint32 signedDataTimestampCutoff, - bytes calldata signature - ) external payable; + bytes calldata signature, + uint256 bidAmount, + bytes calldata data + ) external; function updateDappOevDataFeed( uint256 dappId, diff --git a/contracts/api3-contracts/api3-server-v1/interfaces/IApi3ServerV1OevExtensionPayOevBidCallback.sol b/contracts/api3-contracts/api3-server-v1/interfaces/IApi3ServerV1OevExtensionPayOevBidCallback.sol new file mode 100644 index 0000000..474512e --- /dev/null +++ b/contracts/api3-contracts/api3-server-v1/interfaces/IApi3ServerV1OevExtensionPayOevBidCallback.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title Callback for IApi3ServerV1OevExtension#payOevBid +/// @notice Any contract that calls IApi3ServerV1OevExtension#payOevBid must implement this interface +interface IApi3ServerV1OevExtensionPayOevBidCallback { + /// @notice Called to `msg.sender` after granting the privilege to execute updates for the dApp from IApi3ServerV1OevExtension#payOevBid. + /// @dev In the implementation you must repay the server the tokens owed for the payment of the OEV bid. + /// The implementation is responsible to check that the caller of this method is the correct Api3ServerV1OevExtension. + /// @param amountOwed The amount of tokens owed to the server for the payment of the OEV bid + /// @param data Any data passed through by the caller via the IApi3ServerV1OevExtension#payOevBid call + function api3ServerV1OevExtensionPayOevBidCallback( + uint256 amountOwed, + bytes calldata data + ) external; +} diff --git a/src/chain.ts b/src/chain.ts index b89db61..df04bf3 100644 --- a/src/chain.ts +++ b/src/chain.ts @@ -16,13 +16,13 @@ import { } from '../typechain-types'; export const baseContractAddresses = { - api3OevCbethEthProxy: '0x7583f6435cAD95bcF30C2dD7fDbfD3c5Ab58Ce4C', - api3OevEthUsdProxy: '0x86313242dBfedD9C52733a0Ed384E917424A7436', - api3OevWstethStethProxy: '0x3739c04CfE9d4750Bb40fc46904d592f3ed8EdEf', - api3OevStethUsdProxy: '0x93d2D4Aae8143E2a067a54C8138Dc8054Ad79910', - api3OevUsdcUsdProxy: '0x773f1a8E77Bd9e91a84bD80Bf35e67e4989D5C4C', + api3OevCbethEthProxy: '0x5bbeEE12b8779E1809f52441a9c2de6a3eD3dEA5', + api3OevEthUsdProxy: '0x06314AbEEA3f6A308741b1Df209f55edB58354AB', + api3OevWstethStethProxy: '0x63e3509F3Dc9f055441369A9d54B04D6FeE4adaf', + api3OevStethUsdProxy: '0x75e5A34dad31D1DB19dBeC6fFB82EbBee5e0b9ab', + api3OevUsdcUsdProxy: '0x6FBea86770975081D935456FAFfAB88524a0d1EF', api3ServerV1: '0x709944a48cAf83535e43471680fDA4905FB3920a', - api3ServerV1OevExtension: '0xF930D1E37098128326F8731a476347f0840337cA', + api3ServerV1OevExtension: '0x6a6F4b90ac94Df292fAe521b24b94cE8E58EB91e', multicall3: '0xcA11bde05977b3631167028862bE2a173976CA11', } as const; diff --git a/src/constants.ts b/src/constants.ts index 89e7c53..97067ed 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -22,4 +22,4 @@ export const OEV_AWARD_BLOCK_RANGE = 500; export const POSITIONS_CLOSE_TO_LIQUIDATION_LOG_SIZE = 10; -export const COMET_ADDRESS = '0xa193bcE4554663FECde688D5921dF38D4D41AA96'; +export const COMET_ADDRESS = '0x15729c94243dc9140EaEEb37259A45D0b3550192'; diff --git a/src/lib/oev-liquidation.ts b/src/lib/oev-liquidation.ts index 1bcd60e..6ca6dff 100644 --- a/src/lib/oev-liquidation.ts +++ b/src/lib/oev-liquidation.ts @@ -267,14 +267,17 @@ export const liquidatePositions = async ( // Prepare liquidation calldata. const liquidationParams: Compound3Liquidator.PayBidAndUpdateFeedsAndLiquidateParamsStruct = { - liquidateParams: { - liquidatableAccounts: liquidatablePositions.map(({ position }) => position), - maxAmountsToPurchase: [MaxUint256, MaxUint256, MaxUint256], - liquidationThreshold: 0n, + payOevBidCallbackData: { + signedDataArray, + liquidateParams: { + liquidatableAccounts: liquidatablePositions.map(({ position }) => position), + maxAmountsToPurchase: [MaxUint256, MaxUint256, MaxUint256], + liquidationThreshold: 0n, + }, }, bidAmount, signature, - signedDataArray, + signedDataTimestampCutoff, };