// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
}
library TransferHelper {
function safeTransfer(address token, address to, uint256 value) internal {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20(token).transfer.selector, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FAILED');
}
function safeTransferETH(address to, uint256 value) internal {
(bool success, ) = to.call{value: value}(new bytes(0));
require(success, 'TransferHelper: ETH_TRANSFER_FAILED');
}
}
contract KaskadeRouterV1 {
address private _owner;
bool private _disabled;
address public syncSwapContract; // Address of the SyncSwap contract
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
event SwapSuccessful(address indexed user, uint amountOut, uint timestamp);
event Disabled();
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor(address _syncSwapContract) {
_transferOwnership(msg.sender);
syncSwapContract = _syncSwapContract;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == msg.sender, "Ownable: caller is not the owner");
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
modifier ensure(uint deadline) {
if (block.timestamp > deadline) {
revert("Expired");
}
_;
}
modifier whenNotDisabled() {
require(!_disabled, "Contract is disabled");
_;
}
struct SwapPath {
address tokenIn;
uint amountIn;
SwapStep[] steps;
}
struct SwapStep {
address pool;
bytes data;
bool useVault;
address callback;
bytes callbackData;
}
struct TokenAmount {
address token;
uint amount;
}
function swapSyncSwap(
SwapPath[] memory paths,
uint amountOutMin,
uint deadline
) external payable ensure(deadline) whenNotDisabled returns (TokenAmount memory amountOut) {
// Delegate the call to the SyncSwap contract
(bool success, bytes memory result) = syncSwapContract.delegatecall(
abi.encodeWithSignature("swap((address,uint256,(address,bytes,bool,address,bytes)[])[],uint256,uint256)", paths, amountOutMin, deadline)
);
require(success, "Delegatecall to swap failed");
amountOut = abi.decode(result, (TokenAmount));
// Emit event for successful swap
emit SwapSuccessful(msg.sender, amountOut.amount, block.timestamp);
}
function swap(
bytes calldata callData
) external payable whenNotDisabled returns (TokenAmount memory amountOut) {
// Delegate the call to the SyncSwap contract
(bool success, bytes memory result) = syncSwapContract.delegatecall(
callData
);
require(success, "Delegatecall to swap failed");
amountOut = abi.decode(result, (TokenAmount));
// Emit event for successful swap
emit SwapSuccessful(msg.sender, amountOut.amount, block.timestamp);
}
function disable() external onlyOwner {
_disabled = true;
emit Disabled();
}
function isDisabled() external view returns (bool) {
return _disabled;
}
function rescueERC20(address token, address to, uint256 amount) external onlyOwner {
require(to != address(0) && to != token, "Invalid to");
uint balance = IERC20(token).balanceOf(address(this));
if (amount == 0) {
TransferHelper.safeTransfer(token, to, balance);
} else {
require(amount <= balance, "Exceeds balance");
TransferHelper.safeTransfer(token, to, amount);
}
}
function rescueETH(address payable to, uint256 amount) external onlyOwner {
if (amount == 0) {
amount = address(this).balance;
}
TransferHelper.safeTransferETH(to, amount);
}
receive() external payable {}
}
{
"compilationTarget": {
"contracts/KaskadeRouterV1.sol": "KaskadeRouterV1"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_syncSwapContract","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[],"name":"Disabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOut","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"SwapSuccessful","type":"event"},{"inputs":[],"name":"disable","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isDisabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"rescueERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"rescueETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"callData","type":"bytes"}],"name":"swap","outputs":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct KaskadeRouterV1.TokenAmount","name":"amountOut","type":"tuple"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"bool","name":"useVault","type":"bool"},{"internalType":"address","name":"callback","type":"address"},{"internalType":"bytes","name":"callbackData","type":"bytes"}],"internalType":"struct KaskadeRouterV1.SwapStep[]","name":"steps","type":"tuple[]"}],"internalType":"struct KaskadeRouterV1.SwapPath[]","name":"paths","type":"tuple[]"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapSyncSwap","outputs":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct KaskadeRouterV1.TokenAmount","name":"amountOut","type":"tuple"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"syncSwapContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]