Skip to content

Commit

Permalink
Merge pull request #334 from alpaca-finance/feat/path-config-global
Browse files Browse the repository at this point in the history
[main][feat] add path v3 as global config
  • Loading branch information
m100alpaca authored May 9, 2023
2 parents 2d072d3 + 18de80e commit 94b1f6d
Show file tree
Hide file tree
Showing 14 changed files with 734 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";

// ---- Libraries ---- //
import { LibSafeToken } from "./libraries/LibSafeToken.sol";
import { LibPath } from "./libraries/LibPath.sol";
import { LibPath } from "../reader/libraries/LibPath.sol";

// ---- Interfaces ---- //
import { ILiquidationStrategy } from "./interfaces/ILiquidationStrategy.sol";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// SPDX-License-Identifier: BUSL
pragma solidity 0.8.19;

// ---- External Libraries ---- //
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";

// ---- Libraries ---- //
import { LibSafeToken } from "./libraries/LibSafeToken.sol";

// ---- Interfaces ---- //
import { ILiquidationStrategy } from "./interfaces/ILiquidationStrategy.sol";
import { IPancakeSwapRouterV3 } from "./interfaces/IPancakeSwapRouterV3.sol";
import { IERC20 } from "./interfaces/IERC20.sol";
import { IMoneyMarket } from "./interfaces/IMoneyMarket.sol";
import { IUniSwapV3PathReader } from "../reader/interfaces/IUniSwapV3PathReader.sol";

