1677 lines
59 KiB
Solidity
1677 lines
59 KiB
Solidity
// Sources flattened with hardhat v2.11.2 https://hardhat.org
|
||
|
||
// File contracts/lib/InitializableOwnable.sol
|
||
|
||
|
||
|
||
/**
|
||
* @title Ownable
|
||
* @author DODO Breeder
|
||
*
|
||
* @notice Ownership related functions
|
||
*/
|
||
contract InitializableOwnable {
|
||
address public _OWNER_;
|
||
address public _NEW_OWNER_;
|
||
bool internal _INITIALIZED_;
|
||
|
||
// ============ Events ============
|
||
|
||
event OwnershipTransferPrepared(address indexed previousOwner, address indexed newOwner);
|
||
|
||
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
|
||
|
||
// ============ Modifiers ============
|
||
|
||
modifier notInitialized() {
|
||
require(!_INITIALIZED_, "DODO_INITIALIZED");
|
||
_;
|
||
}
|
||
|
||
modifier onlyOwner() {
|
||
require(msg.sender == _OWNER_, "NOT_OWNER");
|
||
_;
|
||
}
|
||
|
||
// ============ Functions ============
|
||
|
||
function initOwner(address newOwner) public notInitialized {
|
||
_INITIALIZED_ = true;
|
||
_OWNER_ = newOwner;
|
||
}
|
||
|
||
function transferOwnership(address newOwner) public onlyOwner {
|
||
emit OwnershipTransferPrepared(_OWNER_, newOwner);
|
||
_NEW_OWNER_ = newOwner;
|
||
}
|
||
|
||
function claimOwnership() public {
|
||
require(msg.sender == _NEW_OWNER_, "INVALID_CLAIM");
|
||
emit OwnershipTransferred(_OWNER_, _NEW_OWNER_);
|
||
_OWNER_ = _NEW_OWNER_;
|
||
_NEW_OWNER_ = address(0);
|
||
}
|
||
}
|
||
|
||
|
||
// File contracts/intf/IDODOApprove.sol
|
||
|
||
|
||
interface IDODOApprove {
|
||
function claimTokens(address token,address who,address dest,uint256 amount) external;
|
||
function getDODOProxy() external view returns (address);
|
||
}
|
||
|
||
|
||
// File contracts/DODOApproveProxy.sol
|
||
|
||
|
||
|
||
|
||
interface IDODOApproveProxy {
|
||
function isAllowedProxy(address _proxy) external view returns (bool);
|
||
function claimTokens(address token,address who,address dest,uint256 amount) external;
|
||
}
|
||
|
||
/**
|
||
* @title DODOApproveProxy
|
||
* @author DODO Breeder
|
||
*
|
||
* @notice Allow different version dodoproxy to claim from DODOApprove
|
||
*/
|
||
contract DODOApproveProxy is InitializableOwnable {
|
||
|
||
// ============ Storage ============
|
||
uint256 private constant _TIMELOCK_DURATION_ = 3 days;
|
||
mapping (address => bool) public _IS_ALLOWED_PROXY_;
|
||
uint256 public _TIMELOCK_;
|
||
address public _PENDING_ADD_DODO_PROXY_;
|
||
address public immutable _DODO_APPROVE_;
|
||
|
||
// ============ Modifiers ============
|
||
modifier notLocked() {
|
||
require(
|
||
_TIMELOCK_ <= block.timestamp,
|
||
"SetProxy is timelocked"
|
||
);
|
||
_;
|
||
}
|
||
|
||
constructor(address dodoApporve) {
|
||
_DODO_APPROVE_ = dodoApporve;
|
||
}
|
||
|
||
function init(address owner, address[] memory proxies) external {
|
||
initOwner(owner);
|
||
for(uint i = 0; i < proxies.length; i++)
|
||
_IS_ALLOWED_PROXY_[proxies[i]] = true;
|
||
}
|
||
|
||
function unlockAddProxy(address newDodoProxy) public onlyOwner {
|
||
_TIMELOCK_ = block.timestamp + _TIMELOCK_DURATION_;
|
||
_PENDING_ADD_DODO_PROXY_ = newDodoProxy;
|
||
}
|
||
|
||
function lockAddProxy() public onlyOwner {
|
||
_PENDING_ADD_DODO_PROXY_ = address(0);
|
||
_TIMELOCK_ = 0;
|
||
}
|
||
|
||
|
||
function addDODOProxy() external onlyOwner notLocked() {
|
||
_IS_ALLOWED_PROXY_[_PENDING_ADD_DODO_PROXY_] = true;
|
||
lockAddProxy();
|
||
}
|
||
|
||
function removeDODOProxy (address oldDodoProxy) public onlyOwner {
|
||
_IS_ALLOWED_PROXY_[oldDodoProxy] = false;
|
||
}
|
||
|
||
function claimTokens(
|
||
address token,
|
||
address who,
|
||
address dest,
|
||
uint256 amount
|
||
) external {
|
||
require(_IS_ALLOWED_PROXY_[msg.sender], "DODOApproveProxy:Access restricted");
|
||
IDODOApprove(_DODO_APPROVE_).claimTokens(
|
||
token,
|
||
who,
|
||
dest,
|
||
amount
|
||
);
|
||
}
|
||
|
||
function isAllowedProxy(address _proxy) external view returns (bool) {
|
||
return _IS_ALLOWED_PROXY_[_proxy];
|
||
}
|
||
}
|
||
|
||
|
||
// File contracts/intf/IWETH.sol
|
||
|
||
|
||
|
||
|
||
interface IWETH {
|
||
function totalSupply() external view returns (uint256);
|
||
|
||
function balanceOf(address account) external view returns (uint256);
|
||
|
||
function transfer(address recipient, uint256 amount) external returns (bool);
|
||
|
||
function allowance(address owner, address spender) external view returns (uint256);
|
||
|
||
function approve(address spender, uint256 amount) external returns (bool);
|
||
|
||
function transferFrom(
|
||
address src,
|
||
address dst,
|
||
uint256 wad
|
||
) external returns (bool);
|
||
|
||
function deposit() external payable;
|
||
|
||
function withdraw(uint256 wad) external;
|
||
}
|
||
|
||
|
||
// File @openzeppelin/contracts/utils/math/Math.sol@v4.7.3
|
||
|
||
|
||
|
||
/**
|
||
* @dev Standard math utilities missing in the Solidity language.
|
||
*/
|
||
library Math {
|
||
enum Rounding {
|
||
Down, // Toward negative infinity
|
||
Up, // Toward infinity
|
||
Zero // Toward zero
|
||
}
|
||
|
||
/**
|
||
* @dev Returns the largest of two numbers.
|
||
*/
|
||
function max(uint256 a, uint256 b) internal pure returns (uint256) {
|
||
return a >= b ? a : b;
|
||
}
|
||
|
||
/**
|
||
* @dev Returns the smallest of two numbers.
|
||
*/
|
||
function min(uint256 a, uint256 b) internal pure returns (uint256) {
|
||
return a < b ? a : b;
|
||
}
|
||
|
||
/**
|
||
* @dev Returns the average of two numbers. The result is rounded towards
|
||
* zero.
|
||
*/
|
||
function average(uint256 a, uint256 b) internal pure returns (uint256) {
|
||
// (a + b) / 2 can overflow.
|
||
return (a & b) + (a ^ b) / 2;
|
||
}
|
||
|
||
/**
|
||
* @dev Returns the ceiling of the division of two numbers.
|
||
*
|
||
* This differs from standard division with `/` in that it rounds up instead
|
||
* of rounding down.
|
||
*/
|
||
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
|
||
// (a + b - 1) / b can overflow on addition, so we distribute.
|
||
return a == 0 ? 0 : (a - 1) / b + 1;
|
||
}
|
||
|
||
/**
|
||
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
|
||
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
|
||
* with further edits by Uniswap Labs also under MIT license.
|
||
*/
|
||
function mulDiv(
|
||
uint256 x,
|
||
uint256 y,
|
||
uint256 denominator
|
||
) internal pure returns (uint256 result) {
|
||
unchecked {
|
||
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
|
||
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
|
||
// variables such that product = prod1 * 2^256 + prod0.
|
||
uint256 prod0; // Least significant 256 bits of the product
|
||
uint256 prod1; // Most significant 256 bits of the product
|
||
assembly {
|
||
let mm := mulmod(x, y, not(0))
|
||
prod0 := mul(x, y)
|
||
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
|
||
}
|
||
|
||
// Handle non-overflow cases, 256 by 256 division.
|
||
if (prod1 == 0) {
|
||
return prod0 / denominator;
|
||
}
|
||
|
||
// Make sure the result is less than 2^256. Also prevents denominator == 0.
|
||
require(denominator > prod1);
|
||
|
||
///////////////////////////////////////////////
|
||
// 512 by 256 division.
|
||
///////////////////////////////////////////////
|
||
|
||
// Make division exact by subtracting the remainder from [prod1 prod0].
|
||
uint256 remainder;
|
||
assembly {
|
||
// Compute remainder using mulmod.
|
||
remainder := mulmod(x, y, denominator)
|
||
|
||
// Subtract 256 bit number from 512 bit number.
|
||
prod1 := sub(prod1, gt(remainder, prod0))
|
||
prod0 := sub(prod0, remainder)
|
||
}
|
||
|
||
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
|
||
// See https://cs.stackexchange.com/q/138556/92363.
|
||
|
||
// Does not overflow because the denominator cannot be zero at this stage in the function.
|
||
uint256 twos = denominator & (~denominator + 1);
|
||
assembly {
|
||
// Divide denominator by twos.
|
||
denominator := div(denominator, twos)
|
||
|
||
// Divide [prod1 prod0] by twos.
|
||
prod0 := div(prod0, twos)
|
||
|
||
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
|
||
twos := add(div(sub(0, twos), twos), 1)
|
||
}
|
||
|
||
// Shift in bits from prod1 into prod0.
|
||
prod0 |= prod1 * twos;
|
||
|
||
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
|
||
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
|
||
// four bits. That is, denominator * inv = 1 mod 2^4.
|
||
uint256 inverse = (3 * denominator) ^ 2;
|
||
|
||
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
|
||
// in modular arithmetic, doubling the correct bits in each step.
|
||
inverse *= 2 - denominator * inverse; // inverse mod 2^8
|
||
inverse *= 2 - denominator * inverse; // inverse mod 2^16
|
||
inverse *= 2 - denominator * inverse; // inverse mod 2^32
|
||
inverse *= 2 - denominator * inverse; // inverse mod 2^64
|
||
inverse *= 2 - denominator * inverse; // inverse mod 2^128
|
||
inverse *= 2 - denominator * inverse; // inverse mod 2^256
|
||
|
||
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
|
||
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
|
||
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
|
||
// is no longer required.
|
||
result = prod0 * inverse;
|
||
return result;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
|
||
*/
|
||
function mulDiv(
|
||
uint256 x,
|
||
uint256 y,
|
||
uint256 denominator,
|
||
Rounding rounding
|
||
) internal pure returns (uint256) {
|
||
uint256 result = mulDiv(x, y, denominator);
|
||
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
|
||
result += 1;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* @dev Returns the square root of a number. It the number is not a perfect square, the value is rounded down.
|
||
*
|
||
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
|
||
*/
|
||
function sqrt(uint256 a) internal pure returns (uint256) {
|
||
if (a == 0) {
|
||
return 0;
|
||
}
|
||
|
||
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
|
||
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
|
||
// `msb(a) <= a < 2*msb(a)`.
|
||
// We also know that `k`, the position of the most significant bit, is such that `msb(a) = 2**k`.
|
||
// This gives `2**k < a <= 2**(k+1)` → `2**(k/2) <= sqrt(a) < 2 ** (k/2+1)`.
|
||
// Using an algorithm similar to the msb conmputation, we are able to compute `result = 2**(k/2)` which is a
|
||
// good first aproximation of `sqrt(a)` with at least 1 correct bit.
|
||
uint256 result = 1;
|
||
uint256 x = a;
|
||
if (x >> 128 > 0) {
|
||
x >>= 128;
|
||
result <<= 64;
|
||
}
|
||
if (x >> 64 > 0) {
|
||
x >>= 64;
|
||
result <<= 32;
|
||
}
|
||
if (x >> 32 > 0) {
|
||
x >>= 32;
|
||
result <<= 16;
|
||
}
|
||
if (x >> 16 > 0) {
|
||
x >>= 16;
|
||
result <<= 8;
|
||
}
|
||
if (x >> 8 > 0) {
|
||
x >>= 8;
|
||
result <<= 4;
|
||
}
|
||
if (x >> 4 > 0) {
|
||
x >>= 4;
|
||
result <<= 2;
|
||
}
|
||
if (x >> 2 > 0) {
|
||
result <<= 1;
|
||
}
|
||
|
||
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
|
||
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
|
||
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
|
||
// into the expected uint128 result.
|
||
unchecked {
|
||
result = (result + a / result) >> 1;
|
||
result = (result + a / result) >> 1;
|
||
result = (result + a / result) >> 1;
|
||
result = (result + a / result) >> 1;
|
||
result = (result + a / result) >> 1;
|
||
result = (result + a / result) >> 1;
|
||
result = (result + a / result) >> 1;
|
||
return min(result, a / result);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @notice Calculates sqrt(a), following the selected rounding direction.
|
||
*/
|
||
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
|
||
uint256 result = sqrt(a);
|
||
if (rounding == Rounding.Up && result * result < a) {
|
||
result += 1;
|
||
}
|
||
return result;
|
||
}
|
||
}
|
||
|
||
|
||
// File contracts/lib/DecimalMath.sol
|
||
|
||
|
||
/**
|
||
* @title DecimalMath
|
||
* @author DODO Breeder
|
||
*
|
||
* @notice Functions for fixed point number with 18 decimals
|
||
*/
|
||
|
||
library DecimalMath {
|
||
|
||
uint256 internal constant ONE = 10**18;
|
||
uint256 internal constant ONE2 = 10**36;
|
||
|
||
function mul(uint256 target, uint256 d) internal pure returns (uint256) {
|
||
return target * d / (10**18);
|
||
}
|
||
|
||
function mulFloor(uint256 target, uint256 d) internal pure returns (uint256) {
|
||
return target * d / (10**18);
|
||
}
|
||
|
||
function mulCeil(uint256 target, uint256 d) internal pure returns (uint256) {
|
||
return _divCeil(target * d, 10**18);
|
||
}
|
||
|
||
function div(uint256 target, uint256 d) internal pure returns (uint256) {
|
||
return target * (10**18) / d;
|
||
}
|
||
|
||
function divFloor(uint256 target, uint256 d) internal pure returns (uint256) {
|
||
return target * (10**18) / d;
|
||
}
|
||
|
||
function divCeil(uint256 target, uint256 d) internal pure returns (uint256) {
|
||
return _divCeil(target * (10**18), d);
|
||
}
|
||
|
||
function reciprocalFloor(uint256 target) internal pure returns (uint256) {
|
||
return uint256(10**36) / target;
|
||
}
|
||
|
||
function reciprocalCeil(uint256 target) internal pure returns (uint256) {
|
||
return _divCeil(uint256(10**36), target);
|
||
}
|
||
|
||
function sqrt(uint256 target) internal pure returns (uint256) {
|
||
return Math.sqrt(target * ONE);
|
||
}
|
||
|
||
function powFloor(uint256 target, uint256 e) internal pure returns (uint256) {
|
||
if (e == 0) {
|
||
return 10 ** 18;
|
||
} else if (e == 1) {
|
||
return target;
|
||
} else {
|
||
uint p = powFloor(target, e / 2);
|
||
p = p * p / (10**18);
|
||
if (e % 2 == 1) {
|
||
p = p * target / (10**18);
|
||
}
|
||
return p;
|
||
}
|
||
}
|
||
|
||
function _divCeil(uint256 a, uint256 b) internal pure returns (uint256) {
|
||
uint256 quotient = a / b;
|
||
uint256 remainder = a - quotient * b;
|
||
if (remainder > 0) {
|
||
return quotient + 1;
|
||
} else {
|
||
return quotient;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// File contracts/SmartRoute/intf/IDODOAdapter.sol
|
||
|
||
|
||
|
||
interface IDODOAdapter {
|
||
|
||
function sellBase(address to, address pool, bytes memory data) external;
|
||
|
||
function sellQuote(address to, address pool, bytes memory data) external;
|
||
}
|
||
|
||
|
||
// File @openzeppelin/contracts/token/ERC20/IERC20.sol@v4.7.3
|
||
|
||
|
||
|
||
/**
|
||
* @dev Interface of the ERC20 standard as defined in the EIP.
|
||
*/
|
||
interface IERC20 {
|
||
/**
|
||
* @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);
|
||
|
||
/**
|
||
* @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 `to`.
|
||
*
|
||
* Returns a boolean value indicating whether the operation succeeded.
|
||
*
|
||
* Emits a {Transfer} event.
|
||
*/
|
||
function transfer(address to, 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 `from` to `to` 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 from,
|
||
address to,
|
||
uint256 amount
|
||
) external returns (bool);
|
||
}
|
||
|
||
|
||
// File @openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol@v4.7.3
|
||
|
||
|
||
|
||
/**
|
||
* @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);
|
||
}
|
||
|
||
|
||
// File @openzeppelin/contracts/utils/Address.sol@v4.7.3
|
||
|
||
|
||
|
||
/**
|
||
* @dev Collection of functions related to the address type
|
||
*/
|
||
library Address {
|
||
/**
|
||
* @dev Returns true if `account` is a contract.
|
||
*
|
||
* [IMPORTANT]
|
||
* ====
|
||
* It is unsafe to assume that an address for which this function returns
|
||
* false is an externally-owned account (EOA) and not a contract.
|
||
*
|
||
* Among others, `isContract` will return false for the following
|
||
* types of addresses:
|
||
*
|
||
* - an externally-owned account
|
||
* - a contract in construction
|
||
* - an address where a contract will be created
|
||
* - an address where a contract lived, but was destroyed
|
||
* ====
|
||
*
|
||
* [IMPORTANT]
|
||
* ====
|
||
* You shouldn't rely on `isContract` to protect against flash loan attacks!
|
||
*
|
||
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
|
||
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
|
||
* constructor.
|
||
* ====
|
||
*/
|
||
function isContract(address account) internal view returns (bool) {
|
||
// This method relies on extcodesize/address.code.length, which returns 0
|
||
// for contracts in construction, since the code is only stored at the end
|
||
// of the constructor execution.
|
||
|
||
return account.code.length > 0;
|
||
}
|
||
|
||
/**
|
||
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
|
||
* `recipient`, forwarding all available gas and reverting on errors.
|
||
*
|
||
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
|
||
* of certain opcodes, possibly making contracts go over the 2300 gas limit
|
||
* imposed by `transfer`, making them unable to receive funds via
|
||
* `transfer`. {sendValue} removes this limitation.
|
||
*
|
||
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
|
||
*
|
||
* IMPORTANT: because control is transferred to `recipient`, care must be
|
||
* taken to not create reentrancy vulnerabilities. Consider using
|
||
* {ReentrancyGuard} or the
|
||
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
|
||
*/
|
||
function sendValue(address payable recipient, uint256 amount) internal {
|
||
require(address(this).balance >= amount, "Address: insufficient balance");
|
||
|
||
(bool success, ) = recipient.call{value: amount}("");
|
||
require(success, "Address: unable to send value, recipient may have reverted");
|
||
}
|
||
|
||
/**
|
||
* @dev Performs a Solidity function call using a low level `call`. A
|
||
* plain `call` is an unsafe replacement for a function call: use this
|
||
* function instead.
|
||
*
|
||
* If `target` reverts with a revert reason, it is bubbled up by this
|
||
* function (like regular Solidity function calls).
|
||
*
|
||
* Returns the raw returned data. To convert to the expected return value,
|
||
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
|
||
*
|
||
* Requirements:
|
||
*
|
||
* - `target` must be a contract.
|
||
* - calling `target` with `data` must not revert.
|
||
*
|
||
* _Available since v3.1._
|
||
*/
|
||
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
|
||
return functionCall(target, data, "Address: low-level call failed");
|
||
}
|
||
|
||
/**
|
||
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
|
||
* `errorMessage` as a fallback revert reason when `target` reverts.
|
||
*
|
||
* _Available since v3.1._
|
||
*/
|
||
function functionCall(
|
||
address target,
|
||
bytes memory data,
|
||
string memory errorMessage
|
||
) internal returns (bytes memory) {
|
||
return functionCallWithValue(target, data, 0, errorMessage);
|
||
}
|
||
|
||
/**
|
||
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
|
||
* but also transferring `value` wei to `target`.
|
||
*
|
||
* Requirements:
|
||
*
|
||
* - the calling contract must have an ETH balance of at least `value`.
|
||
* - the called Solidity function must be `payable`.
|
||
*
|
||
* _Available since v3.1._
|
||
*/
|
||
function functionCallWithValue(
|
||
address target,
|
||
bytes memory data,
|
||
uint256 value
|
||
) internal returns (bytes memory) {
|
||
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
|
||
}
|
||
|
||
/**
|
||
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
|
||
* with `errorMessage` as a fallback revert reason when `target` reverts.
|
||
*
|
||
* _Available since v3.1._
|
||
*/
|
||
function functionCallWithValue(
|
||
address target,
|
||
bytes memory data,
|
||
uint256 value,
|
||
string memory errorMessage
|
||
) internal returns (bytes memory) {
|
||
require(address(this).balance >= value, "Address: insufficient balance for call");
|
||
require(isContract(target), "Address: call to non-contract");
|
||
|
||
(bool success, bytes memory returndata) = target.call{value: value}(data);
|
||
return verifyCallResult(success, returndata, errorMessage);
|
||
}
|
||
|
||
/**
|
||
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
|
||
* but performing a static call.
|
||
*
|
||
* _Available since v3.3._
|
||
*/
|
||
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
|
||
return functionStaticCall(target, data, "Address: low-level static call failed");
|
||
}
|
||
|
||
/**
|
||
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
|
||
* but performing a static call.
|
||
*
|
||
* _Available since v3.3._
|
||
*/
|
||
function functionStaticCall(
|
||
address target,
|
||
bytes memory data,
|
||
string memory errorMessage
|
||
) internal view returns (bytes memory) {
|
||
require(isContract(target), "Address: static call to non-contract");
|
||
|
||
(bool success, bytes memory returndata) = target.staticcall(data);
|
||
return verifyCallResult(success, returndata, errorMessage);
|
||
}
|
||
|
||
/**
|
||
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
|
||
* but performing a delegate call.
|
||
*
|
||
* _Available since v3.4._
|
||
*/
|
||
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
|
||
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
|
||
}
|
||
|
||
/**
|
||
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
|
||
* but performing a delegate call.
|
||
*
|
||
* _Available since v3.4._
|
||
*/
|
||
function functionDelegateCall(
|
||
address target,
|
||
bytes memory data,
|
||
string memory errorMessage
|
||
) internal returns (bytes memory) {
|
||
require(isContract(target), "Address: delegate call to non-contract");
|
||
|
||
(bool success, bytes memory returndata) = target.delegatecall(data);
|
||
return verifyCallResult(success, returndata, errorMessage);
|
||
}
|
||
|
||
/**
|
||
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
|
||
* revert reason using the provided one.
|
||
*
|
||
* _Available since v4.3._
|
||
*/
|
||
function verifyCallResult(
|
||
bool success,
|
||
bytes memory returndata,
|
||
string memory errorMessage
|
||
) internal pure returns (bytes memory) {
|
||
if (success) {
|
||
return returndata;
|
||
} else {
|
||
// Look for revert reason and bubble it up if present
|
||
if (returndata.length > 0) {
|
||
// The easiest way to bubble the revert reason is using memory via assembly
|
||
/// @solidity memory-safe-assembly
|
||
assembly {
|
||
let returndata_size := mload(returndata)
|
||
revert(add(32, returndata), returndata_size)
|
||
}
|
||
} else {
|
||
revert(errorMessage);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// File @openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol@v4.7.3
|
||
|
||
|
||
|
||
|
||
|
||
/**
|
||
* @title SafeERC20
|
||
* @dev Wrappers around ERC20 operations that throw on failure (when the token
|
||
* contract returns false). Tokens that return no value (and instead revert or
|
||
* throw on failure) are also supported, non-reverting calls are assumed to be
|
||
* successful.
|
||
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
|
||
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
|
||
*/
|
||
library SafeERC20 {
|
||
using Address for address;
|
||
|
||
function safeTransfer(
|
||
IERC20 token,
|
||
address to,
|
||
uint256 value
|
||
) internal {
|
||
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
|
||
}
|
||
|
||
function safeTransferFrom(
|
||
IERC20 token,
|
||
address from,
|
||
address to,
|
||
uint256 value
|
||
) internal {
|
||
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
|
||
}
|
||
|
||
/**
|
||
* @dev Deprecated. This function has issues similar to the ones found in
|
||
* {IERC20-approve}, and its usage is discouraged.
|
||
*
|
||
* Whenever possible, use {safeIncreaseAllowance} and
|
||
* {safeDecreaseAllowance} instead.
|
||
*/
|
||
function safeApprove(
|
||
IERC20 token,
|
||
address spender,
|
||
uint256 value
|
||
) internal {
|
||
// safeApprove should only be called when setting an initial allowance,
|
||
// or when resetting it to zero. To increase and decrease it, use
|
||
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
|
||
require(
|
||
(value == 0) || (token.allowance(address(this), spender) == 0),
|
||
"SafeERC20: approve from non-zero to non-zero allowance"
|
||
);
|
||
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
|
||
}
|
||
|
||
function safeIncreaseAllowance(
|
||
IERC20 token,
|
||
address spender,
|
||
uint256 value
|
||
) internal {
|
||
uint256 newAllowance = token.allowance(address(this), spender) + value;
|
||
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
|
||
}
|
||
|
||
function safeDecreaseAllowance(
|
||
IERC20 token,
|
||
address spender,
|
||
uint256 value
|
||
) internal {
|
||
unchecked {
|
||
uint256 oldAllowance = token.allowance(address(this), spender);
|
||
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
|
||
uint256 newAllowance = oldAllowance - value;
|
||
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
|
||
}
|
||
}
|
||
|
||
function safePermit(
|
||
IERC20Permit token,
|
||
address owner,
|
||
address spender,
|
||
uint256 value,
|
||
uint256 deadline,
|
||
uint8 v,
|
||
bytes32 r,
|
||
bytes32 s
|
||
) internal {
|
||
uint256 nonceBefore = token.nonces(owner);
|
||
token.permit(owner, spender, value, deadline, v, r, s);
|
||
uint256 nonceAfter = token.nonces(owner);
|
||
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
|
||
}
|
||
|
||
/**
|
||
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
|
||
* on the return value: the return value is optional (but if data is returned, it must not be false).
|
||
* @param token The token targeted by the call.
|
||
* @param data The call data (encoded using abi.encode or one of its variants).
|
||
*/
|
||
function _callOptionalReturn(IERC20 token, bytes memory data) private {
|
||
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
|
||
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
|
||
// the target address contains contract code and also asserts for success in the low-level call.
|
||
|
||
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
|
||
if (returndata.length > 0) {
|
||
// Return data is optional
|
||
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// File contracts/SmartRoute/lib/UniversalERC20.sol
|
||
|
||
|
||
|
||
|
||
library UniversalERC20 {
|
||
using SafeERC20 for IERC20;
|
||
|
||
IERC20 private constant ETH_ADDRESS = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
|
||
|
||
// 1. skip 0 amount
|
||
// 2. handle ETH transfer
|
||
function universalTransfer(
|
||
IERC20 token,
|
||
address payable to,
|
||
uint256 amount
|
||
) internal {
|
||
if (amount > 0) {
|
||
if (isETH(token)) {
|
||
to.transfer(amount);
|
||
} else {
|
||
token.safeTransfer(to, amount);
|
||
}
|
||
}
|
||
}
|
||
|
||
function universalApproveMax(
|
||
IERC20 token,
|
||
address to,
|
||
uint256 amount
|
||
) internal {
|
||
uint256 allowance = token.allowance(address(this), to);
|
||
if (allowance < amount) {
|
||
if (allowance > 0) {
|
||
token.safeApprove(to, 0);
|
||
}
|
||
token.safeApprove(to, type(uint256).max);
|
||
}
|
||
}
|
||
|
||
function universalBalanceOf(IERC20 token, address who) internal view returns (uint256) {
|
||
if (isETH(token)) {
|
||
return who.balance;
|
||
} else {
|
||
return token.balanceOf(who);
|
||
}
|
||
}
|
||
|
||
function tokenBalanceOf(IERC20 token, address who) internal view returns (uint256) {
|
||
return token.balanceOf(who);
|
||
}
|
||
|
||
function isETH(IERC20 token) internal pure returns (bool) {
|
||
return token == ETH_ADDRESS;
|
||
}
|
||
}
|
||
|
||
|
||
// File @openzeppelin/contracts/utils/Context.sol@v4.7.3
|
||
|
||
|
||
|
||
/**
|
||
* @dev Provides information about the current execution context, including the
|
||
* sender of the transaction and its data. While these are generally available
|
||
* via msg.sender and msg.data, they should not be accessed in such a direct
|
||
* manner, since when dealing with meta-transactions the account sending and
|
||
* paying for execution may not be the actual sender (as far as an application
|
||
* is concerned).
|
||
*
|
||
* This contract is only required for intermediate, library-like contracts.
|
||
*/
|
||
abstract contract Context {
|
||
function _msgSender() internal view virtual returns (address) {
|
||
return msg.sender;
|
||
}
|
||
|
||
function _msgData() internal view virtual returns (bytes calldata) {
|
||
return msg.data;
|
||
}
|
||
}
|
||
|
||
|
||
// File @openzeppelin/contracts/access/Ownable.sol@v4.7.3
|
||
|
||
|
||
|
||
/**
|
||
* @dev Contract module which provides a basic access control mechanism, where
|
||
* there is an account (an owner) that can be granted exclusive access to
|
||
* specific functions.
|
||
*
|
||
* By default, the owner account will be the one that deploys the contract. This
|
||
* can later be changed with {transferOwnership}.
|
||
*
|
||
* This module is used through inheritance. It will make available the modifier
|
||
* `onlyOwner`, which can be applied to your functions to restrict their use to
|
||
* the owner.
|
||
*/
|
||
abstract contract Ownable is Context {
|
||
address private _owner;
|
||
|
||
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
|
||
|
||
/**
|
||
* @dev Initializes the contract setting the deployer as the initial owner.
|
||
*/
|
||
constructor() {
|
||
_transferOwnership(_msgSender());
|
||
}
|
||
|
||
/**
|
||
* @dev Throws if called by any account other than the owner.
|
||
*/
|
||
modifier onlyOwner() {
|
||
_checkOwner();
|
||
_;
|
||
}
|
||
|
||
/**
|
||
* @dev Returns the address of the current owner.
|
||
*/
|
||
function owner() public view virtual returns (address) {
|
||
return _owner;
|
||
}
|
||
|
||
/**
|
||
* @dev Throws if the sender is not the owner.
|
||
*/
|
||
function _checkOwner() internal view virtual {
|
||
require(owner() == _msgSender(), "Ownable: caller is not the owner");
|
||
}
|
||
|
||
/**
|
||
* @dev Leaves the contract without owner. It will not be possible to call
|
||
* `onlyOwner` functions anymore. Can only be called by the current owner.
|
||
*
|
||
* NOTE: Renouncing ownership will leave the contract without an owner,
|
||
* thereby removing any functionality that is only available to the owner.
|
||
*/
|
||
function renounceOwnership() public virtual onlyOwner {
|
||
_transferOwnership(address(0));
|
||
}
|
||
|
||
/**
|
||
* @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);
|
||
}
|
||
}
|
||
|
||
|
||
// File contracts/SmartRoute/DODORouteProxy.sol
|
||
|
||
/*
|
||
|
||
Copyright 2022 DODO ZOO.
|
||
SPDX-License-Identifier: Apache-2.0
|
||
|
||
*/
|
||
|
||
pragma solidity 0.8.16;
|
||
pragma experimental ABIEncoderV2;
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/// @title DODORouteProxy
|
||
/// @author DODO Breeder
|
||
/// @notice new routeProxy contract with fee rebate to manage all route. It provides three methods to swap,
|
||
/// including mixSwap, multiSwap and externalSwap. Mixswap is for linear swap, which describes one token path
|
||
/// with one pool each time. Multiswap is a simplified version about 1inch, which describes one token path
|
||
/// with several pools each time. ExternalSwap is for other routers like 0x, 1inch and paraswap. Dodo and
|
||
/// front-end users could take certain route fee rebate from each swap. Wherein dodo will get a fixed percentage,
|
||
/// and front-end users could assign any proportion through function parameters.
|
||
/// @dev dependence: DODOApprove.sol / DODOApproveProxy.sol / IDODOAdapter.sol
|
||
/// In dodo's contract system, there is only one approve entrance DODOApprove.sol. DODOApprove manages DODOApproveProxy,
|
||
/// Any contract which needs claim user's tokens must be registered in DODOApproveProxy. They used in DODORouteProxy are
|
||
/// to manage user's token, all user's token must be claimed through DODOApproveProxy and DODOApprove
|
||
/// IDODOAdapter determine the interface of adapter, in which swap happened. There are different adapters for different
|
||
/// pools. Adapter addresses are parameters contructed off chain so they are loose coupling with routeProxy.
|
||
/// adapters have two interface functions. func sellBase(address to, address pool, bytes memory moreInfo) and func sellQuote(address to, address pool, bytes memory moreInfo)
|
||
|
||
contract DODOFeeRouteProxy is Ownable {
|
||
|
||
using UniversalERC20 for IERC20;
|
||
|
||
// ============ Storage ============
|
||
|
||
address constant _ETH_ADDRESS_ = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
|
||
address public immutable _WETH_;
|
||
// dodo appprove proxy address, the only entrance to get user's token
|
||
address public immutable _DODO_APPROVE_PROXY_;
|
||
// used in multiSwap for split, sum of pool weight must equal totalWeight
|
||
// in PoolInfo, pool weight has 8 bit, so totalWeight < 2**8
|
||
uint256 public totalWeight = 100;
|
||
// check safe safe for external call, add trusted external swap contract, 0x,1inch, paraswap
|
||
// only owner could manage
|
||
mapping(address => bool) public isWhiteListedContract;
|
||
// check safe for external approve, add trusted external swap approve contract, 0x, 1inch, paraswap
|
||
// only owner could manage
|
||
// Specially for 0x swap from eth, add zero address
|
||
mapping(address => bool) public isApproveWhiteListedContract;
|
||
|
||
// dodo route fee rate, unit is 10**18, default fee rate is 1.5 * 1e16 / 1e18 = 0.015 = 0.15%
|
||
uint256 public routeFeeRate = 1500000000000000;
|
||
// dodo route fee receiver
|
||
address public routeFeeReceiver;
|
||
|
||
struct PoolInfo {
|
||
// pool swap direciton, 0 is for sellBase, 1 is for sellQuote
|
||
uint256 direction;
|
||
// distinct transferFrom pool(like dodoV1) and transfer pool
|
||
// 1 is for transferFrom pool, pool call transferFrom function to get tokens from adapter
|
||
// 2 is for transfer pool, pool determine swapAmount through balanceOf(Token) - reserve
|
||
uint256 poolEdition;
|
||
// pool weight, actualWeight = weight/totalWeight, totalAmount * actualWeight = amount through this pool swap
|
||
uint256 weight;
|
||
// pool address
|
||
address pool;
|
||
// pool adapter, making actual swap call in corresponding adapter
|
||
address adapter;
|
||
// pool adapter's Info, record addtional infos(could be zero-bytes) needed by each pool adapter
|
||
bytes moreInfo;
|
||
}
|
||
|
||
// ============ Events ============
|
||
|
||
event OrderHistory(
|
||
address fromToken,
|
||
address toToken,
|
||
address sender,
|
||
uint256 fromAmount,
|
||
uint256 returnAmount
|
||
);
|
||
|
||
event PositiveSlippage(
|
||
address token,
|
||
uint256 amount
|
||
);
|
||
|
||
// ============ Modifiers ============
|
||
|
||
modifier judgeExpired(uint256 deadLine) {
|
||
require(deadLine >= block.timestamp, "DODORouteProxy: EXPIRED");
|
||
_;
|
||
}
|
||
|
||
fallback() external payable {}
|
||
|
||
receive() external payable {}
|
||
|
||
// ============ Constructor ============
|
||
|
||
constructor(address payable weth, address dodoApproveProxy, address feeReceiver) {
|
||
require(feeReceiver != address(0), "DODORouteProxy: feeReceiver invalid");
|
||
require(dodoApproveProxy != address(0), "DODORouteProxy: dodoApproveProxy invalid");
|
||
require(weth != address(0), "DODORouteProxy: weth address invalid");
|
||
|
||
_WETH_ = weth;
|
||
_DODO_APPROVE_PROXY_ = dodoApproveProxy;
|
||
routeFeeReceiver = feeReceiver;
|
||
}
|
||
|
||
// ============ Owner only ============
|
||
|
||
function addWhiteList(address contractAddr) public onlyOwner {
|
||
isWhiteListedContract[contractAddr] = true;
|
||
}
|
||
|
||
function removeWhiteList(address contractAddr) public onlyOwner {
|
||
isWhiteListedContract[contractAddr] = false;
|
||
}
|
||
|
||
function addApproveWhiteList(address contractAddr) public onlyOwner {
|
||
isApproveWhiteListedContract[contractAddr] = true;
|
||
}
|
||
|
||
function removeApproveWhiteList(address contractAddr) public onlyOwner {
|
||
isApproveWhiteListedContract[contractAddr] = false;
|
||
}
|
||
|
||
function changeRouteFeeRate(uint256 newFeeRate) public onlyOwner {
|
||
require(newFeeRate < 10**18, "DODORouteProxy: newFeeRate overflowed");
|
||
routeFeeRate = newFeeRate;
|
||
}
|
||
|
||
function changeRouteFeeReceiver(address newFeeReceiver) public onlyOwner {
|
||
require(newFeeReceiver != address(0), "DODORouteProxy: feeReceiver invalid");
|
||
routeFeeReceiver = newFeeReceiver;
|
||
}
|
||
|
||
function changeTotalWeight(uint256 newTotalWeight) public onlyOwner {
|
||
require(newTotalWeight < 2 ** 8, "DODORouteProxy: totalWeight overflowed");
|
||
totalWeight = newTotalWeight;
|
||
}
|
||
|
||
/// @notice used for emergency, generally there wouldn't be tokens left
|
||
function superWithdraw(address token) public onlyOwner {
|
||
if(token != _ETH_ADDRESS_) {
|
||
uint256 restAmount = IERC20(token).universalBalanceOf(address(this));
|
||
IERC20(token).universalTransfer(payable(routeFeeReceiver), restAmount);
|
||
} else {
|
||
uint256 restAmount = address(this).balance;
|
||
payable(routeFeeReceiver).transfer(restAmount);
|
||
}
|
||
}
|
||
|
||
// ============ Swap ============
|
||
|
||
|
||
/// @notice Call external black box contracts to finish a swap
|
||
/// @param approveTarget external swap approve address
|
||
/// @param swapTarget external swap address
|
||
/// @param feeData route fee info
|
||
/// @param callDataConcat external swap data, toAddress need to be routeProxy
|
||
/// specially when toToken is ETH, use WETH as external calldata's toToken
|
||
function externalSwap(
|
||
address fromToken,
|
||
address toToken,
|
||
address approveTarget,
|
||
address swapTarget,
|
||
uint256 fromTokenAmount,
|
||
uint256 expReturnAmount,
|
||
uint256 minReturnAmount,
|
||
bytes memory feeData,
|
||
bytes memory callDataConcat,
|
||
uint256 deadLine
|
||
) external payable judgeExpired(deadLine) returns (uint256 receiveAmount) {
|
||
require(isWhiteListedContract[swapTarget], "DODORouteProxy: Not Whitelist Contract");
|
||
require(isApproveWhiteListedContract[approveTarget], "DODORouteProxy: Not Whitelist Appprove Contract");
|
||
|
||
// transfer in fromToken
|
||
if (fromToken != _ETH_ADDRESS_) {
|
||
// approve if needed
|
||
if (approveTarget != address(0)) {
|
||
IERC20(fromToken).universalApproveMax(approveTarget, fromTokenAmount);
|
||
}
|
||
|
||
IDODOApproveProxy(_DODO_APPROVE_PROXY_).claimTokens(
|
||
fromToken,
|
||
msg.sender,
|
||
address(this),
|
||
fromTokenAmount
|
||
);
|
||
} else {
|
||
// value check
|
||
require(msg.value == fromTokenAmount, "DODORouteProxy: invalid ETH amount");
|
||
}
|
||
|
||
// swap
|
||
uint256 toTokenOriginBalance;
|
||
if(toToken != _ETH_ADDRESS_) {
|
||
toTokenOriginBalance = IERC20(toToken).universalBalanceOf(address(this));
|
||
} else {
|
||
toTokenOriginBalance = IERC20(_WETH_).universalBalanceOf(address(this));
|
||
}
|
||
|
||
{
|
||
uint256 valueToSend = fromToken == _ETH_ADDRESS_ ? fromTokenAmount : 0;
|
||
require(swapTarget != _DODO_APPROVE_PROXY_, "DODORouteProxy: Risk Target");
|
||
(bool success, bytes memory result) = swapTarget.call{
|
||
value: valueToSend
|
||
}(callDataConcat);
|
||
// revert with lowlevel info
|
||
if (success == false) {
|
||
assembly {
|
||
revert(add(result,32),mload(result))
|
||
}
|
||
}
|
||
}
|
||
|
||
// calculate toToken amount
|
||
if(toToken != _ETH_ADDRESS_) {
|
||
receiveAmount = IERC20(toToken).universalBalanceOf(address(this)) - (
|
||
toTokenOriginBalance
|
||
);
|
||
} else {
|
||
receiveAmount = IERC20(_WETH_).universalBalanceOf(address(this)) - (
|
||
toTokenOriginBalance
|
||
);
|
||
}
|
||
|
||
// distribute toToken
|
||
receiveAmount = _routeWithdraw(toToken, receiveAmount, feeData, expReturnAmount, minReturnAmount);
|
||
|
||
emit OrderHistory(fromToken, toToken, msg.sender, fromTokenAmount, receiveAmount);
|
||
}
|
||
|
||
/// @notice linear version, describes one token path with one pool each time
|
||
/// @param mixAdapters adapter address array, record each pool's interrelated adapter in order
|
||
/// @param mixPairs pool address array, record pool address of the whole route in order
|
||
/// @param assetTo asset Address(pool or proxy), describe pool adapter's receiver address. Specially assetTo[0] is deposit receiver before all
|
||
/// @param directions pool directions aggregation, one bit represent one pool direction, 0 means sellBase, 1 means sellQuote
|
||
/// @param moreInfos pool adapter's Info set, record addtional infos(could be zero-bytes) needed by each pool adapter, keeping order with adapters
|
||
/// @param feeData route fee info, bytes decode into broker and brokerFee, determine rebate proportion, brokerFee in [0, 1e18]
|
||
function mixSwap(
|
||
address fromToken,
|
||
address toToken,
|
||
uint256 fromTokenAmount,
|
||
uint256 expReturnAmount,
|
||
uint256 minReturnAmount,
|
||
address[] memory mixAdapters,
|
||
address[] memory mixPairs,
|
||
address[] memory assetTo,
|
||
uint256 directions,
|
||
bytes[] memory moreInfos,
|
||
bytes memory feeData,
|
||
uint256 deadLine
|
||
) external payable judgeExpired(deadLine) returns (uint256) {
|
||
require(mixPairs.length > 0, "DODORouteProxy: PAIRS_EMPTY");
|
||
require(mixPairs.length == mixAdapters.length, "DODORouteProxy: PAIR_ADAPTER_NOT_MATCH");
|
||
require(mixPairs.length == assetTo.length - 1, "DODORouteProxy: PAIR_ASSETTO_NOT_MATCH");
|
||
require(minReturnAmount > 0, "DODORouteProxy: RETURN_AMOUNT_ZERO");
|
||
|
||
address _fromToken = fromToken;
|
||
address _toToken = toToken;
|
||
uint256 _fromTokenAmount = fromTokenAmount;
|
||
uint256 _expReturnAmount = expReturnAmount;
|
||
uint256 _minReturnAmount = minReturnAmount;
|
||
address[] memory _mixAdapters = mixAdapters;
|
||
address[] memory _mixPairs = mixPairs;
|
||
address[] memory _assetTo = assetTo;
|
||
uint256 _directions = directions;
|
||
bytes[] memory _moreInfos = moreInfos;
|
||
bytes memory _feeData = feeData;
|
||
|
||
uint256 toTokenOriginBalance;
|
||
if(_toToken != _ETH_ADDRESS_) {
|
||
toTokenOriginBalance = IERC20(_toToken).universalBalanceOf(address(this));
|
||
} else {
|
||
toTokenOriginBalance = IERC20(_WETH_).universalBalanceOf(address(this));
|
||
}
|
||
|
||
// transfer in fromToken
|
||
bool isETH = _fromToken == _ETH_ADDRESS_;
|
||
_deposit(
|
||
msg.sender,
|
||
_assetTo[0],
|
||
_fromToken,
|
||
_fromTokenAmount,
|
||
isETH
|
||
);
|
||
|
||
// swap
|
||
for (uint256 i = 0; i < _mixPairs.length; i++) {
|
||
if (_directions & 1 == 0) {
|
||
IDODOAdapter(_mixAdapters[i]).sellBase(
|
||
_assetTo[i + 1],
|
||
_mixPairs[i],
|
||
_moreInfos[i]
|
||
);
|
||
} else {
|
||
IDODOAdapter(_mixAdapters[i]).sellQuote(
|
||
_assetTo[i + 1],
|
||
_mixPairs[i],
|
||
_moreInfos[i]
|
||
);
|
||
}
|
||
_directions = _directions >> 1;
|
||
}
|
||
|
||
// calculate toToken amount
|
||
uint256 receiveAmount;
|
||
if(_toToken != _ETH_ADDRESS_) {
|
||
receiveAmount = IERC20(_toToken).universalBalanceOf(address(this)) - (
|
||
toTokenOriginBalance
|
||
);
|
||
} else {
|
||
receiveAmount = IERC20(_WETH_).universalBalanceOf(address(this)) - (
|
||
toTokenOriginBalance
|
||
);
|
||
}
|
||
|
||
// distribute toToken
|
||
receiveAmount = _routeWithdraw(_toToken, receiveAmount, _feeData, _expReturnAmount, _minReturnAmount);
|
||
|
||
emit OrderHistory(_fromToken, _toToken, msg.sender, _fromTokenAmount, receiveAmount);
|
||
|
||
return receiveAmount;
|
||
}
|
||
|
||
/// @notice split version, describes one token path with several pools each time. Called one token pair with several pools "one split"
|
||
/// @param splitNumber record pool number in one split, determine sequence(poolInfo) array subscript in transverse. Begin with 0
|
||
/// for example, [0,1, 3], mean the first split has one(1 - 0) pool, the second split has 2 (3 - 1) pool
|
||
/// @param midToken middle token set, record token path in order.
|
||
/// Specially midToken[1] is WETH addresss when fromToken is ETH. Besides midToken[1] is also fromToken
|
||
/// Specially midToken[length - 2] is WETH address and midToken[length -1 ] is ETH address when toToken is ETH. Besides midToken[length -1]
|
||
/// is the last toToken and midToken[length - 2] is common second last middle token.
|
||
/// @param assetFrom asset Address(pool or proxy)describe pool adapter's receiver address. Specially assetFrom[0] is deposit receiver before all
|
||
/// @param sequence PoolInfo sequence, describe each pool's attributions, ordered by spiltNumber
|
||
/// @param feeData route fee info, bytes decode into broker and brokerFee, determine rebate proportion, brokerFee in [0, 1e18]
|
||
function dodoMutliSwap(
|
||
uint256 fromTokenAmount,
|
||
uint256 expReturnAmount,
|
||
uint256 minReturnAmount,
|
||
uint256[] memory splitNumber,
|
||
address[] memory midToken,
|
||
address[] memory assetFrom,
|
||
bytes[] memory sequence,
|
||
bytes memory feeData,
|
||
uint256 deadLine
|
||
) external payable judgeExpired(deadLine) returns (uint256 receiveAmount) {
|
||
address toToken = midToken[midToken.length - 1];
|
||
{
|
||
require(
|
||
assetFrom.length == splitNumber.length,
|
||
"DODORouteProxy: PAIR_ASSETTO_NOT_MATCH"
|
||
);
|
||
require(minReturnAmount > 0, "DODORouteProxy: RETURN_AMOUNT_ZERO");
|
||
uint256 _fromTokenAmount = fromTokenAmount;
|
||
address fromToken = midToken[0];
|
||
|
||
uint256 toTokenOriginBalance;
|
||
if(toToken != _ETH_ADDRESS_) {
|
||
toTokenOriginBalance = IERC20(toToken).universalBalanceOf(address(this));
|
||
} else {
|
||
toTokenOriginBalance = IERC20(_WETH_).universalBalanceOf(address(this));
|
||
}
|
||
|
||
// transfer in fromToken
|
||
_deposit(
|
||
msg.sender,
|
||
assetFrom[0],
|
||
fromToken,
|
||
_fromTokenAmount,
|
||
fromToken == _ETH_ADDRESS_
|
||
);
|
||
|
||
// swap
|
||
_multiSwap(midToken, splitNumber, sequence, assetFrom);
|
||
|
||
// calculate toToken amount
|
||
if(toToken != _ETH_ADDRESS_) {
|
||
receiveAmount = IERC20(toToken).universalBalanceOf(address(this)) - (
|
||
toTokenOriginBalance
|
||
);
|
||
} else {
|
||
receiveAmount = IERC20(_WETH_).universalBalanceOf(address(this)) - (
|
||
toTokenOriginBalance
|
||
);
|
||
}
|
||
}
|
||
// distribute toToken
|
||
receiveAmount = _routeWithdraw(toToken, receiveAmount, feeData, expReturnAmount, minReturnAmount);
|
||
|
||
emit OrderHistory(
|
||
midToken[0], //fromToken
|
||
midToken[midToken.length - 1], //toToken
|
||
msg.sender,
|
||
fromTokenAmount,
|
||
receiveAmount
|
||
);
|
||
}
|
||
|
||
//====================== internal =======================
|
||
/// @notice multiSwap process
|
||
function _multiSwap(
|
||
address[] memory midToken,
|
||
uint256[] memory splitNumber,
|
||
bytes[] memory swapSequence,
|
||
address[] memory assetFrom
|
||
) internal {
|
||
for (uint256 i = 1; i < splitNumber.length; i++) {
|
||
// begin one split(one token pair with one or more pools)
|
||
// define midtoken address, ETH -> WETH address
|
||
uint256 curTotalAmount = IERC20(midToken[i]).tokenBalanceOf(assetFrom[i - 1]);
|
||
uint256 curTotalWeight = totalWeight;
|
||
|
||
// split amount into all pools if needed, transverse all pool in this split
|
||
for (uint256 j = splitNumber[i - 1]; j < splitNumber[i]; j++) {
|
||
PoolInfo memory curPoolInfo;
|
||
{
|
||
(address pool, address adapter, uint256 mixPara, bytes memory moreInfo) = abi
|
||
.decode(swapSequence[j], (address, address, uint256, bytes));
|
||
|
||
curPoolInfo.direction = mixPara >> 17;
|
||
curPoolInfo.weight = (0xffff & mixPara) >> 9;
|
||
curPoolInfo.poolEdition = (0xff & mixPara);
|
||
curPoolInfo.pool = pool;
|
||
curPoolInfo.adapter = adapter;
|
||
curPoolInfo.moreInfo = moreInfo;
|
||
}
|
||
|
||
// assetFrom[i - 1] is routeProxy when there are more than one pools in this split
|
||
if (assetFrom[i - 1] == address(this)) {
|
||
uint256 curAmount = curTotalAmount * curPoolInfo.weight / curTotalWeight;
|
||
// last spilt check
|
||
if(j == splitNumber[i] - 1) {
|
||
curAmount = IERC20(midToken[i]).tokenBalanceOf(address(this));
|
||
}
|
||
|
||
if (curPoolInfo.poolEdition == 1) {
|
||
//For using transferFrom pool (like dodoV1, Curve), pool call transferFrom function to get tokens from adapter
|
||
SafeERC20.safeTransfer(IERC20(midToken[i]), curPoolInfo.adapter, curAmount);
|
||
} else {
|
||
//For using transfer pool (like dodoV2), pool determine swapAmount through balanceOf(Token) - reserve
|
||
SafeERC20.safeTransfer(IERC20(midToken[i]), curPoolInfo.pool, curAmount);
|
||
}
|
||
}
|
||
|
||
if (curPoolInfo.direction == 0) {
|
||
IDODOAdapter(curPoolInfo.adapter).sellBase(
|
||
assetFrom[i],
|
||
curPoolInfo.pool,
|
||
curPoolInfo.moreInfo
|
||
);
|
||
} else {
|
||
IDODOAdapter(curPoolInfo.adapter).sellQuote(
|
||
assetFrom[i],
|
||
curPoolInfo.pool,
|
||
curPoolInfo.moreInfo
|
||
);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// @notice before the first pool swap, contract call _deposit to get ERC20 token through DODOApprove/transfer ETH to WETH
|
||
function _deposit(
|
||
address from,
|
||
address to,
|
||
address token,
|
||
uint256 amount,
|
||
bool isETH
|
||
) internal {
|
||
if (isETH) {
|
||
if (amount > 0) {
|
||
require(msg.value == amount, "ETH_VALUE_WRONG");
|
||
IWETH(_WETH_).deposit{value: amount}();
|
||
if (to != address(this)) SafeERC20.safeTransfer(IERC20(_WETH_), to, amount);
|
||
}
|
||
} else {
|
||
IDODOApproveProxy(_DODO_APPROVE_PROXY_).claimTokens(token, from, to, amount);
|
||
}
|
||
}
|
||
|
||
/// @notice after all swaps, transfer tokens to original receiver(user) and distribute fees to DODO and broker
|
||
/// Specially when toToken is ETH, distribute WETH
|
||
function _routeWithdraw(
|
||
address toToken,
|
||
uint256 receiveAmount,
|
||
bytes memory feeData,
|
||
uint256 expReturnAmount,
|
||
uint256 minReturnAmount
|
||
) internal returns(uint256 userReceiveAmount) {
|
||
address originToToken = toToken;
|
||
if(toToken == _ETH_ADDRESS_) {
|
||
toToken = _WETH_;
|
||
}
|
||
(address broker, uint256 brokerFeeRate) = abi.decode(feeData, (address, uint256));
|
||
require(brokerFeeRate < 10**18, "DODORouteProxy: brokerFeeRate overflowed");
|
||
|
||
uint256 routeFee = DecimalMath.mulFloor(receiveAmount, routeFeeRate);
|
||
|
||
uint256 brokerFee = DecimalMath.mulFloor(receiveAmount, brokerFeeRate);
|
||
IERC20(toToken).universalTransfer(payable(broker), brokerFee);
|
||
|
||
receiveAmount = receiveAmount - routeFee - brokerFee;
|
||
require(receiveAmount >= minReturnAmount, "DODORouteProxy: Return amount is not enough");
|
||
|
||
if (receiveAmount > expReturnAmount) {
|
||
uint256 amount = receiveAmount - expReturnAmount;
|
||
IERC20(toToken).universalTransfer(payable(routeFeeReceiver), amount + routeFee);
|
||
receiveAmount = expReturnAmount;
|
||
|
||
emit PositiveSlippage(toToken, amount);
|
||
} else {
|
||
IERC20(toToken).universalTransfer(payable(routeFeeReceiver), routeFee);
|
||
}
|
||
|
||
if (originToToken == _ETH_ADDRESS_) {
|
||
IWETH(_WETH_).withdraw(receiveAmount);
|
||
payable(msg.sender).transfer(receiveAmount);
|
||
} else {
|
||
IERC20(toToken).universalTransfer(payable(msg.sender), receiveAmount);
|
||
}
|
||
|
||
userReceiveAmount = receiveAmount;
|
||
}
|
||
}
|