// SPDX-License-Identifier: MIT pragma solidity >=0.8.9; import "rubic-bridge-base/contracts/architecture/WithDestinationFunctionality.sol"; import "rubic-bridge-base/contracts/libraries/SmartApprove.sol"; import "../framework/MessageSenderApp.sol"; import "../../interfaces/IWETH.sol"; contract SwapBase is MessageSenderApp, WithDestinationFunctionality { using SafeERC20Upgradeable for IERC20Upgradeable; using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet; address public nativeWrap; uint64 public nonce; mapping(bytes32 => RefundData) public refundDetails; modifier isTransit(address _transitToken, address _tokenInPath) { checkIsTransit(_transitToken, _tokenInPath); _; } // ============== struct for refunds ============== struct RefundData { address integrator; // integrator address in order to take commission address token; // transit token uint256 amount; // amount of transit token address to; // recipient bool nativeOut; // receive wrapped/native } // ============== struct for V2 like dexes ============== struct SwapInfoV2 { address dex; // the DEX to use for the swap // if this array has only one element, it means no need to swap address[] path; // the following fields are only needed if path.length > 1 uint256 deadline; // deadline for the swap uint256 amountOutMinimum; // minimum receive amount for the swap } // ============== struct for V3 like dexes ============== struct SwapInfoV3 { address dex; // the DEX to use for the swap bytes path; uint256 deadline; uint256 amountOutMinimum; } // ============== struct for inch swap ============== struct SwapInfoInch { address dex; // path is tokenIn, tokenOut address[] path; bytes data; uint256 amountOutMinimum; } // ============== struct dstSwap ============== // This is needed to make v2 -> SGN -> v3 swaps and etc. struct SwapInfoDest { address dex; // dex address bool nativeOut; address receiverEOA; // EOA recipient in dst chain address integrator; SwapVersion version; // identifies swap type address[] path; // path address for v2 and inch bytes pathV3; // path address for v3 uint256 deadline; // for v2 and v3 uint256 amountOutMinimum; } struct SwapRequestDest { SwapInfoDest swap; uint64 nonce; uint64 dstChainId; } enum SwapVersion { v2, v3, bridge } // ============== common checks for src swaps ============== function _deriveFeeAndPerformChecksNative( uint256 _amountIn, uint64 _dstChainId, address _integrator, address srcInputToken ) internal onlyEOA whenNotPaused returns (uint256 _fee) { require(srcInputToken == nativeWrap, "token mismatch"); require(msg.value >= _amountIn, "amount insufficient"); IWETH(nativeWrap).deposit{value: _amountIn}(); _fee = accrueFixedAndGasFees( _integrator, integratorToFeeInfo[_integrator], _dstChainId ) - _amountIn; } function _deriveFeeAndPerformChecks( uint256 _amountIn, uint64 _dstChainId, address _integrator, address srcInputToken ) internal onlyEOA whenNotPaused returns (uint256 _fee) { IERC20Upgradeable(srcInputToken).safeTransferFrom( msg.sender, address(this), _amountIn ); _fee = accrueFixedAndGasFees( _integrator, integratorToFeeInfo[_integrator], _dstChainId ); } // ============== Celer call ============== function _sendMessage( address _receiver, uint64 _dstChainId, SwapInfoDest calldata _dstSwap, uint32 _maxBridgeSlippage, uint64 _nonce, uint256 _fee, address _srcOutputToken, uint256 _srcAmtOut, bool _success ) internal returns (bytes32 id) { if (!_success) revert("src swap failed"); require(_srcAmtOut >= minTokenAmount[_srcOutputToken], "less than min"); if (maxTokenAmount[_srcOutputToken] > 0) { require( _srcAmtOut <= maxTokenAmount[_srcOutputToken], "greater than max" ); } id = _crossChainTransferWithSwap( _receiver, _dstChainId, _dstSwap, _maxBridgeSlippage, _nonce, _fee, _srcOutputToken, _srcAmtOut ); } function _crossChainTransferWithSwap( address _receiver, uint64 _dstChainId, SwapInfoDest calldata _dstSwap, uint32 _maxBridgeSlippage, uint64 _nonce, uint256 _fee, address srcOutputToken, uint256 srcAmtOut ) private returns (bytes32 id) { // todo increment nonce in compute ... require(_dstSwap.path.length > 0, "empty dst swap path"); bytes memory message = abi.encode( SwapRequestDest({ swap: _dstSwap, nonce: nonce, dstChainId: _dstChainId }) ); id = _computeSwapRequestId( _dstSwap.receiverEOA, uint64(block.chainid), _dstChainId, message ); sendMessageWithTransfer( _receiver, srcOutputToken, srcAmtOut, _dstChainId, _nonce, _maxBridgeSlippage, message, _fee ); } // ============== Utilities ============== function _beforeSwapAndSendMessage() internal returns (uint64) { return ++nonce; } function _retrieveDstTokenAddress(SwapInfoDest memory _swapInfo) internal pure returns (address) { if (_swapInfo.version == SwapVersion.v3) { require(_swapInfo.pathV3.length > 20, "dst swap expected"); return address(_getLastBytes20(_swapInfo.pathV3)); } else if (_swapInfo.version == SwapVersion.v2) { require(_swapInfo.path.length > 1, "dst swap expected"); return _swapInfo.path[_swapInfo.path.length - 1]; } else { require(_swapInfo.path.length == 1, "dst bridge expected"); return _swapInfo.path[0]; } } // returns address of first token for V3 function _getFirstBytes20(bytes memory input) internal pure returns (bytes20 result) { assembly { result := mload(add(input, 32)) } } // returns address of tokenOut for V3 function _getLastBytes20(bytes memory input) internal pure returns (bytes20 result) { uint256 offset = input.length + 12; assembly { result := mload(add(input, offset)) } } function _computeSwapRequestId( address _receiverEOA, uint64 _srcChainId, uint64 _dstChainId, bytes memory _message ) internal pure returns (bytes32) { return keccak256( abi.encodePacked( _receiverEOA, _srcChainId, _dstChainId, _message ) ); } /** * @dev Function to check if the address in path is transit token received from Celer */ function checkIsTransit(address _transitToken, address _tokenInPath) internal pure { require(_transitToken == _tokenInPath, "first token must be transit"); } }