Skip to content

Commit

Permalink
chore: uniswap death
Browse files Browse the repository at this point in the history
  • Loading branch information
exception committed Sep 5, 2024
1 parent efad095 commit 3bed6e7
Show file tree
Hide file tree
Showing 8 changed files with 569 additions and 51 deletions.
78 changes: 28 additions & 50 deletions contracts/callers/UniswapV3Caller.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,22 @@ import { Base } from "../shared/Base.sol";
import { TokensHandler } from "../shared/TokensHandler.sol";
import { Weth } from "../shared/Weth.sol";
// solhint-disable-next-line
import { CallbackValidation } from "@uniswap/v3-periphery/contracts/libraries/CallbackValidation.sol";
import { TickMath } from "@uniswap/v3-core/contracts/libraries/TickMath.sol";
import { CallbackValidation } from "./helpers/uniswap/CallbackValidation.sol";
import { TickMath } from "./helpers/uniswap/TickMath.sol";
// solhint-disable-next-line
import { UniswapV3Swap } from "@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol";
import { IUniswapV3SwapCallback } from "@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol";
import { Path } from "./helpers/uniswap/Path.sol";

contract UniswapV3Caller is TokensHandler, Weth {
using Path for bytes;

address internal constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

struct SwapCallbackData {
bytes path;
address payer;
}

constructor(address _weth) Weth(_weth) {}

function callBytes(bytes calldata callerCallData) external {
Expand All @@ -30,7 +38,6 @@ contract UniswapV3Caller is TokensHandler, Weth {

if (inputToken == ETH) {
depositEth(fixedSideAmount);
inputToken = getWeth();
}

if (isExactInput) {
Expand All @@ -39,16 +46,14 @@ contract UniswapV3Caller is TokensHandler, Weth {
exactOutputSwap(inputToken, outputToken, pool, fixedSideAmount);
}

if (outputToken == ETH) {
withdrawEth();
Base.transfer(ETH, msg.sender, Base.getBalance(ETH));
} else {
Base.transfer(outputToken, msg.sender, Base.getBalance(outputToken));
}
// Unwrap weth if necessary
if (outputToken == ETH) withdrawEth();

if (inputToken != ETH) {
Base.transfer(inputToken, msg.sender, Base.getBalance(inputToken));
}
// In case of non-zero input token, transfer the remaining amount back to `msg.sender`
Base.transfer(inputToken, msg.sender, Base.getBalance(inputToken));

// In case of non-zero output token, transfer the total balance to `msg.sender`
Base.transfer(outputToken, msg.sender, Base.getBalance(outputToken));
}

function exactInputSwap(
Expand All @@ -65,7 +70,7 @@ contract UniswapV3Caller is TokensHandler, Weth {
address(this),
inputToken < outputToken,
int256(amountIn),
inputToken < outputToken ? -TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1,
inputToken < outputToken ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1,
abi.encode(inputToken, outputToken)
);
}
Expand All @@ -78,58 +83,31 @@ contract UniswapV3Caller is TokensHandler, Weth {
) internal {
IUniswapV3Pool v3Pool = IUniswapV3Pool(pool);

int256 amountInMaximum = int256(
calculateMaxInput(inputToken, outputToken, pool, amountOut)
);

SafeERC20.safeApprove(IERC20(inputToken), pool, uint256(amountInMaximum));

(int256 amount0, int256 amount1) = v3Pool.swap(
address(this),
inputToken < outputToken,
-int256(amountOut),
inputToken < outputToken ? -TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1,
inputToken < outputToken ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1,
abi.encode(inputToken, outputToken)
);

// Refund any excess tokens
uint256 refundAmount = uint256(
amountInMaximum - (inputToken < outputToken ? amount0 : amount1)
);
if (refundAmount > 0) {
SafeERC20.safeTransfer(IERC20(inputToken), msg.sender, refundAmount);
}
}

function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes calldata data
) external {
(address inputToken, address outputToken) = abi.decode(data, (address, address));

if (amount0Delta > 0) {
SafeERC20.safeTransfer(IERC20(inputToken), msg.sender, uint256(amount0Delta));
} else {
SafeERC20.safeTransfer(IERC20(outputToken), msg.sender, uint256(amount1Delta));
}
}

function calculateMaxInput(
address inputToken,
address outputToken,
address pool,
uint256 amountOut
) internal view returns (uint256 memory maxInput) {
IUniswapV3Pool v3Pool = IUniswapV3Pool(pool);
SwapCallbackData memory callbackData = abi.decode(data, (SwapCallbackData));
(address tokenIn, address tokenOut, ) = callbackData.path.decodeFirstPool();

(uint160 sqrtRatioX96, , , , , , ) = v3Pool.slot0();
uint256 price = (sqrtRatioX96 * sqrtRatioX96) / (2 ** 96);
(bool isExactInput, uint256 amountToPay) = amount0Delta > 0
? (tokenIn < tokenOut, uint256(amount0Delta))
: (tokenOut < tokenIn, uint256(amount1Delta));

if (inputToken < outputToken) {
return (amountOut * price) / 1e18;
if (isExactInput) {
Base.transfer(tokenIn, msg.sender, amountToPay);
} else {
return (amountOut * 1e18) / price;
Base.transfer(tokenOut, msg.sender, amountToPay);
}
}

Expand Down
104 changes: 104 additions & 0 deletions contracts/callers/helpers/uniswap/BytesLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* @title Solidity Bytes Arrays Utils
* @author Gonçalo Sá <goncalo.sa@consensys.net>
*
* @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity.
* The library lets you concatenate, slice and type cast bytes arrays both in memory and storage.

Check failure on line 7 in contracts/callers/helpers/uniswap/BytesLib.sol

View workflow job for this annotation

GitHub Actions / lint

Line length must be no more than 99 but current length is 102
*/
pragma solidity 0.8.12;

library BytesLib {
function slice(
bytes memory _bytes,
uint256 _start,
uint256 _length
) internal pure returns (bytes memory) {
require(_length + 31 >= _length, "slice_overflow");
require(_start + _length >= _start, "slice_overflow");
require(_bytes.length >= _start + _length, "slice_outOfBounds");

bytes memory tempBytes;

assembly {

Check warning on line 23 in contracts/callers/helpers/uniswap/BytesLib.sol

View workflow job for this annotation

GitHub Actions / lint

Avoid to use inline assembly. It is acceptable only in rare cases
switch iszero(_length)
case 0 {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)

// The first word of the slice result is potentially a partial
// word read from the original array. To read it, we calculate
// the length of that partial word and start copying that many
// bytes into the array. The first word we copy will start with
// data we don't care about, but the last `lengthmod` bytes will
// land at the beginning of the contents of the new array. When
// we're done copying, we overwrite the full first word with
// the actual length of the slice.
let lengthmod := and(_length, 31)

// The multiplication in the next line is necessary
// because when slicing multiples of 32 bytes (lengthmod == 0)
// the following copy loop was copying the origin's length
// and then ending prematurely not copying everything it should.
let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
let end := add(mc, _length)

for {
// The multiplication in the next line has the same exact purpose
// as the one above.
let cc := add(
add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))),
_start
)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
mstore(mc, mload(cc))
}

mstore(tempBytes, _length)

//update free-memory pointer
//allocating the array padded to 32 bytes like the compiler does now
mstore(0x40, and(add(mc, 31), not(31)))
}
//if we want a zero-length slice let's just return a zero-length array
default {
tempBytes := mload(0x40)
//zero out the 32 bytes slice we are about to return
//we need to do it because Solidity does not garbage collect
mstore(tempBytes, 0)

mstore(0x40, add(tempBytes, 0x20))
}
}

return tempBytes;
}

