ConcentratedLiquidityAdapter

0x31aa702b208b698bdf9e1c750af230b5af64d655

Verification
Verified
v0.8.30+commit.73712a01
Type
Contract
8,262 bytes
ABI entries
15
1 read · 1 write
License
none

Contract information

Address
0x31aa702b208b698bdf9e1c750af230b5af64d655
Chain
Robinhood Chain (4663)
Compiler
v0.8.30+commit.73712a01
Optimization
Enabled
Creation tx
0x8187a64ab2…bcd60848f8

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (1)

revShareRouter()address

Events (1)

TokenPlatformFeePaid

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "router_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "ApprovalFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "Expired",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidInput",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NativeTransferFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "OnlyRouter",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrantCall",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TransferFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TransferFromFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UnsupportedOperation",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "payer",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "TokenPlatformFeePaid",
    "type": "event"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "enum IRevShareAdapter.Operation",
            "name": "operation",
            "type": "uint8"
          },
          {
            "internalType": "address",
            "name": "payer",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "recipient",
            "type": "address"
          },
          {
            "internalType": "address[]",
            "name": "tokens",
            "type": "address[]"
          },
          {
            "internalType": "uint256[]",
            "name": "amounts",
            "type": "uint256[]"
          },
          {
            "internalType": "uint256",
            "name": "deadline",
            "type": "uint256"
          },
          {
            "internalType": "uint16",
            "name": "platformFeeBps",
            "type": "uint16"
          },
          {
            "internalType": "address",
            "name": "feeRecipient",
            "type": "address"
          },
          {
            "internalType": "bytes",
            "name": "data",
            "type": "bytes"
          }
        ],
        "internalType": "struct IRevShareAdapter.Request",
        "name": "request",
        "type": "tuple"
      }
    ],
    "name": "execute",
    "outputs": [
      {
        "internalType": "bytes",
        "name": "result",
        "type": "bytes"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "revShareRouter",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

import {BaseAdapter} from "./BaseAdapter.sol";
import {SafeTransferLib} from "../libraries/SafeTransferLib.sol";

interface IConcentratedPositionManager {
    struct MintParams {
        address token0; address token1; uint24 fee; int24 tickLower; int24 tickUpper;
        uint256 amount0Desired; uint256 amount1Desired; uint256 amount0Min; uint256 amount1Min;
        address recipient; uint256 deadline;
    }
    struct IncreaseLiquidityParams {
        uint256 tokenId; uint256 amount0Desired; uint256 amount1Desired;
        uint256 amount0Min; uint256 amount1Min; uint256 deadline;
    }
    struct DecreaseLiquidityParams {
        uint256 tokenId; uint128 liquidity; uint256 amount0Min; uint256 amount1Min; uint256 deadline;
    }
    struct CollectParams { uint256 tokenId; address recipient; uint128 amount0Max; uint128 amount1Max; }

    function createAndInitializePoolIfNecessary(address, address, uint24, uint160)
        external payable returns (address);
    function mint(MintParams calldata) external payable returns (uint256, uint128, uint256, uint256);
    function increaseLiquidity(IncreaseLiquidityParams calldata)
        external payable returns (uint128, uint256, uint256);
    function decreaseLiquidity(DecreaseLiquidityParams calldata) external payable returns (uint256, uint256);
    function collect(CollectParams calldata) external payable returns (uint256, uint256);
    function refundETH() external payable;
}

interface IConcentratedSwapRouter {
    struct ExactInputSingleParams {
        address tokenIn; address tokenOut; uint24 fee; address recipient;
        uint256 amountIn; uint256 amountOutMinimum; uint160 sqrtPriceLimitX96;
    }
    function exactInputSingle(ExactInputSingleParams calldata) external payable returns (uint256);
}

interface IDeadlineConcentratedSwapRouter {
    struct ExactInputSingleParams {
        address tokenIn; address tokenOut; uint24 fee; address recipient; uint256 deadline;
        uint256 amountIn; uint256 amountOutMinimum; uint160 sqrtPriceLimitX96;
    }
    function exactInputSingle(ExactInputSingleParams calldata) external payable returns (uint256);
}

interface IConcentratedWrappedNative {
    function deposit() external payable;
    function withdraw(uint256 amount) external;
}

/// @notice Adapter for Uniswap V3-compatible position managers and swap routers.
/// @dev Compatible with SushiSwap V3 by setting `swapRouterUsesDeadline` for its router ABI.
contract ConcentratedLiquidityAdapter is BaseAdapter {
    using SafeTransferLib for address;

    struct Venue {
        address factory;
        address positionManager;
        address swapRouter;
        address wrappedNative;
        bool swapRouterUsesDeadline;
    }

    struct PoolConfig { Venue venue; address token0; address token1; uint24 fee; uint160 sqrtPriceX96; }
    struct SwapConfig {
        Venue venue; address tokenIn; address tokenOut; uint24 fee;
        uint256 minAmountOut; uint160 sqrtPriceLimitX96; bool nativeIn; bool nativeOut;
    }
    struct MintConfig {
        Venue venue; address token0; address token1; uint24 fee; int24 tickLower; int24 tickUpper;
        uint256 amount0Min; uint256 amount1Min;
    }
    struct PositionConfig {
        Venue venue; uint256 tokenId; uint128 liquidity;
        uint256 amount0Min; uint256 amount1Min; uint128 amount0Max; uint128 amount1Max;
    }

    constructor(address router_) BaseAdapter(router_) {}

    function execute(Request calldata request)
        external payable override onlyRouter nonReentrant returns (bytes memory result)
    {
        _validate(request);
        if (request.operation == Operation.CreatePool) return _createPool(request);
        if (request.operation == Operation.Swap) return _swap(request);
        if (request.operation == Operation.AddLiquidity) return _mint(request);
        if (request.operation == Operation.IncreaseLiquidity) return _increase(request);
        if (request.operation == Operation.RemoveLiquidity) return _decreaseAndCollect(request);
        if (request.operation == Operation.CollectFees) return _collect(request);
        revert UnsupportedOperation();
    }

    function _createPool(Request calldata request) private returns (bytes memory) {
        if (msg.value != 0 || request.tokens.length != 0 || request.amounts.length != 0) revert InvalidInput();
        PoolConfig memory config = abi.decode(request.data, (PoolConfig));
        _validateVenue(config.venue);
        address pool = IConcentratedPositionManager(config.venue.positionManager)
            .createAndInitializePoolIfNecessary(config.token0, config.token1, config.fee, config.sqrtPriceX96);
        return abi.encode(pool);
    }

    function _swap(Request calldata request) private returns (bytes memory) {
        if (request.amounts.length != 1) revert InvalidInput();
        SwapConfig memory config = abi.decode(request.data, (SwapConfig));
        _validateVenue(config.venue);
        if (
            (config.nativeIn && config.nativeOut)
                || (config.nativeIn && config.tokenIn != config.venue.wrappedNative)
                || (config.nativeOut && config.tokenOut != config.venue.wrappedNative)
        ) revert InvalidInput();

        uint256 wrappedBalanceBefore = SafeTransferLib.balanceOf(
            config.venue.wrappedNative, address(this)
        );
        uint256 amountIn;
        if (config.nativeIn) {
            if (request.tokens.length != 0 || msg.value != request.amounts[0]) revert InvalidInput();
            amountIn = request.amounts[0];
            IConcentratedWrappedNative(config.venue.wrappedNative).deposit{value: amountIn}();
        } else {
            if (
                msg.value != 0 || request.tokens.length != 1
                    || request.tokens[0] != config.tokenIn
            ) revert InvalidInput();
            amountIn = _pullSwapInput(request, config.tokenIn, request.amounts[0]);
        }

        address swapRecipient = config.nativeOut ? address(this) : request.recipient;
        uint256 nativeOutputBalanceBefore = config.nativeOut
            ? SafeTransferLib.balanceOf(config.venue.wrappedNative, address(this))
            : 0;
        uint256 amountOut = _performSwap(config, amountIn, swapRecipient, request.deadline);

        if (config.nativeOut) {
            _deliverNativeOutput(
                config.venue.wrappedNative,
                request.recipient,
                nativeOutputBalanceBefore,
                amountOut,
                config.minAmountOut
            );
        }
        if (config.nativeIn) {
            _refundWrappedNative(config.venue.wrappedNative, request.payer, wrappedBalanceBefore);
        } else {
            _refund(config.tokenIn, request.payer);
        }
        _refundNative(request.payer);
        return abi.encode(amountOut);
    }

    function _performSwap(
        SwapConfig memory config,
        uint256 amountIn,
        address recipient,
        uint256 deadline
    ) private returns (uint256 amountOut) {
        config.tokenIn.forceApprove(config.venue.swapRouter, amountIn);
        if (config.venue.swapRouterUsesDeadline) {
            amountOut = IDeadlineConcentratedSwapRouter(config.venue.swapRouter).exactInputSingle(
                IDeadlineConcentratedSwapRouter.ExactInputSingleParams({
                    tokenIn: config.tokenIn, tokenOut: config.tokenOut, fee: config.fee,
                    recipient: recipient, deadline: deadline, amountIn: amountIn,
                    amountOutMinimum: config.minAmountOut, sqrtPriceLimitX96: config.sqrtPriceLimitX96
                })
            );
        } else {
            amountOut = IConcentratedSwapRouter(config.venue.swapRouter).exactInputSingle(
                IConcentratedSwapRouter.ExactInputSingleParams({
                    tokenIn: config.tokenIn, tokenOut: config.tokenOut, fee: config.fee,
                    recipient: recipient, amountIn: amountIn,
                    amountOutMinimum: config.minAmountOut, sqrtPriceLimitX96: config.sqrtPriceLimitX96
                })
            );
        }
        config.tokenIn.forceApprove(config.venue.swapRouter, 0);
    }

    function _deliverNativeOutput(
        address wrappedNative,
        address recipient,
        uint256 balanceBefore,
        uint256 reportedAmountOut,
        uint256 minimumAmountOut
    ) private {
        uint256 received = SafeTransferLib.balanceOf(wrappedNative, address(this)) - balanceBefore;
        if (received != reportedAmountOut || received < minimumAmountOut) revert InvalidInput();
        IConcentratedWrappedNative(wrappedNative).withdraw(received);
        SafeTransferLib.safeTransferNative(recipient, received);
    }

    function _refundWrappedNative(address wrappedNative, address payer, uint256 balanceBefore)
        private
    {
        uint256 balanceAfter = SafeTransferLib.balanceOf(wrappedNative, address(this));
        if (balanceAfter > balanceBefore) {
            wrappedNative.safeTransfer(payer, balanceAfter - balanceBefore);
        }
    }

    function _mint(Request calldata request) private returns (bytes memory) {
        if (msg.value != 0 || request.tokens.length != 2 || request.amounts.length != 2) revert InvalidInput();
        MintConfig memory config = abi.decode(request.data, (MintConfig));
        _validateVenue(config.venue);
        if (request.tokens[0] != config.token0 || request.tokens[1] != config.token1) revert InvalidInput();
        _fundPosition(config.venue.positionManager, request);
        (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) =
            IConcentratedPositionManager(config.venue.positionManager).mint(
                IConcentratedPositionManager.MintParams({
                    token0: config.token0, token1: config.token1, fee: config.fee,
                    tickLower: config.tickLower, tickUpper: config.tickUpper,
                    amount0Desired: request.amounts[0], amount1Desired: request.amounts[1],
                    amount0Min: config.amount0Min, amount1Min: config.amount1Min,
                    recipient: request.recipient, deadline: request.deadline
                })
            );
        _clearAndRefund(config.venue.positionManager, request);
        return abi.encode(tokenId, liquidity, amount0, amount1);
    }

    function _increase(Request calldata request) private returns (bytes memory) {
        if (msg.value != 0 || request.tokens.length != 2 || request.amounts.length != 2) revert InvalidInput();
        PositionConfig memory config = abi.decode(request.data, (PositionConfig));
        _validateVenue(config.venue);
        _fundPosition(config.venue.positionManager, request);
        (uint128 liquidity, uint256 amount0, uint256 amount1) =
            IConcentratedPositionManager(config.venue.positionManager).increaseLiquidity(
                IConcentratedPositionManager.IncreaseLiquidityParams({
                    tokenId: config.tokenId, amount0Desired: request.amounts[0],
                    amount1Desired: request.amounts[1], amount0Min: config.amount0Min,
                    amount1Min: config.amount1Min, deadline: request.deadline
                })
            );
        _clearAndRefund(config.venue.positionManager, request);
        return abi.encode(liquidity, amount0, amount1);
    }

    function _decreaseAndCollect(Request calldata request) private returns (bytes memory) {
        if (msg.value != 0 || request.tokens.length != 0 || request.amounts.length != 0) revert InvalidInput();
        PositionConfig memory config = abi.decode(request.data, (PositionConfig));
        _validateVenue(config.venue);
        (uint256 principal0, uint256 principal1) =
            IConcentratedPositionManager(config.venue.positionManager).decreaseLiquidity(
                IConcentratedPositionManager.DecreaseLiquidityParams({
                    tokenId: config.tokenId, liquidity: config.liquidity,
                    amount0Min: config.amount0Min, amount1Min: config.amount1Min,
                    deadline: request.deadline
                })
            );
        (uint256 collected0, uint256 collected1) = _collectPosition(config, request.recipient);
        return abi.encode(principal0, principal1, collected0, collected1);
    }

    function _collect(Request calldata request) private returns (bytes memory) {
        if (msg.value != 0 || request.tokens.length != 0 || request.amounts.length != 0) revert InvalidInput();
        PositionConfig memory config = abi.decode(request.data, (PositionConfig));
        _validateVenue(config.venue);
        (uint256 amount0, uint256 amount1) = _collectPosition(config, request.recipient);
        return abi.encode(amount0, amount1);
    }

    function _collectPosition(PositionConfig memory config, address recipient)
        private returns (uint256 amount0, uint256 amount1)
    {
        return IConcentratedPositionManager(config.venue.positionManager).collect(
            IConcentratedPositionManager.CollectParams({
                tokenId: config.tokenId, recipient: recipient,
                amount0Max: config.amount0Max == 0 ? type(uint128).max : config.amount0Max,
                amount1Max: config.amount1Max == 0 ? type(uint128).max : config.amount1Max
            })
        );
    }

    function _fundPosition(address manager, Request calldata request) private {
        for (uint256 i; i < 2; ++i) {
            _pull(request.tokens[i], request.payer, request.amounts[i]);
            request.tokens[i].forceApprove(manager, request.amounts[i]);
        }
    }

    function _clearAndRefund(address manager, Request calldata request) private {
        for (uint256 i; i < 2; ++i) {
            request.tokens[i].forceApprove(manager, 0);
            _refund(request.tokens[i], request.payer);
        }
    }

    function _validateVenue(Venue memory venue) private view {
        _requireContract(venue.positionManager);
        _requireContract(venue.swapRouter);
        if (venue.factory == address(0) || venue.wrappedNative == address(0)) revert InvalidAddress();
    }
}
Chain explorer6126msChain node81ms