Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions contracts/base/Dispatcher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ abstract contract Dispatcher is
// 0x00 <= command < 0x08
if (command < Commands.V2_SWAP_EXACT_IN) {
if (command == Commands.V3_SWAP_EXACT_IN) {
// equivalent: abi.decode(inputs, (address, uint256, uint256, bytes, bool))
// equivalent: abi.decode(inputs, (address, uint256, uint256, bytes, bool, uint256[]))
address recipient;
uint256 amountIn;
uint256 amountOutMin;
Expand All @@ -78,10 +78,11 @@ abstract contract Dispatcher is
payerIsUser := calldataload(add(inputs.offset, 0x80))
}
bytes calldata path = inputs.toBytes(3);
uint256[] calldata minHopPriceX36 = inputs.toUint256Array(5);
address payer = payerIsUser ? msgSender() : address(this);
v3SwapExactInput(map(recipient), amountIn, amountOutMin, path, payer);
v3SwapExactInput(map(recipient), amountIn, amountOutMin, path, payer, minHopPriceX36);
} else if (command == Commands.V3_SWAP_EXACT_OUT) {
// equivalent: abi.decode(inputs, (address, uint256, uint256, bytes, bool))
// equivalent: abi.decode(inputs, (address, uint256, uint256, bytes, bool, uint256[]))
address recipient;
uint256 amountOut;
uint256 amountInMax;
Expand All @@ -94,8 +95,9 @@ abstract contract Dispatcher is
payerIsUser := calldataload(add(inputs.offset, 0x80))
}
bytes calldata path = inputs.toBytes(3);
uint256[] calldata minHopPriceX36 = inputs.toUint256Array(5);
address payer = payerIsUser ? msgSender() : address(this);
v3SwapExactOutput(map(recipient), amountOut, amountInMax, path, payer);
v3SwapExactOutput(map(recipient), amountOut, amountInMax, path, payer, minHopPriceX36);
} else if (command == Commands.PERMIT2_TRANSFER_FROM) {
// equivalent: abi.decode(inputs, (address, address, uint160))
address token;
Expand Down Expand Up @@ -157,14 +159,24 @@ abstract contract Dispatcher is
bips := calldataload(add(inputs.offset, 0x40))
}
Payments.payPortion(token, map(recipient), bips);
} else if (command == Commands.PAY_PORTION_FULL_PRECISION) {
// equivalent: abi.decode(inputs, (address, address, uint256))
address token;
address recipient;
uint256 portion;
assembly {
token := calldataload(inputs.offset)
recipient := calldataload(add(inputs.offset, 0x20))
portion := calldataload(add(inputs.offset, 0x40))
}
Payments.payPortionFullPrecision(token, map(recipient), portion);
} else {
// placeholder area for command 0x07
revert InvalidCommandType(command);
}
} else {
// 0x08 <= command < 0x10
if (command == Commands.V2_SWAP_EXACT_IN) {
// equivalent: abi.decode(inputs, (address, uint256, uint256, bytes, bool))
// equivalent: abi.decode(inputs, (address, uint256, uint256, address[], bool, uint256[]))
address recipient;
uint256 amountIn;
uint256 amountOutMin;
Expand All @@ -177,10 +189,11 @@ abstract contract Dispatcher is
payerIsUser := calldataload(add(inputs.offset, 0x80))
}
address[] calldata path = inputs.toAddressArray(3);
uint256[] calldata minHopPriceX36 = inputs.toUint256Array(5);
address payer = payerIsUser ? msgSender() : address(this);
v2SwapExactInput(map(recipient), amountIn, amountOutMin, path, payer);
v2SwapExactInput(map(recipient), amountIn, amountOutMin, path, payer, minHopPriceX36);
} else if (command == Commands.V2_SWAP_EXACT_OUT) {
// equivalent: abi.decode(inputs, (address, uint256, uint256, bytes, bool))
// equivalent: abi.decode(inputs, (address, uint256, uint256, address[], bool, uint256[]))
address recipient;
uint256 amountOut;
uint256 amountInMax;
Expand All @@ -193,8 +206,9 @@ abstract contract Dispatcher is
payerIsUser := calldataload(add(inputs.offset, 0x80))
}
address[] calldata path = inputs.toAddressArray(3);
uint256[] calldata minHopPriceX36 = inputs.toUint256Array(5);
address payer = payerIsUser ? msgSender() : address(this);
v2SwapExactOutput(map(recipient), amountOut, amountInMax, path, payer);
v2SwapExactOutput(map(recipient), amountOut, amountInMax, path, payer, minHopPriceX36);
} else if (command == Commands.PERMIT2_PERMIT) {
// equivalent: abi.decode(inputs, (IAllowanceTransfer.PermitSingle, bytes))
IAllowanceTransfer.PermitSingle calldata permitSingle;
Expand Down
2 changes: 1 addition & 1 deletion contracts/libraries/Commands.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ library Commands {
uint256 constant SWEEP = 0x04;
uint256 constant TRANSFER = 0x05;
uint256 constant PAY_PORTION = 0x06;
// COMMAND_PLACEHOLDER = 0x07;
uint256 constant PAY_PORTION_FULL_PRECISION = 0x07;

// Command Types where 0x08<=value<=0x0f, executed in the second nested-if block
uint256 constant V2_SWAP_EXACT_IN = 0x08;
Expand Down
3 changes: 3 additions & 0 deletions contracts/libraries/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@ library Constants {

/// @dev The minimum length of an encoding that contains 2 or more pools
uint256 internal constant MULTIPLE_V3_POOLS_MIN_LENGTH = V3_POP_OFFSET + NEXT_V3_POOL_OFFSET;

/// @dev Precision multiplier for per-hop price calculations
uint256 internal constant PRICE_PRECISION = 1e36;
}
18 changes: 18 additions & 0 deletions contracts/modules/Payments.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ abstract contract Payments is PaymentsImmutables {

error InsufficientToken();
error InsufficientETH();
error InvalidPortion();

/// @notice Pays an amount of ETH or ERC20 to a recipient
/// @param token The token to pay (can be ETH using Constants.ETH)
Expand Down Expand Up @@ -50,6 +51,23 @@ abstract contract Payments is PaymentsImmutables {
}
}

/// @notice Pays a proportion of the contract's ETH or ERC20 to a recipient with 1e18 precision
/// @param token The token to pay (can be ETH using Constants.ETH)
/// @param recipient The address that will receive payment
/// @param portion Portion of whole balance of the contract, where 1e18 represents 100%
function payPortionFullPrecision(address token, address recipient, uint256 portion) internal {
if (portion > 1e18) revert InvalidPortion();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not immediately clear that 1e18 is precision value. Maybe use a constant here?

if (token == Constants.ETH) {
uint256 balance = address(this).balance;
uint256 amount = balance * portion / 1e18;
recipient.safeTransferETH(amount);
} else {
uint256 balance = ERC20(token).balanceOf(address(this));
uint256 amount = balance * portion / 1e18;
ERC20(token).safeTransfer(recipient, amount);
}
}

/// @notice Sweeps all of the contract's ERC20 or ETH to an address
/// @param token The token to sweep (can be ETH using Constants.ETH)
/// @param recipient The address that will receive payment
Expand Down
4 changes: 2 additions & 2 deletions contracts/modules/V3ToV4Migrator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ abstract contract V3ToV4Migrator is MigratorImmutables {
uint256 action = uint8(actions[actionIndex]);

if (
action == Actions.INCREASE_LIQUIDITY || action == Actions.DECREASE_LIQUIDITY
|| action == Actions.BURN_POSITION
action == Actions.INCREASE_LIQUIDITY || action == Actions.INCREASE_LIQUIDITY_FROM_DELTAS
|| action == Actions.DECREASE_LIQUIDITY || action == Actions.BURN_POSITION
) {
revert OnlyMintAllowed();
}
Expand Down
45 changes: 32 additions & 13 deletions contracts/modules/uniswap/v2/V2SwapRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ abstract contract V2SwapRouter is UniswapImmutables, Permit2Payments {
error V2TooLittleReceived();
error V2TooMuchRequested();
error V2InvalidPath();
error V2TooLittleReceivedPerHop(uint256 hopIndex, uint256 minPrice, uint256 price);
error V2InvalidHopPriceLength();

function _v2Swap(address[] calldata path, address recipient, address pair) private {
function _v2Swap(address[] calldata path, address recipient, address pair, uint256[] calldata minHopPriceX36)
private
{
unchecked {
if (path.length < 2) revert V2InvalidPath();

// cached to save on duplicate operations
(address token0,) = UniswapV2Library.sortTokens(path[0], path[1]);
uint256 finalPairIndex = path.length - 1;
Expand All @@ -29,6 +31,11 @@ abstract contract V2SwapRouter is UniswapImmutables, Permit2Payments {
input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
uint256 amountInput = ERC20(input).balanceOf(pair) - reserveInput;
uint256 amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput);
if (minHopPriceX36.length != 0) {
uint256 price = amountOutput * Constants.PRICE_PRECISION / amountInput;
Comment on lines 33 to +35
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw this operation will overflow for any values over 2^137, which I assume is fine but just flagging since seems like V2 amounts are uint256 instead of uint128 like v3/v4

uint256 minPrice = minHopPriceX36[i];
if (price < minPrice) revert V2TooLittleReceivedPerHop(i, minPrice, price);
}
(uint256 amount0Out, uint256 amount1Out) =
input == token0 ? (uint256(0), amountOutput) : (amountOutput, uint256(0));
address nextPair;
Expand All @@ -49,16 +56,22 @@ abstract contract V2SwapRouter is UniswapImmutables, Permit2Payments {
/// @param amountOutMinimum The minimum desired amount of output tokens
/// @param path The path of the trade as an array of token addresses
/// @param payer The address that will be paying the input
/// @param minHopPriceX36 Per-hop minimum price array in 1e36 precision (empty to disable)
function v2SwapExactInput(
address recipient,
uint256 amountIn,
uint256 amountOutMinimum,
address[] calldata path,
address payer
address payer,
uint256[] calldata minHopPriceX36
) internal {
address firstPair = UniswapV2Library.pairFor(
UNISWAP_V2_FACTORY, UNISWAP_V2_PAIR_INIT_CODE_HASH, path[0], path[1]
);
if (path.length < 2) revert V2InvalidPath();
if (minHopPriceX36.length != 0 && minHopPriceX36.length != path.length - 1) {
revert V2InvalidHopPriceLength();
}

address firstPair =
UniswapV2Library.pairFor(UNISWAP_V2_FACTORY, UNISWAP_V2_PAIR_INIT_CODE_HASH, path[0], path[1]);
if (
amountIn != Constants.ALREADY_PAID // amountIn of 0 to signal that the pair already has the tokens
) {
Expand All @@ -68,7 +81,7 @@ abstract contract V2SwapRouter is UniswapImmutables, Permit2Payments {
ERC20 tokenOut = ERC20(path[path.length - 1]);
uint256 balanceBefore = tokenOut.balanceOf(recipient);

_v2Swap(path, recipient, firstPair);
_v2Swap(path, recipient, firstPair, minHopPriceX36);

uint256 amountOut = tokenOut.balanceOf(recipient) - balanceBefore;
if (amountOut < amountOutMinimum) revert V2TooLittleReceived();
Expand All @@ -80,19 +93,25 @@ abstract contract V2SwapRouter is UniswapImmutables, Permit2Payments {
/// @param amountInMaximum The maximum desired amount of input tokens
/// @param path The path of the trade as an array of token addresses
/// @param payer The address that will be paying the input
/// @param minHopPriceX36 Per-hop minimum price array in 1e36 precision (empty to disable)
function v2SwapExactOutput(
address recipient,
uint256 amountOut,
uint256 amountInMaximum,
address[] calldata path,
address payer
address payer,
uint256[] calldata minHopPriceX36
) internal {
(uint256 amountIn, address firstPair) = UniswapV2Library.getAmountInMultihop(
UNISWAP_V2_FACTORY, UNISWAP_V2_PAIR_INIT_CODE_HASH, amountOut, path
);
if (path.length < 2) revert V2InvalidPath();
if (minHopPriceX36.length != 0 && minHopPriceX36.length != path.length - 1) {
revert V2InvalidHopPriceLength();
}

(uint256 amountIn, address firstPair) =
UniswapV2Library.getAmountInMultihop(UNISWAP_V2_FACTORY, UNISWAP_V2_PAIR_INIT_CODE_HASH, amountOut, path);
if (amountIn > amountInMaximum) revert V2TooMuchRequested();

payOrPermit2Transfer(path[0], payer, firstPair, amountIn);
_v2Swap(path, recipient, firstPair);
_v2Swap(path, recipient, firstPair, minHopPriceX36);
}
}
19 changes: 16 additions & 3 deletions contracts/modules/uniswap/v3/BytesLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,18 @@ library BytesLib {
pure
returns (uint256 length, uint256 offset)
{
uint256 relativeOffset;
assembly {
// The offset of the `_arg`-th element is `32 * arg`, which stores the offset of the length pointer.
// shl(5, x) is equivalent to mul(32, x)
let lengthPtr := add(_bytes.offset, calldataload(add(_bytes.offset, shl(5, _arg))))
length := calldataload(lengthPtr)
offset := add(lengthPtr, 0x20)
relativeOffset := sub(offset, _bytes.offset)
let relativeOffset := sub(offset, _bytes.offset)
if lt(_bytes.length, add(shl(5, length), relativeOffset)) {
mstore(0, 0x3b99b53d) // SliceOutOfBounds()
revert(0x1c, 0x04)
}
}
if (_bytes.length < length + relativeOffset) revert SliceOutOfBounds();
}

/// @notice Decode the `_arg`-th element in `_bytes` as `address[]`
Expand All @@ -73,6 +75,17 @@ library BytesLib {
}
}

/// @notice Decode the `_arg`-th element in `_bytes` as `uint256[]`
/// @param _bytes The input bytes string to extract a uint256 array from
/// @param _arg The index of the argument to extract
function toUint256Array(bytes calldata _bytes, uint256 _arg) internal pure returns (uint256[] calldata res) {
(uint256 length, uint256 offset) = toLengthOffset(_bytes, _arg);
assembly {
res.length := length
res.offset := offset
}
}

/// @notice Equivalent to abi.decode(bytes, bytes[])
/// @param _bytes The input bytes string to extract an parameters from
function decodeCommandsAndInputs(bytes calldata _bytes) internal pure returns (bytes calldata, bytes[] calldata) {
Expand Down
Loading
Loading