function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) {
require(_start + 20 >= _start, "toAddress_overflow");
require(_bytes.length >= _start + 20, "toAddress_outOfBounds");
address tempAddress;

assembly {

Check warning on line 86 in contracts/callers/helpers/uniswap/BytesLib.sol

View workflow job for this annotation

GitHub Actions / lint

Avoid to use inline assembly. It is acceptable only in rare cases
tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)
}

return tempAddress;
}

function toUint24(bytes memory _bytes, uint256 _start) internal pure returns (uint24) {
require(_start + 3 >= _start, "toUint24_overflow");
require(_bytes.length >= _start + 3, "toUint24_outOfBounds");
uint24 tempUint;

assembly {

Check warning on line 98 in contracts/callers/helpers/uniswap/BytesLib.sol

View workflow job for this annotation

GitHub Actions / lint

Avoid to use inline assembly. It is acceptable only in rare cases
tempUint := mload(add(add(_bytes, 0x3), _start))
}

return tempUint;
}
}
35 changes: 35 additions & 0 deletions contracts/callers/helpers/uniswap/CallbackValidation.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.12;

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

Check warning on line 4 in contracts/callers/helpers/uniswap/CallbackValidation.sol

View workflow job for this annotation

GitHub Actions / lint

global import of path @uniswap/v3-core/contracts/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 "./PoolAddress.sol";

Check warning on line 5 in contracts/callers/helpers/uniswap/CallbackValidation.sol

