// SPDX-License-Identifier: AGPL-3.0-onlypragmasolidity >=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.librarySafeTransferLib{
/*///////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/functionsafeTransferETH(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
//////////////////////////////////////////////////////////////*/functionsafeTransferFrom(address token,
addressfrom,
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");
}
functionsafeTransfer(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");
}
functionsafeApprove(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
//////////////////////////////////////////////////////////////*/functiondidLastOptionalReturnCallSucceed(bool callStatus) privatepurereturns (bool success) {
assembly {
// Get how many bytes the call returned.let returnDataSize :=returndatasize()
// If the call reverted:ifiszero(callStatus) {
// Copy the revert message into memory.returndatacopy(0, 0, returnDataSize)
// Revert with the same message.revert(0, returnDataSize)
}
switch returnDataSize
case32 {
// Copy the return data into memory.returndatacopy(0, 0, returnDataSize)
// Set success to whether it returned true.
success :=iszero(iszero(mload(0)))
}
case0 {
// There was no return data.
success :=1
}
default {
// It returned some malformed input.
success :=0
}
}
}
}
Contract Source Code
File 6 of 8: TSAggregator.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.10;import { SafeTransferLib } from"../lib/SafeTransferLib.sol";
import { ReentrancyGuard } from"../lib/ReentrancyGuard.sol";
import { Owners } from"./Owners.sol";
import { TSAggregatorTokenTransferProxy } from'./TSAggregatorTokenTransferProxy.sol';
abstractcontractTSAggregatorisOwners, ReentrancyGuard{
usingSafeTransferLibforaddress;
eventFeeSet(uint256 fee, address feeRecipient);
uint256public fee;
addresspublic 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 ETHreceive() externalpayable{}
functionsetFee(uint256 _fee, address _feeRecipient) publicisOwner{
require(_fee <=1000, "fee can not be more than 10%");
fee = _fee;
feeRecipient = _feeRecipient;
emit FeeSet(_fee, _feeRecipient);
}
functionskimFee(uint256 amount) internalreturns (uint256) {
if (fee !=0&& feeRecipient !=address(0)) {
uint256 feeAmount = (amount * fee) /10000;
feeRecipient.safeTransferETH(feeAmount);
amount -= feeAmount;
}
return amount;
}
}
Contract Source Code
File 7 of 8: TSAggregatorGeneric.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.10;import { SafeTransferLib } from"../lib/SafeTransferLib.sol";
import { TSAggregator } from"./TSAggregator.sol";
import { IERC20 } from"./interfaces/IERC20.sol";
import { IThorchainRouter } from"./interfaces/IThorchainRouter.sol";
import { TSAggregatorTokenTransferProxy } from'./TSAggregatorTokenTransferProxy.sol';
contractTSAggregatorGenericisTSAggregator{
usingSafeTransferLibforaddress;
constructor(address _ttp) TSAggregator(_ttp) {
}
// Use 1inch's swap API endpoint to get data to send// e.g. https://api.1inch.io/v4.0/1/swap?toTokenAddress=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE&fromTokenAddress=0x111111111117dc0aa78b770fa6a738034120c302&amount=10000000000000000&fromAddress=0x2f8aedd149afbdb5206ecaf8b1a3abb9186c8053&slippage=1&disableEstimate=true// toTokenAddress needs to be 0xeeee so ETH is sent back to swapIn// fromAddress needs to be the address of this contract// disableEstimate makes the API return a result even if there's no token balance in the contractfunctionswapIn(address tcRouter,
address tcVault,
stringcalldata tcMemo,
address token,
uint amount,
address router,
bytescalldata data,
uint deadline
) publicnonReentrant{
require(router !=address(tokenTransferProxy), "no calling ttp");
tokenTransferProxy.transferTokens(token, msg.sender, address(this), amount);
token.safeApprove(address(router), 0); // USDT quirk
token.safeApprove(address(router), amount);
(bool success,) = router.call(data);
require(success, "failed to swap");
uint256 amountOut =address(this).balance;
amountOut = skimFee(amountOut);
IThorchainRouter(tcRouter).depositWithExpiry{value: amountOut}(
payable(tcVault),
address(0), // ETH
amountOut,
tcMemo,
deadline
);
}
}
Contract Source Code
File 8 of 8: TSAggregatorTokenTransferProxy.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.10;import { SafeTransferLib } from"../lib/SafeTransferLib.sol";
import { Owners } from"./Owners.sol";
contractTSAggregatorTokenTransferProxyisOwners{
usingSafeTransferLibforaddress;
constructor() {
_setOwner(msg.sender, true);
}
functiontransferTokens(address token, addressfrom, address to, uint256 amount) externalisOwner{
require(from==tx.origin|| _isContract(from), "Invalid from address");
token.safeTransferFrom(from, to, amount);
}
function_isContract(address account) internalviewreturns (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-assemblyassembly { size :=extcodesize(account) }
return size >0;
}
}