contract PancakeswapV3IbTokenLiquidationStrategy_WithPathReader is ILiquidationStrategy, Ownable {
using LibSafeToken for IERC20;

event LogSetCaller(address _caller, bool _isOk);

error PancakeswapV3IbTokenLiquidationStrategy_Unauthorized();
error PancakeswapV3IbTokenLiquidationStrategy_RepayTokenIsSameWithUnderlyingToken();
error PancakeswapV3IbTokenLiquidationStrategy_PathConfigNotFound(address tokenIn, address tokenOut);

IPancakeSwapRouterV3 internal immutable router;
IMoneyMarket internal immutable moneyMarket;
IUniSwapV3PathReader internal immutable pathReader;

mapping(address => bool) public callersOk;

struct WithdrawParam {
address to;
address token;
uint256 amount;
}

/// @notice allow only whitelisted callers
modifier onlyWhitelistedCallers() {
if (!callersOk[msg.sender]) {
revert PancakeswapV3IbTokenLiquidationStrategy_Unauthorized();
}
_;
}

constructor(
address _router,
address _moneyMarket,
address _pathReader
) {
router = IPancakeSwapRouterV3(_router);
moneyMarket = IMoneyMarket(_moneyMarket);
pathReader = IUniSwapV3PathReader(_pathReader);
}

/// @notice Execute liquidate from collatToken to repayToken
/// @param _ibToken The source token
/// @param _repayToken The destination token
/// @param _ibTokenAmountIn Available amount of source token to trade
/// @param _minReceive Min token receive after swap
function executeLiquidation(
address _ibToken,
address _repayToken,
uint256 _ibTokenAmountIn,
uint256, /*_repayAmount*/
uint256 _minReceive
) external onlyWhitelistedCallers {
// get underlying tokenAddress from MoneyMarket
address _underlyingToken = moneyMarket.getTokenFromIbToken(_ibToken);

// Revert if _underlyingToken and _repayToken are the same address
if (_underlyingToken == _repayToken) {
revert PancakeswapV3IbTokenLiquidationStrategy_RepayTokenIsSameWithUnderlyingToken();
}

bytes memory _path = pathReader.paths(_underlyingToken, _repayToken);
// Revert if no swapPath config for _underlyingToken and _repayToken pair
if (_path.length == 0) {
revert PancakeswapV3IbTokenLiquidationStrategy_PathConfigNotFound(_underlyingToken, _repayToken);
}

// withdraw ibToken from Moneymarket for underlyingToken
uint256 _withdrawnUnderlyingAmount = moneyMarket.withdraw(msg.sender, _ibToken, _ibTokenAmountIn);

// setup params from swap
IPancakeSwapRouterV3.ExactInputParams memory params = IPancakeSwapRouterV3.ExactInputParams({
path: _path,
recipient: msg.sender,
deadline: block.timestamp,
amountIn: _withdrawnUnderlyingAmount,
amountOutMinimum: _minReceive
});

// approve router for swapping
IERC20(_underlyingToken).safeApprove(address(router), _withdrawnUnderlyingAmount);
// swap all ib's underlyingToken to repayToken
router.exactInput(params);
}

/// @notice Set callers ok
/// @param _callers A list of caller addresses
/// @param _isOk An ok flag
function setCallersOk(address[] calldata _callers, bool _isOk) external onlyOwner {
uint256 _length = _callers.length;
for (uint256 _i; _i < _length; ) {
callersOk[_callers[_i]] = _isOk;
emit LogSetCaller(_callers[_i], _isOk);
unchecked {
++_i;
}
}
}

/// @notice Withdraw ERC20 from this contract
/// @param _withdrawParams an array of Withdrawal parameters (to, token, amount)
function withdraw(WithdrawParam[] calldata _withdrawParams) external onlyOwner {
uint256 _length = _withdrawParams.length;
for (uint256 _i; _i < _length; ) {
IERC20(_withdrawParams[_i].token).safeTransfer(_withdrawParams[_i].to, _withdrawParams[_i].amount);

unchecked {
++_i;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";

// ---- Libraries ---- //
import { LibSafeToken } from "./libraries/LibSafeToken.sol";
import { LibPath } from "./libraries/LibPath.sol";
import { LibPath } from "../reader/libraries/LibPath.sol";

// ---- Interfaces ---- //
import { ILiquidationStrategy } from "./interfaces/ILiquidationStrategy.sol";
import { IPancakeSwapRouterV3 } from "./interfaces/IPancakeSwapRouterV3.sol";
import { IERC20 } from "./interfaces/IERC20.sol";
import { IPancakeV3Pool } from "./interfaces/IPancakeV3Pool.sol";
import { IUniSwapV3PathReader } from "../reader/interfaces/IUniSwapV3PathReader.sol";

contract PancakeswapV3TokenLiquidationStrategy is ILiquidationStrategy, Ownable {
using LibSafeToken for IERC20;
Expand All @@ -27,6 +28,7 @@ contract PancakeswapV3TokenLiquidationStrategy is ILiquidationStrategy, Ownable
error PancakeswapV3TokenLiquidationStrategy_NoLiquidity(address tokenA, address tokenB, uint24 fee);

IPancakeSwapRouterV3 internal immutable router;
IUniSwapV3PathReader internal immutable pathReader;

address internal constant PANCAKE_V3_POOL_DEPLOYER = 0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9;
bytes32 internal constant POOL_INIT_CODE_HASH = 0x6ce8eb472fa82df5469c6ab6d485f17c3ad13c8cd7af59b3d4a8026c5ce0f7e2;
Expand All @@ -49,8 +51,9 @@ contract PancakeswapV3TokenLiquidationStrategy is ILiquidationStrategy, Ownable
_;
}

constructor(address _router) {
constructor(address _router, address _pathReader) {
router = IPancakeSwapRouterV3(_router);
pathReader = IUniSwapV3PathReader(_pathReader);
}

/// @notice Execute liquidate from collatToken to repayToken
Expand All @@ -70,7 +73,7 @@ contract PancakeswapV3TokenLiquidationStrategy is ILiquidationStrategy, Ownable
revert PancakeswapV3TokenLiquidationStrategy_RepayTokenIsSameWithCollatToken();
}

bytes memory _path = paths[_collatToken][_repayToken];
bytes memory _path = pathReader.paths(_collatToken, _repayToken);
// Revert if no swapPath config for _collatToken and _repayToken pair
if (_path.length == 0) {
revert PancakeswapV3TokenLiquidationStrategy_PathConfigNotFound(_collatToken, _repayToken);
Expand All @@ -91,50 +94,6 @@ contract PancakeswapV3TokenLiquidationStrategy is ILiquidationStrategy, Ownable
router.exactInput(params);
}

/// @notice Set paths config to be used during swap step in executeLiquidation
/// @param _paths Array of parameters used to set path
function setPaths(bytes[] calldata _paths) external onlyOwner {
uint256 _len = _paths.length;
for (uint256 _i; _i < _len; ) {
bytes memory _path = _paths[_i];

while (true) {
bool hasMultiplePools = LibPath.hasMultiplePools(_path);

// extract the token from encoded hop
(address _token0, address _token1, uint24 _fee) = _path.decodeFirstPool();

// compute pool address from token0, token1 and fee
address _pool = _computeAddressV3(_token0, _token1, _fee);

// revert EVM error if pool is not existing (cannot call liquidity)
if (IPancakeV3Pool(_pool).liquidity() == 0) {
// revert no liquidity if there's no liquidity
revert PancakeswapV3TokenLiquidationStrategy_NoLiquidity(_token0, _token1, _fee);
}

// if true, go to the next hop
if (hasMultiplePools) {
_path = _path.skipToken();
} else {
// if it's last hop
// Get source token address from first hop
(address _source, , ) = _paths[_i].decodeFirstPool();
// Get destination token from last hop
(, address _destination, ) = _path.decodeFirstPool();
// Assign to global paths
paths[_source][_destination] = _paths[_i];
emit LogSetPath(_source, _destination, _paths[_i]);
break;
}
}

unchecked {
++_i;
}
}
}

/// @notice Set callers ok
/// @param _callers A list of caller addresses
/// @param _isOk An ok flag
Expand All @@ -161,26 +120,4 @@ contract PancakeswapV3TokenLiquidationStrategy is ILiquidationStrategy, Ownable
}
}
}

function _computeAddressV3(
address _tokenA,
address _tokenB,
uint24 _fee
) internal pure returns (address pool) {
if (_tokenA > _tokenB) (_tokenA, _tokenB) = (_tokenB, _tokenA);
pool = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
hex"ff",
PANCAKE_V3_POOL_DEPLOYER,
keccak256(abi.encode(_tokenA, _tokenB, _fee)),
POOL_INIT_CODE_HASH
)
)
)
)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/
pragma solidity 0.8.19;