View workflow job for this annotation

GitHub Actions / lint

global import of path ./PoolAddress.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)

/// @notice Provides validation for callbacks from Uniswap V3 Pools
library CallbackValidation {
/// @notice Returns the address of a valid Uniswap V3 Pool
/// @param factory The contract address of the Uniswap V3 factory
/// @param tokenA The contract address of either token0 or token1
/// @param tokenB The contract address of the other token
/// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip

Check failure on line 13 in contracts/callers/helpers/uniswap/CallbackValidation.sol

View workflow job for this annotation

GitHub Actions / lint

Line length must be no more than 99 but current length is 100
/// @return pool The V3 pool contract address
function verifyCallback(
address factory,
address tokenA,
address tokenB,
uint24 fee
) internal view returns (IUniswapV3Pool pool) {
return verifyCallback(factory, PoolAddress.getPoolKey(tokenA, tokenB, fee));
}

/// @notice Returns the address of a valid Uniswap V3 Pool
/// @param factory The contract address of the Uniswap V3 factory
/// @param poolKey The identifying key of the V3 pool
/// @return pool The V3 pool contract address
function verifyCallback(
address factory,
PoolAddress.PoolKey memory poolKey
) internal view returns (IUniswapV3Pool pool) {
pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
require(msg.sender == address(pool));

Check failure on line 33 in contracts/callers/helpers/uniswap/CallbackValidation.sol

View workflow job for this annotation

GitHub Actions / lint

Provide an error message for require
}
}
30 changes: 30 additions & 0 deletions contracts/callers/helpers/uniswap/Path.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.12;

import "./BytesLib.sol";

Check warning on line 4 in contracts/callers/helpers/uniswap/Path.sol

View workflow job for this annotation

GitHub Actions / lint

global import of path ./BytesLib.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)

/// @title Functions for manipulating path data for multihop swaps
library Path {
using BytesLib for bytes;

/// @dev The length of the bytes encoded address
uint256 private constant ADDR_SIZE = 20;
/// @dev The length of the bytes encoded fee
uint256 private constant FEE_SIZE = 3;

/// @dev The offset of a single token address and pool fee
uint256 private constant NEXT_OFFSET = ADDR_SIZE + FEE_SIZE;

/// @notice Decodes the first pool in path
/// @param path The bytes encoded swap path
/// @return tokenA The first token of the given pool
/// @return tokenB The second token of the given pool
/// @return fee The fee level of the pool
function decodeFirstPool(
bytes memory path
) internal pure returns (address tokenA, address tokenB, uint24 fee) {
tokenA = path.toAddress(0);
fee = path.toUint24(ADDR_SIZE);
tokenB = path.toAddress(NEXT_OFFSET);
}
}
54 changes: 54 additions & 0 deletions contracts/callers/helpers/uniswap/PoolAddress.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.12;

/// @title Provides functions for deriving a pool address from the factory, tokens, and the fee
library PoolAddress {
bytes32 internal constant POOL_INIT_CODE_HASH =
0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54;

/// @notice The identifying key of the pool
struct PoolKey {

Check warning on line 10 in contracts/callers/helpers/uniswap/PoolAddress.sol

View workflow job for this annotation

GitHub Actions / lint

Function order is incorrect, struct definition can not go after state variable declaration (line 6)
address token0;
address token1;
uint24 fee;
}

/// @notice Returns PoolKey: the ordered tokens with the matched fee levels
/// @param tokenA The first token of a pool, unsorted
/// @param tokenB The second token of a pool, unsorted
/// @param fee The fee level of the pool
/// @return Poolkey The pool details with ordered token0 and token1 assignments
function getPoolKey(
address tokenA,
address tokenB,
uint24 fee
) internal pure returns (PoolKey memory) {
if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA);
return PoolKey({ token0: tokenA, token1: tokenB, fee: fee });
}

/// @notice Deterministically computes the pool address given the factory and PoolKey
/// @param factory The Uniswap V3 factory contract address
/// @param key The PoolKey
/// @return pool The contract address of the V3 pool
function computeAddress(
address factory,
PoolKey memory key
) internal pure returns (address pool) {
require(key.token0 < key.token1);

Check failure on line 38 in contracts/callers/helpers/uniswap/PoolAddress.sol

View workflow job for this annotation

GitHub Actions / lint

Provide an error message for require
pool = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
hex"ff",
factory,
keccak256(abi.encode(key.token0, key.token1, key.fee)),
POOL_INIT_CODE_HASH
)
)
)
)
);
}
}
Loading

0 comments on commit 3bed6e7

Please sign in to comment.