File 1 of 1: XenaAggregator.sol
pragma solidity 0.7.6;
pragma abicoder v2;
library TransferHelper {
function safeApprove(
address token,
address to,
uint256 value
) internal {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), "TransferHelper: APPROVE_FAILED");
}
function safeTransfer(
address token,
address to,
uint256 value
) internal {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), "TransferHelper: TRANSFER_FAILED");
}
function safeTransferFrom(
address token,
address from,
address to,
uint256 value
) internal {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), "TransferHelper: TRANSFER_FROM_FAILED");
}
function safeTransferETH(address to, uint256 value) internal {
(bool success, ) = to.call{value: value}(new bytes(0));
require(success, "TransferHelper: ETH_TRANSFER_FAILED");
}
}
interface IERC20 {
event Approval(address indexed owner, address indexed spender, uint256 value);
event Transfer(address indexed from, address indexed to, uint256 value);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint256);
function balanceOf(address owner) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transfer(address to, uint256 value) external returns (bool);
function transferFrom(
address from,
address to,
uint256 value
) external returns (bool);
}
interface IXenaAggregator {
event Exchange(address pair, uint256 amountOut, address output);
event Fee(address token, uint256 totalAmount, uint256 totalFee, address[] recipients, uint256[] amounts, uint256 timestamp);
function WETH() external view returns (address);
}
library SafeMath {
function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
require((z = x + y) >= x, "ds-math-add-overflow");
}
function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
require((z = x - y) <= x, "ds-math-sub-underflow");
}
function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow");
}
function div(uint256 a, uint256 b) internal pure returns (uint256 c) {
require(b > 0, "ds-math-division-by-zero");
c = a / b;
}
}
interface IWETH {
function deposit() external payable;
function transfer(address to, uint256 value) external returns (bool);
function withdraw(uint256) external;
function balanceOf(address account) external view returns (uint256);
}
interface IAggregationExecutor {
function callBytes(bytes calldata data, address srcSpender) external payable;
}
interface IERC20Permit {
function permit(
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
}
library RevertReasonParser {
function parse(bytes memory data, string memory prefix) internal pure returns (string memory) {
if (data.length >= 68 && data[0] == "\x08" && data[1] == "\xc3" && data[2] == "\x79" && data[3] == "\xa0") {
string memory reason;
assembly {
reason := add(data, 68)
}
require(data.length >= 68 + bytes(reason).length, "Invalid revert reason");
return string(abi.encodePacked(prefix, "Error(", reason, ")"));
}
else if (data.length == 36 && data[0] == "\x4e" && data[1] == "\x48" && data[2] == "\x7b" && data[3] == "\x71") {
uint256 code;
assembly {
code := mload(add(data, 36))
}
return string(abi.encodePacked(prefix, "Panic(", _toHex(code), ")"));
}
return string(abi.encodePacked(prefix, "Unknown(", _toHex(data), ")"));
}
function _toHex(uint256 value) private pure returns (string memory) {
return _toHex(abi.encodePacked(value));
}
function _toHex(bytes memory data) private pure returns (string memory) {
bytes16 alphabet = 0x30313233343536373839616263646566;
bytes memory str = new bytes(2 + data.length * 2);
str[0] = "0";
str[1] = "x";
for (uint256 i = 0; i < data.length; i++) {
str[2 * i + 2] = alphabet[uint8(data[i] >> 4)];
str[2 * i + 3] = alphabet[uint8(data[i] & 0x0f)];
}
return string(str);
}
}
contract Permitable {
event Error(string reason);
function _permit(
IERC20 token,
uint256 amount,
bytes calldata permit
) internal {
if (permit.length == 32 * 7) {
(bool success, bytes memory result) = address(token).call(abi.encodePacked(IERC20Permit.permit.selector, permit));
if (!success) {
string memory reason = RevertReasonParser.parse(result, "Permit call failed: ");
if (token.allowance(msg.sender, address(this)) < amount) {
revert(reason);
} else {
emit Error(reason);
}
}
}
}
}
abstract contract Context {
function _msgSender() internal view virtual returns (address payable) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes memory) {
this;
return msg.data;
}
}
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() internal {
address msgSender = _msgSender();
_owner = msgSender;
emit OwnershipTransferred(address(0), msgSender);
}
function owner() public view virtual returns (address) {
return _owner;
}
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
function renounceOwnership() public virtual onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
contract XenaAggregator is IXenaAggregator, Ownable, Permitable {
using SafeMath for uint256;
address public immutable override WETH;
address private constant ETH_ADDRESS = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
uint256 private constant _PARTIAL_FILL = 0x01;
uint256 private constant _REQUIRES_EXTRA_ETH = 0x02;
uint256 private constant _SHOULD_CLAIM = 0x04;
uint256 private constant _BURN_FROM_MSG_SENDER = 0x08;
uint256 private constant _BURN_FROM_TX_ORIGIN = 0x10;
uint256 private constant _FEE_ON_DST = 0x20;
struct SwapDescription {
IERC20 srcToken;
IERC20 dstToken;
address srcReceiver;
address dstReceiver;
uint256 amount;
uint256 minReturnAmount;
address[] feeReceivers;
uint256[] feeAmounts;
uint256 flags;
bytes permit;
}
event Swapped(address sender, IERC20 srcToken, IERC20 dstToken, address dstReceiver, uint256 spentAmount, uint256 returnAmount);
modifier ensure(uint256 deadline) {
require(deadline >= block.timestamp, "Router: EXPIRED");
_;
}
constructor(address _WETH) public {
WETH = _WETH;
}
receive() external payable {}
function swap(
IAggregationExecutor caller,
SwapDescription calldata desc,
bytes calldata data
) external payable returns (uint256 returnAmount) {
require(desc.minReturnAmount > 0, "Min return should not be 0");
require(data.length > 0, "data should be not zero");
uint256 amount = desc.amount;
IERC20 srcToken = desc.srcToken;
IERC20 dstToken = desc.dstToken;
if (desc.flags & _REQUIRES_EXTRA_ETH != 0) {
require(msg.value > (isETH(srcToken) ? amount : 0), "Invalid msg.value");
} else {
require(msg.value == (isETH(srcToken) ? amount : 0), "Invalid msg.value");
}
if (desc.flags & _SHOULD_CLAIM != 0) {
require(!isETH(srcToken), "Claim token is ETH");
_permit(srcToken, amount, desc.permit);
TransferHelper.safeTransferFrom(address(srcToken), msg.sender, desc.srcReceiver, amount);
}
address dstReceiver = (desc.dstReceiver == address(0)) ? msg.sender : desc.dstReceiver;
uint256 initialSrcBalance = (desc.flags & _PARTIAL_FILL != 0) ? getBalance(srcToken, msg.sender) : 0;
uint256 initialDstBalance = getBalance(dstToken, dstReceiver);
uint256 initialDstRouterBalance = getBalance(dstToken, address(this));
if (desc.flags & _FEE_ON_DST == 0) {
amount = _takeFee(desc.srcToken, desc.feeReceivers, desc.feeAmounts, isETH(desc.srcToken) ? msg.value : amount);
}
{
(bool success, bytes memory result) = address(caller).call{value: isETH(srcToken) ? amount : 0}(abi.encodeWithSelector(caller.callBytes.selector, data, msg.sender));
if (!success) {
revert(RevertReasonParser.parse(result, "callBytes failed: "));
}
}
if (desc.flags & _FEE_ON_DST != 0) {
returnAmount = getBalance(dstToken, address(this)).sub(initialDstRouterBalance);
returnAmount = _takeFee(desc.dstToken, desc.feeReceivers, desc.feeAmounts, returnAmount);
transferAll(address(desc.dstToken), dstReceiver, returnAmount);
}
uint256 spentAmount = desc.amount;
returnAmount = getBalance(dstToken, dstReceiver).sub(initialDstBalance);
if (desc.flags & _PARTIAL_FILL != 0) {
spentAmount = initialSrcBalance.add(desc.amount).sub(getBalance(srcToken, msg.sender));
require(returnAmount.mul(desc.amount) >= desc.minReturnAmount.mul(spentAmount), "Return amount is not enough");
} else {
require(returnAmount >= desc.minReturnAmount, "Return amount is not enough");
}
emit Swapped(msg.sender, srcToken, dstToken, dstReceiver, spentAmount, returnAmount);
emit Exchange(address(caller), returnAmount, isETH(dstToken) ? WETH : address(dstToken));
}
function _takeFee(
IERC20 token,
address[] memory recipients,
uint256[] memory amounts,
uint256 totalAmount
) internal returns (uint256 leftAmount) {
leftAmount = totalAmount;
uint256 recipientsLen = recipients.length;
if (recipientsLen > 0) {
uint256 balanceBefore = getBalance(token, address(this));
require(amounts.length == recipientsLen, "Invalid length");
for (uint256 i; i < recipientsLen; ++i) {
uint256 amount = (totalAmount * amounts[i]) / 10000;
transferAllERC20(address(token), recipients[i], amount);
}
uint256 totalFee = balanceBefore - getBalance(token, address(this));
leftAmount = totalAmount - totalFee;
emit Fee(address(token), totalAmount, totalFee, recipients, amounts, block.timestamp);
}
}
function getBalance(IERC20 token, address account) internal view returns (uint256) {
if (isETH(token)) {
return account.balance;
} else {
return token.balanceOf(account);
}
}
function transferETHTo(uint256 amount, address to) internal {
IWETH(WETH).deposit{value: amount}();
assert(IWETH(WETH).transfer(to, amount));
}
function transferAll(
address token,
address to,
uint256 amount
) internal returns (bool) {
if (amount == 0) {
return true;
}
if (isETH(IERC20(token))) {
TransferHelper.safeTransferETH(to, amount);
} else {
TransferHelper.safeTransfer(token, to, amount);
}
return true;
}
function transferAllERC20(
address token,
address to,
uint256 amount
) internal returns (bool) {
if (amount == 0) {
return true;
}
if (isETH(IERC20(token))) {
IWETH(WETH).deposit{value: amount}();
TransferHelper.safeTransfer(WETH, to, amount);
} else {
TransferHelper.safeTransfer(token, to, amount);
}
return true;
}
function isETH(IERC20 token) internal pure returns (bool) {
return (address(token) == ETH_ADDRESS);
}
function rescueFunds(address token, uint256 amount) external onlyOwner {
if (isETH(IERC20(token))) {
TransferHelper.safeTransferETH(msg.sender, amount);
} else {
TransferHelper.safeTransfer(token, msg.sender, amount);
}
}
}