//SPDX-License-Identifier: GPL-3.0
pragma solidity =0.8.11;
import "@rari-capital/solmate/src/utils/SafeTransferLib.sol";
import "@rari-capital/solmate/src/tokens/ERC20.sol";
import "../libraries/PermitHelper.sol";
/// @title Rainbow base aggregator contract
contract BaseAggregator {
/// @dev Used to prevent re-entrancy
uint256 internal status;
/// @dev Set of allowed swapTargets.
mapping(address => bool) public swapTargets;
/// @dev modifier that prevents reentrancy attacks on specific methods
modifier nonReentrant() {
// On the first call to nonReentrant, status will be 1
require(status != 2, "NON_REENTRANT");
// Any calls to nonReentrant after this point will fail
status = 2;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
status = 1;
}
/// @dev modifier that ensures only approved targets can be called
modifier onlyApprovedTarget(address target) {
require(swapTargets[target], "TARGET_NOT_AUTH");
_;
}
/** EXTERNAL **/
/// @param buyTokenAddress the address of token that the user should receive
/// @param target the address of the aggregator contract that will exec the swap
/// @param swapCallData the calldata that will be passed to the aggregator contract
/// @param feeAmount the amount of ETH that we will take as a fee
function fillQuoteEthToToken(
address buyTokenAddress,
address payable target,
bytes calldata swapCallData,
uint256 feeAmount
) external payable nonReentrant onlyApprovedTarget(target) {
// 1 - Get the initial balances
uint256 initialTokenBalance = ERC20(buyTokenAddress).balanceOf(
address(this)
);
uint256 initialEthAmount = address(this).balance - msg.value;
uint256 sellAmount = msg.value - feeAmount;
// 2 - Call the encoded swap function call on the contract at `target`,
// passing along any ETH attached to this function call to cover protocol fees
// minus our fees, which are kept in this contract
(bool success, bytes memory res) = target.call{value: sellAmount}(
swapCallData
);
// Get the revert message of the call and revert with it if the call failed
if (!success) {
assembly {
let returndata_size := mload(res)
revert(add(32, res), returndata_size)
}
}
// 3 - Make sure we received the tokens
{
uint256 finalTokenBalance = ERC20(buyTokenAddress).balanceOf(
address(this)
);
require(initialTokenBalance < finalTokenBalance, "NO_TOKENS");
}
// 4 - Send the received tokens back to the user
SafeTransferLib.safeTransfer(
ERC20(buyTokenAddress),
msg.sender,
ERC20(buyTokenAddress).balanceOf(address(this)) -
initialTokenBalance
);
// 5 - Return the remaining ETH to the user (if any)
{
uint256 finalEthAmount = address(this).balance - feeAmount;
if (finalEthAmount > initialEthAmount) {
SafeTransferLib.safeTransferETH(
msg.sender,
finalEthAmount - initialEthAmount
);
}
}
}
/// @param sellTokenAddress the address of token that the user is selling
/// @param buyTokenAddress the address of token that the user should receive
/// @param target the address of the aggregator contract that will exec the swap
/// @param swapCallData the calldata that will be passed to the aggregator contract
/// @param sellAmount the amount of tokens that the user is selling
/// @param feeAmount the amount of the tokens to sell that we will take as a fee
function fillQuoteTokenToToken(
address sellTokenAddress,
address buyTokenAddress,
address payable target,
bytes calldata swapCallData,
uint256 sellAmount,
uint256 feeAmount
) external payable nonReentrant onlyApprovedTarget(target) {
_fillQuoteTokenToToken(
sellTokenAddress,
buyTokenAddress,
target,
swapCallData,
sellAmount,
feeAmount
);
}
/// @dev method that executes ERC20 to ERC20 token swaps with the ability to take a fee from the input
// and accepts a signature to use permit, so the user doesn't have to make an previous approval transaction
/// @param sellTokenAddress the address of token that the user is selling
/// @param buyTokenAddress the address of token that the user should receive
/// @param target the address of the aggregator contract that will exec the swap
/// @param swapCallData the calldata that will be passed to the aggregator contract
/// @param sellAmount the amount of tokens that the user is selling
/// @param feeAmount the amount of the tokens to sell that we will take as a fee
/// @param permitData struct containing the value, nonce, deadline, v, r and s values of the permit data
function fillQuoteTokenToTokenWithPermit(
address sellTokenAddress,
address buyTokenAddress,
address payable target,
bytes calldata swapCallData,
uint256 sellAmount,
uint256 feeAmount,
PermitHelper.Permit calldata permitData
) external payable nonReentrant onlyApprovedTarget(target) {
// 1 - Apply permit
PermitHelper.permit(
permitData,
sellTokenAddress,
msg.sender,
address(this)
);
//2 - Call fillQuoteTokenToToken
_fillQuoteTokenToToken(
sellTokenAddress,
buyTokenAddress,
target,
swapCallData,
sellAmount,
feeAmount
);
}
/// @dev method that executes ERC20 to ETH token swaps with the ability to take a fee from the output
/// @param sellTokenAddress the address of token that the user is selling
/// @param target the address of the aggregator contract that will exec the swap
/// @param swapCallData the calldata that will be passed to the aggregator contract
/// @param sellAmount the amount of tokens that the user is selling
/// @param feePercentageBasisPoints the amount of ETH that we will take as a fee in 1e18 basis points (basis points with 4 decimals plus 14 extra decimals of precision)
function fillQuoteTokenToEth(
address sellTokenAddress,
address payable target,
bytes calldata swapCallData,
uint256 sellAmount,
uint256 feePercentageBasisPoints
) external payable nonReentrant onlyApprovedTarget(target) {
_fillQuoteTokenToEth(
sellTokenAddress,
target,
swapCallData,
sellAmount,
feePercentageBasisPoints
);
}
/// @dev method that executes ERC20 to ETH token swaps with the ability to take a fee from the output
// and accepts a signature to use permit, so the user doesn't have to make an previous approval transaction
/// @param sellTokenAddress the address of token that the user is selling
/// @param target the address of the aggregator contract that will exec the swap
/// @param swapCallData the calldata that will be passed to the aggregator contract
/// @param sellAmount the amount of tokens that the user is selling
/// @param feePercentageBasisPoints the amount of ETH that we will take as a fee in 1e18 basis points (basis points with 4 decimals plus 14 extra decimals of precision)
/// @param permitData struct containing the amount, nonce, deadline, v, r and s values of the permit data
function fillQuoteTokenToEthWithPermit(
address sellTokenAddress,
address payable target,
bytes calldata swapCallData,
uint256 sellAmount,
uint256 feePercentageBasisPoints,
PermitHelper.Permit calldata permitData
) external payable nonReentrant onlyApprovedTarget(target) {
// 1 - Apply permit
PermitHelper.permit(
permitData,
sellTokenAddress,
msg.sender,
address(this)
);
// 2 - call fillQuoteTokenToEth
_fillQuoteTokenToEth(
sellTokenAddress,
target,
swapCallData,
sellAmount,
feePercentageBasisPoints
);
}
/** INTERNAL **/
/// @dev internal method that executes ERC20 to ETH token swaps with the ability to take a fee from the output
function _fillQuoteTokenToEth(
address sellTokenAddress,
address payable target,
bytes calldata swapCallData,
uint256 sellAmount,
uint256 feePercentageBasisPoints
) internal {
// 1 - Get the initial ETH amount
uint256 initialEthAmount = address(this).balance - msg.value;
// 2 - Move the tokens to this contract
// NOTE: This implicitly assumes that the the necessary approvals have been granted
// from msg.sender to the BaseAggregator
SafeTransferLib.safeTransferFrom(
ERC20(sellTokenAddress),
msg.sender,
address(this),
sellAmount
);
// 3 - Approve the aggregator's contract to swap the tokens
SafeTransferLib.safeApprove(
ERC20(sellTokenAddress),
target,
sellAmount
);
// 4 - Call the encoded swap function call on the contract at `target`,
// passing along any ETH attached to this function call to cover protocol fees.
(bool success, bytes memory res) = target.call{value: msg.value}(
swapCallData
);
// Get the revert message of the call and revert with it if the call failed
if (!success) {
assembly {
let returndata_size := mload(res)
revert(add(32, res), returndata_size)
}
}
// 5 - Check that the tokens were fully spent during the swap
uint256 allowance = ERC20(sellTokenAddress).allowance(
address(this),
target
);
require(allowance == 0, "ALLOWANCE_NOT_ZERO");
// 6 - Subtract the fees and send the rest to the user
// Fees will be held in this contract
uint256 finalEthAmount = address(this).balance;
uint256 ethDiff = finalEthAmount - initialEthAmount;
require(ethDiff > 0, "NO_ETH_BACK");
if (feePercentageBasisPoints > 0) {
uint256 fees = (ethDiff * feePercentageBasisPoints) / 1e18;
uint256 amountMinusFees = ethDiff - fees;
SafeTransferLib.safeTransferETH(msg.sender, amountMinusFees);
// when there's no fee, 1inch sends the funds directly to the user
// we check to prevent sending 0 ETH in that case
} else if (ethDiff > 0) {
SafeTransferLib.safeTransferETH(msg.sender, ethDiff);
}
}
/// @dev internal method that executes ERC20 to ERC20 token swaps with the ability to take a fee from the input
function _fillQuoteTokenToToken(
address sellTokenAddress,
address buyTokenAddress,
address payable target,
bytes calldata swapCallData,
uint256 sellAmount,
uint256 feeAmount
) internal {
// 1 - Get the initial output token balance
uint256 initialOutputTokenAmount = ERC20(buyTokenAddress).balanceOf(
address(this)
);
// 2 - Move the tokens to this contract (which includes our fees)
// NOTE: This implicitly assumes that the the necessary approvals have been granted
// from msg.sender to the BaseAggregator
SafeTransferLib.safeTransferFrom(
ERC20(sellTokenAddress),
msg.sender,
address(this),
sellAmount
);
// 3 - Approve the aggregator's contract to swap the tokens if needed
SafeTransferLib.safeApprove(
ERC20(sellTokenAddress),
target,
sellAmount - feeAmount
);
// 4 - Call the encoded swap function call on the contract at `target`,
// passing along any ETH attached to this function call to cover protocol fees.
(bool success, bytes memory res) = target.call{value: msg.value}(
swapCallData
);
// Get the revert message of the call and revert with it if the call failed
if (!success) {
assembly {
let returndata_size := mload(res)
revert(add(32, res), returndata_size)
}
}
// 5 - Check that the tokens were fully spent during the swap
uint256 allowance = ERC20(sellTokenAddress).allowance(
address(this),
target
);
require(allowance == 0, "ALLOWANCE_NOT_ZERO");
// 6 - Make sure we received the tokens
uint256 finalOutputTokenAmount = ERC20(buyTokenAddress).balanceOf(
address(this)
);
require(initialOutputTokenAmount < finalOutputTokenAmount, "NO_TOKENS");
// 7 - Send tokens to the user
SafeTransferLib.safeTransfer(
ERC20(buyTokenAddress),
msg.sender,
finalOutputTokenAmount - initialOutputTokenAmount
);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*///////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*///////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*///////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*///////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
bytes32 public constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*///////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*///////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*///////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*///////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
//SPDX-License-Identifier: GPL-3.0
pragma solidity =0.8.11;
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@uniswap/v3-periphery/contracts/interfaces/external/IERC20PermitAllowed.sol";
interface IDAI is IERC20Metadata, IERC20PermitAllowed {
function DOMAIN_SEPARATOR() external view returns (bytes32);
function PERMIT_TYPEHASH() external pure returns (bytes32);
function nonces(address owner) external view returns (uint256);
function version() external view returns (string memory);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Interface for permit
/// @notice Interface used by DAI/CHAI for permit
interface IERC20PermitAllowed {
/// @notice Approve the spender to spend some tokens via the holder signature
/// @dev This is the permit interface used by DAI and CHAI
/// @param holder The address of the token holder, the token owner
/// @param spender The address of the token spender
/// @param nonce The holder's nonce, increases at each call to permit
/// @param expiry The timestamp at which the permit is no longer valid
/// @param allowed Boolean that sets approval amount, true for type(uint256).max and false for 0
/// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s`
/// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s`
/// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v`
function permit(
address holder,
address spender,
uint256 nonce,
uint256 expiry,
bool allowed,
uint8 v,
bytes32 r,
bytes32 s
) external;
}
//SPDX-License-Identifier: GPL-3.0
pragma solidity =0.8.11;
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol";
interface IERC2612 is IERC20Metadata, IERC20Permit {
function _nonces(address owner) external view returns (uint256);
function version() external view returns (string memory);
}
//SPDX-License-Identifier: GPL-3.0
pragma solidity =0.8.11;
import "../interfaces/IERC2612.sol";
import "../interfaces/IDAI.sol";
/// @title PermitHelper
/// @dev Helper methods for using ERC20 Permit (ERC2612 or DAI/CHAI like)
library PermitHelper {
struct Permit {
uint256 value;
uint256 nonce;
uint256 deadline;
bool isDaiStylePermit;
uint8 v;
bytes32 r;
bytes32 s;
}
/// @dev permit method helper that will handle both known implementations
// DAI vs ERC2612 tokens
/// @param permitData bytes containing the encoded permit signature
/// @param tokenAddress address of the token that will be permitted
/// @param holder address that holds the tokens to be permitted
/// @param spender address that will be permitted to spend the tokens
function permit(
Permit memory permitData,
address tokenAddress,
address holder,
address spender
) internal {
if (permitData.isDaiStylePermit) {
IDAI(tokenAddress).permit(
holder,
spender,
permitData.nonce,
permitData.deadline,
true,
permitData.v,
permitData.r,
permitData.s
);
} else {
IERC2612(tokenAddress).permit(
holder,
spender,
permitData.value,
permitData.deadline,
permitData.v,
permitData.r,
permitData.s
);
}
}
}
/*
▐██▒ ███
,╓, ▄▄ ,▄▄▄, .▄▄. ,╓, ▄▄▄ ██▌ ▄▄▄ ,▄▄▄, ,╓╓ ╓╓ ,╓
██████ ███▀▀██⌐ ▐██⌐ ███▀▀███⌐ ████▀▀███ ╓██▀▀▀██▄`██▌ ▐██▌ ▐██
███ ▄▄█████▌ ▐██⌐ ██▌ ▐██▌ ██▌ ║██⌐███ ▓██ ╙██▄█▌██▄██⌐
██▌ ▐██▄▄███▌,▐██⌐ ██▌ ▐██▌ ███▓▄▄███ ╙██▄▄▄██▀ ║███¬╙███▌
╙╙└ ╙╙▀╙─╙▀▀└"╙╙` ╙╙└ ╙╙" ╙╙`╙╙▀▀└ ╙╙▀▀╙` ╙╙└ ╙╙╙
_," _ _"""ⁿ=-, _
⌠ _ __"=.__
▐░...... _ _ "=._
▐░░░░░░░░░░░░░░░░. "= _
╚╩╩╩╩╩╩δφφφφ░░░░░░░░░░░ >__
▐░░░░░░░░__ _ ╙╙╚╩φφ░░░░░░░ ^=_
▐░░░░░░░░░░░░░░░,░ `╙╠φ░░░░░░░ ⁿ
▐░░░░░░░░░░░░░░░░░░░░._ `╚Åφ░░░░░ " _
╚╠╠╠╠╠╠╠╬╬╬▒▒φ░░░░░░░░░░░░ ╙╠░░░░░ "
╚╝╝╝╝╝╝╝╬╬╬╠╢╬╠╬╠╬▒░░░░░░░░░░ "╚φ░░░░ ½_
▐░░░░░░░░░░;░╙╙╝╬╠╬╠╠▒▒░░░░░░░░_ ╚φ░░░░ "_
╚▒φφφ░░░░░░░░░░░░-╙╚╬╠╠╠╬▒░░░░░░░ `╠▒░░░░ ,
╞╬╬╬╠╠╠╬╬╬╬▒φ▒░░░░░░░╙╚╬╬╠╬▒▒░░░░░ .╙╠░░░░ ≥
_▒░░░ΓΓ╙╙╙╚╩╬╠╬╠▒▒░░░░░░╙╬╬╠╬▒▒░░░░░' ╠▒░░░░ ≥
`╙ⁿⁿ≈≈σ╓░ '╙╙╚╬╠╬▒░░░░░░╙╬╬╠╬▒░░░░░ ╠▒░░░░ [
_╙Θ░ ░╙╠╠╬╬▒░░░░░╬╠╠╠▒▒░░░░ ╠▒░░░░ '_
_╙φ░'╙╠╠╬▒░░░░░╟╠╠╠▒░░░░░ _╠▒░░░ ░_
_`φ ░╚╬╠╠▒░░░░║╠╠╠▒░░░░░.`╠░░░░ [
_╚░⌡╚╠╬╬▒░░░░╠╠╠╬▒░░░░░ ╠▒░░░░ ░
_╙░⌡╚╠╠╬▒░░░"▒╠╠╬▒░░░░ ⌠╠░░░░ ⌡_
╠ ░╠╠╠╬▒░░░║╠╬╠╬▒░░░ _╠▒░░░ Γ
▐░░░╠╠╠▒░░░╟╠╠╠╬▒░░░░ ╠▒░░░░ [
_░.░╠╠╠▒░░░▐╬╠╠╬▒░░░░[╠╬░░░░ │
_╙φ░╠╠╠╬▒░░▐╬╬╠╬╬▒░░░[╠╬░░░░ ░≥_
____ ____ __ _______ ____
/$$$$$$ /$$$$$$ /$$
/$$__ $$ /$$__ $$ | $$
| $$ \__/ /$$ /$$ /$$ /$$$$$$ /$$$$$$ | $$ \ $$ /$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$
| $$$$$$ | $$ | $$ | $$ |____ $$ /$$__ $$ | $$$$$$$$ /$$__ $$ /$$__ $$ /$$__ $$ /$$__ $$ /$$__ $$ |____ $$|_ $$_/ /$$__ $$ /$$__ $$
\____ $$| $$ | $$ | $$ /$$$$$$$| $$ \ $$ | $$__ $$| $$ \ $$| $$ \ $$| $$ \__/| $$$$$$$$| $$ \ $$ /$$$$$$$ | $$ | $$ \ $$| $$ \__/
/$$ \ $$| $$ | $$ | $$ /$$__ $$| $$ | $$ | $$ | $$| $$ | $$| $$ | $$| $$ | $$_____/| $$ | $$ /$$__ $$ | $$ /$$| $$ | $$| $$
| $$$$$$/| $$$$$/$$$$/| $$$$$$$| $$$$$$$/ | $$ | $$| $$$$$$$| $$$$$$$| $$ | $$$$$$$| $$$$$$$| $$$$$$$ | $$$$/| $$$$$$/| $$
\______/ \_____/\___/ \_______/| $$____/ |__/ |__/ \____ $$ \____ $$|__/ \_______/ \____ $$ \_______/ \___/ \______/ |__/
| $$ /$$ \ $$ /$$ \ $$ /$$ \ $$
| $$ | $$$$$$/| $$$$$$/ | $$$$$$/
|__/ \______/ \______/ \______/
*/
//SPDX-License-Identifier: GPL-3.0
pragma solidity =0.8.11;
import "@rari-capital/solmate/src/utils/SafeTransferLib.sol";
import "@rari-capital/solmate/src/tokens/ERC20.sol";
import "./routers/BaseAggregator.sol";
/// @title Rainbow swap aggregator contract
contract RainbowRouter is BaseAggregator {
/// @dev The address that is the current owner of this contract
address public owner;
/// @dev Event emitted when the owner changes
event OwnerChanged(address indexed newOwner, address indexed oldOwner);
/// @dev Event emitted when a swap target gets added
event SwapTargetAdded(address indexed target);
/// @dev Event emitted when a swap target gets removed
event SwapTargetRemoved(address indexed target);
/// @dev Event emitted when token fees are withdrawn
event TokenWithdrawn(
address indexed token,
address indexed target,
uint256 amount
);
/// @dev Event emitted when ETH fees are withdrawn
event EthWithdrawn(address indexed target, uint256 amount);
/// @dev modifier that ensures only the owner is allowed to call a specific method
modifier onlyOwner() {
require(msg.sender == owner, "ONLY_OWNER");
_;
}
constructor() {
owner = msg.sender;
status = 1;
}
/// @dev We don't want to accept any ETH, except refunds from aggregators
/// or the owner (for testing purposes), which can also withdraw
/// This is done by evaluating the value of status, which is set to 2
/// only during swaps due to the "nonReentrant" modifier
receive() external payable {
require(status == 2 || msg.sender == owner, "NO_RECEIVE");
}
/// @dev method to add or remove swap targets from swapTargets
/// This is required so we only approve "trusted" swap targets
/// to transfer tokens out of this contract
/// @param target address of the swap target to add
/// @param add flag to add or remove the swap target
function updateSwapTargets(address target, bool add) external onlyOwner {
swapTargets[target] = add;
if (add) {
emit SwapTargetAdded(target);
} else {
emit SwapTargetRemoved(target);
}
}
/// @dev method to withdraw ERC20 tokens (from the fees)
/// @param token address of the token to withdraw
/// @param to address that's receiving the tokens
/// @param amount amount of tokens to withdraw
function withdrawToken(
address token,
address to,
uint256 amount
) external onlyOwner {
require(to != address(0), "ZERO_ADDRESS");
SafeTransferLib.safeTransfer(ERC20(token), to, amount);
emit TokenWithdrawn(token, to, amount);
}
/// @dev method to withdraw ETH (from the fees)
/// @param to address that's receiving the ETH
/// @param amount amount of ETH to withdraw
function withdrawEth(address to, uint256 amount) external onlyOwner {
require(to != address(0), "ZERO_ADDRESS");
SafeTransferLib.safeTransferETH(to, amount);
emit EthWithdrawn(to, amount);
}
/// @dev Transfers ownership of the contract to a new account (`newOwner`).
/// @param newOwner address of the new owner
/// Can only be called by the current owner.
function transferOwnership(address newOwner) external virtual onlyOwner {
require(newOwner != address(0), "ZERO_ADDRESS");
require(newOwner != owner, "SAME_OWNER");
address previousOwner = owner;
owner = newOwner;
emit OwnerChanged(newOwner, previousOwner);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
/// @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(
ERC20 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(
ERC20 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(
ERC20 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
}
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
{
"compilationTarget": {
"contracts/RainbowRouter.sol": "RainbowRouter"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 1000
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"EthWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newOwner","type":"address"},{"indexed":true,"internalType":"address","name":"oldOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"target","type":"address"}],"name":"SwapTargetAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"target","type":"address"}],"name":"SwapTargetRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TokenWithdrawn","type":"event"},{"inputs":[{"internalType":"address","name":"buyTokenAddress","type":"address"},{"internalType":"address payable","name":"target","type":"address"},{"internalType":"bytes","name":"swapCallData","type":"bytes"},{"internalType":"uint256","name":"feeAmount","type":"uint256"}],"name":"fillQuoteEthToToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"sellTokenAddress","type":"address"},{"internalType":"address payable","name":"target","type":"address"},{"internalType":"bytes","name":"swapCallData","type":"bytes"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"uint256","name":"feePercentageBasisPoints","type":"uint256"}],"name":"fillQuoteTokenToEth","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"sellTokenAddress","type":"address"},{"internalType":"address payable","name":"target","type":"address"},{"internalType":"bytes","name":"swapCallData","type":"bytes"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"uint256","name":"feePercentageBasisPoints","type":"uint256"},{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"isDaiStylePermit","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct PermitHelper.Permit","name":"permitData","type":"tuple"}],"name":"fillQuoteTokenToEthWithPermit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"sellTokenAddress","type":"address"},{"internalType":"address","name":"buyTokenAddress","type":"address"},{"internalType":"address payable","name":"target","type":"address"},{"internalType":"bytes","name":"swapCallData","type":"bytes"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"uint256","name":"feeAmount","type":"uint256"}],"name":"fillQuoteTokenToToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"sellTokenAddress","type":"address"},{"internalType":"address","name":"buyTokenAddress","type":"address"},{"internalType":"address payable","name":"target","type":"address"},{"internalType":"bytes","name":"swapCallData","type":"bytes"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"uint256","name":"feeAmount","type":"uint256"},{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"isDaiStylePermit","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct PermitHelper.Permit","name":"permitData","type":"tuple"}],"name":"fillQuoteTokenToTokenWithPermit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"swapTargets","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bool","name":"add","type":"bool"}],"name":"updateSwapTargets","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawEth","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]