library BytesLib {
library LibBytes {
function slice(
bytes memory _bytes,
uint256 _start,
Expand Down
87 changes: 87 additions & 0 deletions solidity/contracts/reader/PCSV3PathReader.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: BUSL
pragma solidity 0.8.19;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { LibPath } from "./libraries/LibPath.sol";
import { IPancakeV3Pool } from "../money-market/interfaces/IPancakeV3Pool.sol";
import { IUniSwapV3PathReader } from "./interfaces/IUniSwapV3PathReader.sol";

contract PCSV3PathReader is IUniSwapV3PathReader, Ownable {
using LibPath for bytes;

address internal constant PANCAKE_V3_POOL_DEPLOYER = 0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9;
bytes32 internal constant POOL_INIT_CODE_HASH = 0x6ce8eb472fa82df5469c6ab6d485f17c3ad13c8cd7af59b3d4a8026c5ce0f7e2;

// tokenIn => tokenOut => path
mapping(address => mapping(address => bytes)) public override paths;

// Errors
error PCSV3PathReader_NoLiquidity(address tokenA, address tokenB, uint24 fee);

// Events
event LogSetPath(address _token0, address _token1, bytes _path);

function setPaths(bytes[] calldata _paths) external onlyOwner {
uint256 _len = _paths.length;
for (uint256 _i; _i < _len; ) {
bytes memory _path = _paths[_i];

while (true) {
bool hasMultiplePools = LibPath.hasMultiplePools(_path);

// extract the token from first encoded hop
(address _token0, address _token1, uint24 _fee) = _path.decodeFirstPool();

// compute pool address from token0, token1 and fee
address _pool = _computeAddressV3(_token0, _token1, _fee);

// revert EVM error if pool is not existing (cannot call liquidity)
if (IPancakeV3Pool(_pool).liquidity() == 0) {
// revert no liquidity if there's no liquidity
revert PCSV3PathReader_NoLiquidity(_token0, _token1, _fee);
}

// if true, go to the next hop
if (hasMultiplePools) {
_path = _path.skipToken();
} else {
// if it's last hop
// Get source token address from first hop
(address _source, , ) = _paths[_i].decodeFirstPool();
// Get destination token from last hop
(, address _destination, ) = _path.decodeFirstPool();
// Assign to global paths
paths[_source][_destination] = _paths[_i];
emit LogSetPath(_source, _destination, _paths[_i]);
break;
}
}

unchecked {
++_i;
}
}
}

function _computeAddressV3(
address _tokenA,
address _tokenB,
uint24 _fee
) internal pure returns (address pool) {
if (_tokenA > _tokenB) (_tokenA, _tokenB) = (_tokenB, _tokenA);
pool = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
hex"ff",
PANCAKE_V3_POOL_DEPLOYER,
keccak256(abi.encode(_tokenA, _tokenB, _fee)),
POOL_INIT_CODE_HASH
)
)
)
)
);
}
}
8 changes: 8 additions & 0 deletions solidity/contracts/reader/interfaces/IUniSwapV3PathReader.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: BUSL
pragma solidity 0.8.19;

interface IUniSwapV3PathReader {
function setPaths(bytes[] calldata _paths) external;

function paths(address _source, address _destination) external returns (bytes memory);
}
Loading

0 comments on commit 94b1f6d

Please sign in to comment.