Contract Name:
TSAggregatorUniswapV2
Contract Source Code:
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import { SafeTransferLib } from "../lib/SafeTransferLib.sol";
import { TSAggregator } from "./TSAggregator.sol";
import { IThorchainRouter } from "./interfaces/IThorchainRouter.sol";
import { IUniswapRouterV2 } from "./interfaces/IUniswapRouterV2extended.sol";
import { Owners } from "./Owners.sol";
contract TSAggregatorUniswapV2 is Owners, TSAggregator {
using SafeTransferLib for address;
address public weth;
IUniswapRouterV2 public swapRouter;
mapping(address => bool) public tokensWithTransferFee;
event SwapIn(address from, address token, uint256 amount, uint256 out, uint256 fee, address vault, string memo);
event SwapOut(address to, address token, uint256 amount, uint256 fee);
constructor(
address _ttp, address _weth, address _swapRouter
) TSAggregator(_ttp) {
weth = _weth;
swapRouter = IUniswapRouterV2(_swapRouter);
_setOwner(msg.sender, true);
}
function addTokenWithTransferFee(address token) external isOwner {
tokensWithTransferFee[token] = true;
}
function swapIn(
address router,
address vault,
string calldata memo,
address token,
uint amount,
uint amountOutMin,
uint deadline
) public nonReentrant {
tokenTransferProxy.transferTokens(token, msg.sender, address(this), amount);
token.safeApprove(address(swapRouter), 0); // USDT quirk
token.safeApprove(address(swapRouter), amount);
address[] memory path = new address[](2);
path[0] = token;
path[1] = weth;
if(tokensWithTransferFee[token]) {
swapRouter.swapExactTokensForETHSupportingFeeOnTransferTokens(
amount,
amountOutMin,
path,
address(this),
deadline
);
} else {
swapRouter.swapExactTokensForETH(
amount,
amountOutMin,
path,
address(this),
deadline
);
}
uint256 out = address(this).balance;
{
uint256 outMinusFee = skimFee(out);
IThorchainRouter(router).depositWithExpiry{value: outMinusFee}(
payable(vault),
address(0),
outMinusFee,
memo,
deadline
);
}
emit SwapIn(msg.sender, token, amount, out+getFee(out), getFee(out), vault, memo);
}
function swapOut(address token, address to, uint256 amountOutMin) public payable nonReentrant {
uint256 amount = skimFee(msg.value);
address[] memory path = new address[](2);
path[0] = weth;
path[1] = token;
if(tokensWithTransferFee[token]) {
swapRouter.swapExactETHForTokensSupportingFeeOnTransferTokens{value: amount}(
_parseAmountOutMin(amountOutMin),
path,
to,
type(uint).max // deadline
);
} else {
swapRouter.swapExactETHForTokens{value: amount}(
_parseAmountOutMin(amountOutMin),
path,
to,
type(uint).max // deadline
);
}
emit SwapOut(to, token, msg.value, msg.value-amount);
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Gnosis (https://github.com/gnosis/gp-v2-contracts/blob/main/src/contracts/libraries/GPv2SafeERC20.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
library SafeTransferLib {
/*///////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool callStatus;
assembly {
// Transfer the ETH and store if it succeeded or not.
callStatus := call(gas(), to, amount, 0, 0, 0, 0)
}
require(callStatus, "ETH_TRANSFER_FAILED");
}
/*///////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
address token,
address from,
address to,
uint256 amount
) internal {
bool callStatus;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata to memory piece by piece:
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) // Begin with the function selector.
mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "from" argument.
mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Finally append the "amount" argument. No mask as it's a full 32 byte value.
// Call the token and store if it succeeded or not.
// We use 100 because the calldata length is 4 + 32 * 3.
callStatus := call(gas(), token, 0, freeMemoryPointer, 100, 0, 0)
}
require(didLastOptionalReturnCallSucceed(callStatus), "TRANSFER_FROM_FAILED");
}
function safeTransfer(
address token,
address to,
uint256 amount
) internal {
bool callStatus;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata to memory piece by piece:
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) // Begin with the function selector.
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Finally append the "amount" argument. No mask as it's a full 32 byte value.
// Call the token and store if it succeeded or not.
// We use 68 because the calldata length is 4 + 32 * 2.
callStatus := call(gas(), token, 0, freeMemoryPointer, 68, 0, 0)
}
require(didLastOptionalReturnCallSucceed(callStatus), "TRANSFER_FAILED");
}
function safeApprove(
address token,
address to,
uint256 amount
) internal {
bool callStatus;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata to memory piece by piece:
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) // Begin with the function selector.
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Finally append the "amount" argument. No mask as it's a full 32 byte value.
// Call the token and store if it succeeded or not.
// We use 68 because the calldata length is 4 + 32 * 2.
callStatus := call(gas(), token, 0, freeMemoryPointer, 68, 0, 0)
}
require(didLastOptionalReturnCallSucceed(callStatus), "APPROVE_FAILED");
}
/*///////////////////////////////////////////////////////////////
INTERNAL HELPER LOGIC
//////////////////////////////////////////////////////////////*/
function didLastOptionalReturnCallSucceed(bool callStatus) private pure returns (bool success) {
assembly {
// Get how many bytes the call returned.
let returnDataSize := returndatasize()
// If the call reverted:
if iszero(callStatus) {
// Copy the revert message into memory.
returndatacopy(0, 0, returnDataSize)
// Revert with the same message.
revert(0, returnDataSize)
}
switch returnDataSize
case 32 {
// Copy the return data into memory.
returndatacopy(0, 0, returnDataSize)
// Set success to whether it returned true.
success := iszero(iszero(mload(0)))
}
case 0 {
// There was no return data.
success := 1
}
default {
// It returned some malformed input.
success := 0
}
}
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import { SafeTransferLib } from "../lib/SafeTransferLib.sol";
import { ReentrancyGuard } from "../lib/ReentrancyGuard.sol";
import { Owners } from "./Owners.sol";
import { TSAggregatorTokenTransferProxy } from './TSAggregatorTokenTransferProxy.sol';
abstract contract TSAggregator is Owners, ReentrancyGuard {
using SafeTransferLib for address;
event FeeSet(uint256 fee, address feeRecipient);
uint256 public fee;
address public feeRecipient;
TSAggregatorTokenTransferProxy public tokenTransferProxy;
constructor(address _tokenTransferProxy) {
_setOwner(msg.sender, true);
tokenTransferProxy = TSAggregatorTokenTransferProxy(_tokenTransferProxy);
}
// Needed for the swap router to be able to send back ETH
receive() external payable {}
function setFee(uint256 _fee, address _feeRecipient) external isOwner {
require(_fee <= 1000, "fee can not be more than 10%");
fee = _fee;
feeRecipient = _feeRecipient;
emit FeeSet(_fee, _feeRecipient);
}
function skimFee(uint256 amount) internal returns (uint256) {
uint256 amountFee = getFee(amount);
if (amountFee > 0) {
feeRecipient.safeTransferETH(amountFee);
amount -= amountFee;
}
return amount;
}
function getFee(uint256 amount) internal view returns (uint256) {
if (fee != 0 && feeRecipient != address(0)) {
return (amount * fee) / 10000;
}
return 0;
}
// Parse amountOutMin treating the last 2 digits as an exponent
// So 1504 = 150000. This allows for compressed memos on chains
// with limited space like Bitcoin
function _parseAmountOutMin(uint256 amount) internal pure returns (uint256) {
return amount / 100 * (10 ** (amount % 100));
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
interface IThorchainRouter {
function depositWithExpiry(
address payable vault,
address asset,
uint amount,
string memory memo,
uint expiration
) external payable;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
interface IUniswapRouterV2 {
function WETH() external view returns (address);
function swapExactTokensForETH(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external;
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external;
function swapExactETHForTokens(
uint amountOutMin,
address[] calldata path,
address to, uint deadline
) external payable;
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external payable;
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
abstract contract Owners {
event OwnerSet(address indexed owner, bool active);
mapping(address => bool) public owners;
modifier isOwner() {
require(owners[msg.sender], "Unauthorized");
_;
}
function _setOwner(address owner, bool active) internal virtual {
owners[owner] = active;
emit OwnerSet(owner, active);
}
function setOwner(address owner, bool active) external virtual isOwner {
_setOwner(owner, active);
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
uint256 private locked = 1;
modifier nonReentrant() {
require(locked == 1, "REENTRANCY");
locked = 2;
_;
locked = 1;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import { SafeTransferLib } from "../lib/SafeTransferLib.sol";
import { Owners } from "./Owners.sol";
contract TSAggregatorTokenTransferProxy is Owners {
using SafeTransferLib for address;
constructor() {
_setOwner(msg.sender, true);
}
function transferTokens(address token, address from, address to, uint256 amount) external isOwner {
require(from == tx.origin || _isContract(from), "Invalid from address");
token.safeTransferFrom(from, to, amount);
}
function _isContract(address account) internal view returns (bool) {
// This method relies on extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly { size := extcodesize(account) }
return size > 0;
}
}