Overview
ETH Balance
0 ETH
Eth Value
$0.00More Info
Private Name Tags
ContractCreator
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Latest 25 internal transactions (View All)
Advanced mode:
Parent Transaction Hash | Block |
From
|
To
|
|||
---|---|---|---|---|---|---|
13520849 | 1196 days ago | 409.18650273 ETH | ||||
13520849 | 1196 days ago | 409.18650273 ETH | ||||
13484343 | 1202 days ago | 13.46819651 ETH | ||||
13484343 | 1202 days ago | 13.46819651 ETH | ||||
13481214 | 1202 days ago | 17.46481842 ETH | ||||
13481214 | 1202 days ago | 17.46481842 ETH | ||||
13476695 | 1203 days ago | 17 ETH | ||||
13476695 | 1203 days ago | 17 ETH | ||||
13476421 | 1203 days ago | 17.50335547 ETH | ||||
13476421 | 1203 days ago | 17.50335547 ETH | ||||
13472920 | 1204 days ago | 207.07245346 ETH | ||||
13472920 | 1204 days ago | 207.07245346 ETH | ||||
13467873 | 1204 days ago | 20.20994776 ETH | ||||
13467873 | 1204 days ago | 20.20994776 ETH | ||||
13461886 | 1205 days ago | 17.1876455 ETH | ||||
13461886 | 1205 days ago | 17.1876455 ETH | ||||
13457861 | 1206 days ago | 3.34159269 ETH | ||||
13457861 | 1206 days ago | 3.34159269 ETH | ||||
13430485 | 1210 days ago | 7.27688847 ETH | ||||
13430485 | 1210 days ago | 7.27688847 ETH | ||||
13429765 | 1210 days ago | 6 ETH | ||||
13429765 | 1210 days ago | 6 ETH | ||||
13424429 | 1211 days ago | 8 ETH | ||||
13424429 | 1211 days ago | 8 ETH | ||||
13416037 | 1213 days ago | 30.77677144 ETH |
Loading...
Loading
Contract Source Code Verified (Exact Match)
Contract Name:
LoanShifterReceiver
Compiler Version
v0.6.12+commit.27d51765
Contract Source Code (Solidity Standard Json-Input format)
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../auth/AdminAuth.sol"; import "../utils/FlashLoanReceiverBase.sol"; import "../interfaces/DSProxyInterface.sol"; import "../exchangeV3/DFSExchangeCore.sol"; import "./ShifterRegistry.sol"; import "./LoanShifterTaker.sol"; /// @title LoanShifterReceiver Recevies the Aave flash loan and calls actions through users DSProxy contract LoanShifterReceiver is DFSExchangeCore, FlashLoanReceiverBase, AdminAuth { address public constant DISCOUNT_ADDR = 0x1b14E8D511c9A4395425314f849bD737BAF8208F; ILendingPoolAddressesProvider public LENDING_POOL_ADDRESS_PROVIDER = ILendingPoolAddressesProvider(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8); address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; uint public constant SERVICE_FEE = 400; // 0.25% Fee ShifterRegistry public constant shifterRegistry = ShifterRegistry(0x597C52281b31B9d949a9D8fEbA08F7A2530a965e); struct ParamData { bytes proxyData1; bytes proxyData2; address proxy; address debtAddr; uint8 protocol1; uint8 protocol2; uint8 swapType; } constructor() public FlashLoanReceiverBase(LENDING_POOL_ADDRESS_PROVIDER) {} function executeOperation( address _reserve, uint256 _amount, uint256 _fee, bytes calldata _params ) external override { // Format the call data for DSProxy (ParamData memory paramData, ExchangeData memory exchangeData) = packFunctionCall(_amount, _fee, _params); address protocolAddr1 = shifterRegistry.getAddr(getNameByProtocol(paramData.protocol1)); address protocolAddr2 = shifterRegistry.getAddr(getNameByProtocol(paramData.protocol2)); // Send Flash loan amount to DSProxy sendTokenToProxy(payable(paramData.proxy), _reserve, _amount); // Execute the Close/Change debt operation DSProxyInterface(paramData.proxy).execute(protocolAddr1, paramData.proxyData1); exchangeData.dfsFeeDivider = SERVICE_FEE; exchangeData.user = DSProxyInterface(paramData.proxy).owner(); if (paramData.swapType == 1) { uint256 amount = exchangeData.srcAmount; if (exchangeData.srcAddr != exchangeData.destAddr) { // COLL_SWAP (, amount) = _sell(exchangeData); } sendTokenAndEthToProxy(payable(paramData.proxy), exchangeData.destAddr, amount); } else if (paramData.swapType == 2) { // DEBT_SWAP if (exchangeData.srcAddr != exchangeData.destAddr) { exchangeData.destAmount = (_amount + _fee); _buy(exchangeData); // Send extra to DSProxy sendTokenToProxy( payable(paramData.proxy), exchangeData.srcAddr, ERC20(exchangeData.srcAddr).balanceOf(address(this)) ); } } else { // NO_SWAP just send tokens to proxy sendTokenAndEthToProxy( payable(paramData.proxy), exchangeData.srcAddr, getBalance(exchangeData.srcAddr) ); } // Execute the Open operation DSProxyInterface(paramData.proxy).execute(protocolAddr2, paramData.proxyData2); // Repay FL transferFundsBackToPoolInternal(_reserve, _amount.add(_fee)); // if there is some eth left (0x fee), return it to user if (address(this).balance > 0) { tx.origin.transfer(address(this).balance); } } function packFunctionCall( uint256 _amount, uint256 _fee, bytes memory _params ) internal pure returns (ParamData memory paramData, ExchangeData memory exchangeData) { LoanShifterTaker.LoanShiftData memory shiftData; address proxy; (shiftData, exchangeData, proxy) = abi.decode( _params, (LoanShifterTaker.LoanShiftData, ExchangeData, address) ); bytes memory proxyData1; bytes memory proxyData2; uint256 openDebtAmount = (_amount + _fee); if (shiftData.fromProtocol == LoanShifterTaker.Protocols.MCD) { // MAKER FROM proxyData1 = abi.encodeWithSignature( "close(uint256,address,uint256,uint256)", shiftData.id1, shiftData.addrLoan1, _amount, shiftData.collAmount ); } else if (shiftData.fromProtocol == LoanShifterTaker.Protocols.COMPOUND) { // COMPOUND FROM if (shiftData.swapType == LoanShifterTaker.SwapType.DEBT_SWAP) { // DEBT_SWAP proxyData1 = abi.encodeWithSignature( "changeDebt(address,address,uint256,uint256)", shiftData.debtAddr1, shiftData.debtAddr2, _amount, exchangeData.srcAmount ); } else { proxyData1 = abi.encodeWithSignature( "close(address,address,uint256,uint256)", shiftData.addrLoan1, shiftData.debtAddr1, shiftData.collAmount, shiftData.debtAmount ); } } if (shiftData.toProtocol == LoanShifterTaker.Protocols.MCD) { // MAKER TO proxyData2 = abi.encodeWithSignature( "open(uint256,address,uint256)", shiftData.id2, shiftData.addrLoan2, openDebtAmount ); } else if (shiftData.toProtocol == LoanShifterTaker.Protocols.COMPOUND) { // COMPOUND TO if (shiftData.swapType == LoanShifterTaker.SwapType.DEBT_SWAP) { // DEBT_SWAP proxyData2 = abi.encodeWithSignature("repayAll(address)", shiftData.debtAddr2); } else { proxyData2 = abi.encodeWithSignature( "open(address,address,uint256)", shiftData.addrLoan2, shiftData.debtAddr2, openDebtAmount ); } } paramData = ParamData({ proxyData1: proxyData1, proxyData2: proxyData2, proxy: proxy, debtAddr: shiftData.debtAddr1, protocol1: uint8(shiftData.fromProtocol), protocol2: uint8(shiftData.toProtocol), swapType: uint8(shiftData.swapType) }); } function sendTokenAndEthToProxy( address payable _proxy, address _reserve, uint256 _amount ) internal { if (_reserve != ETH_ADDRESS) { ERC20(_reserve).safeTransfer(_proxy, _amount); } _proxy.transfer(address(this).balance); } function sendTokenToProxy( address payable _proxy, address _reserve, uint256 _amount ) internal { if (_reserve != ETH_ADDRESS) { ERC20(_reserve).safeTransfer(_proxy, _amount); } else { _proxy.transfer(address(this).balance); } } function getNameByProtocol(uint8 _proto) internal pure returns (string memory) { if (_proto == 0) { return "MCD_SHIFTER"; } else if (_proto == 1) { return "COMP_SHIFTER"; } } receive() external payable override(FlashLoanReceiverBase, DFSExchangeCore) {} }
pragma solidity ^0.6.0; import "../utils/GasBurner.sol"; import "../interfaces/IAToken.sol"; import "../interfaces/ILendingPool.sol"; import "../interfaces/ILendingPoolAddressesProvider.sol"; import "../utils/SafeERC20.sol"; /// @title Basic compound interactions through the DSProxy contract AaveBasicProxy is GasBurner { using SafeERC20 for ERC20; address public constant ETH_ADDR = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant AAVE_LENDING_POOL_ADDRESSES = 0x24a42fD28C976A61Df5D00D0599C34c4f90748c8; uint16 public constant AAVE_REFERRAL_CODE = 64; /// @notice User deposits tokens to the Aave protocol /// @dev User needs to approve the DSProxy to pull the _tokenAddr tokens /// @param _tokenAddr The address of the token to be deposited /// @param _amount Amount of tokens to be deposited function deposit(address _tokenAddr, uint256 _amount) public burnGas(5) payable { address lendingPoolCore = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPoolCore(); address lendingPool = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPool(); uint ethValue = _amount; if (_tokenAddr != ETH_ADDR) { ERC20(_tokenAddr).safeTransferFrom(msg.sender, address(this), _amount); approveToken(_tokenAddr, lendingPoolCore); ethValue = 0; } ILendingPool(lendingPool).deposit{value: ethValue}(_tokenAddr, _amount, AAVE_REFERRAL_CODE); setUserUseReserveAsCollateralIfNeeded(_tokenAddr); } /// @notice User withdraws tokens from the Aave protocol /// @param _tokenAddr The address of the token to be withdrawn /// @param _aTokenAddr ATokens to be withdrawn /// @param _amount Amount of tokens to be withdrawn /// @param _wholeAmount If true we will take the whole amount on chain function withdraw(address _tokenAddr, address _aTokenAddr, uint256 _amount, bool _wholeAmount) public burnGas(8) { uint256 amount = _wholeAmount ? ERC20(_aTokenAddr).balanceOf(address(this)) : _amount; IAToken(_aTokenAddr).redeem(amount); withdrawTokens(_tokenAddr); } /// @notice User borrows tokens to the Aave protocol /// @param _tokenAddr The address of the token to be borrowed /// @param _amount Amount of tokens to be borrowed /// @param _type Send 1 for stable rate and 2 for variable rate function borrow(address _tokenAddr, uint256 _amount, uint256 _type) public burnGas(8) { address lendingPool = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPool(); ILendingPool(lendingPool).borrow(_tokenAddr, _amount, _type, AAVE_REFERRAL_CODE); withdrawTokens(_tokenAddr); } /// @dev User needs to approve the DSProxy to pull the _tokenAddr tokens /// @notice User paybacks tokens to the Aave protocol /// @param _tokenAddr The address of the token to be paybacked /// @param _aTokenAddr ATokens to be paybacked /// @param _amount Amount of tokens to be payed back /// @param _wholeDebt If true the _amount will be set to the whole amount of the debt function payback(address _tokenAddr, address _aTokenAddr, uint256 _amount, bool _wholeDebt) public burnGas(3) payable { address lendingPoolCore = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPoolCore(); address lendingPool = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPool(); uint256 amount = _amount; (,uint256 borrowAmount,,,,,uint256 originationFee,,,) = ILendingPool(lendingPool).getUserReserveData(_tokenAddr, address(this)); if (_wholeDebt) { amount = borrowAmount + originationFee; } if (_tokenAddr != ETH_ADDR) { ERC20(_tokenAddr).safeTransferFrom(msg.sender, address(this), amount); approveToken(_tokenAddr, lendingPoolCore); } ILendingPool(lendingPool).repay{value: msg.value}(_tokenAddr, amount, payable(address(this))); withdrawTokens(_tokenAddr); } /// @dev User needs to approve the DSProxy to pull the _tokenAddr tokens /// @notice User paybacks tokens to the Aave protocol /// @param _tokenAddr The address of the token to be paybacked /// @param _aTokenAddr ATokens to be paybacked /// @param _amount Amount of tokens to be payed back /// @param _wholeDebt If true the _amount will be set to the whole amount of the debt function paybackOnBehalf(address _tokenAddr, address _aTokenAddr, uint256 _amount, bool _wholeDebt, address payable _onBehalf) public burnGas(3) payable { address lendingPoolCore = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPoolCore(); address lendingPool = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPool(); uint256 amount = _amount; (,uint256 borrowAmount,,,,,uint256 originationFee,,,) = ILendingPool(lendingPool).getUserReserveData(_tokenAddr, _onBehalf); if (_wholeDebt) { amount = borrowAmount + originationFee; } if (_tokenAddr != ETH_ADDR) { ERC20(_tokenAddr).safeTransferFrom(msg.sender, address(this), amount); approveToken(_tokenAddr, lendingPoolCore); } ILendingPool(lendingPool).repay{value: msg.value}(_tokenAddr, amount, _onBehalf); withdrawTokens(_tokenAddr); } /// @notice Helper method to withdraw tokens from the DSProxy /// @param _tokenAddr Address of the token to be withdrawn function withdrawTokens(address _tokenAddr) public { uint256 amount = _tokenAddr == ETH_ADDR ? address(this).balance : ERC20(_tokenAddr).balanceOf(address(this)); if (amount > 0) { if (_tokenAddr != ETH_ADDR) { ERC20(_tokenAddr).safeTransfer(msg.sender, amount); } else { msg.sender.transfer(amount); } } } /// @notice Approves token contract to pull underlying tokens from the DSProxy /// @param _tokenAddr Token we are trying to approve /// @param _caller Address which will gain the approval function approveToken(address _tokenAddr, address _caller) internal { if (_tokenAddr != ETH_ADDR) { ERC20(_tokenAddr).safeApprove(_caller, uint256(-1)); } } function setUserUseReserveAsCollateralIfNeeded(address _tokenAddr) public { address lendingPool = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPool(); (,,,,,,,,,bool collateralEnabled) = ILendingPool(lendingPool).getUserReserveData(_tokenAddr, address(this)); if (!collateralEnabled) { ILendingPool(lendingPool).setUserUseReserveAsCollateral(_tokenAddr, true); } } function setUserUseReserveAsCollateral(address _tokenAddr, bool _true) public { address lendingPool = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPool(); ILendingPool(lendingPool).setUserUseReserveAsCollateral(_tokenAddr, _true); } function swapBorrowRateMode(address _reserve) public { address lendingPool = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPool(); ILendingPool(lendingPool).swapBorrowRateMode(_reserve); } }
pragma solidity ^0.6.0; import "../interfaces/GasTokenInterface.sol"; contract GasBurner { // solhint-disable-next-line const-name-snakecase GasTokenInterface public constant gasToken = GasTokenInterface(0x0000000000b3F879cb30FE243b4Dfee438691c04); modifier burnGas(uint _amount) { if (gasToken.balanceOf(address(this)) >= _amount) { gasToken.free(_amount); } _; } }
pragma solidity ^0.6.0; abstract contract IAToken { function redeem(uint256 _amount) external virtual; function balanceOf(address _owner) external virtual view returns (uint256 balance); }
pragma solidity ^0.6.0; abstract contract ILendingPool { function flashLoan( address payable _receiver, address _reserve, uint _amount, bytes calldata _params) external virtual; function deposit(address _reserve, uint256 _amount, uint16 _referralCode) external virtual payable; function setUserUseReserveAsCollateral(address _reserve, bool _useAsCollateral) external virtual; function borrow(address _reserve, uint256 _amount, uint256 _interestRateMode, uint16 _referralCode) external virtual; function repay( address _reserve, uint256 _amount, address payable _onBehalfOf) external virtual payable; function swapBorrowRateMode(address _reserve) external virtual; function getReserves() external virtual view returns(address[] memory); /// @param _reserve underlying token address function getReserveData(address _reserve) external virtual view returns ( uint256 totalLiquidity, // reserve total liquidity uint256 availableLiquidity, // reserve available liquidity for borrowing uint256 totalBorrowsStable, // total amount of outstanding borrows at Stable rate uint256 totalBorrowsVariable, // total amount of outstanding borrows at Variable rate uint256 liquidityRate, // current deposit APY of the reserve for depositors, in Ray units. uint256 variableBorrowRate, // current variable rate APY of the reserve pool, in Ray units. uint256 stableBorrowRate, // current stable rate APY of the reserve pool, in Ray units. uint256 averageStableBorrowRate, // current average stable borrow rate uint256 utilizationRate, // expressed as total borrows/total liquidity. uint256 liquidityIndex, // cumulative liquidity index uint256 variableBorrowIndex, // cumulative variable borrow index address aTokenAddress, // aTokens contract address for the specific _reserve uint40 lastUpdateTimestamp // timestamp of the last update of reserve data ); /// @param _user users address function getUserAccountData(address _user) external virtual view returns ( uint256 totalLiquidityETH, // user aggregated deposits across all the reserves. In Wei uint256 totalCollateralETH, // user aggregated collateral across all the reserves. In Wei uint256 totalBorrowsETH, // user aggregated outstanding borrows across all the reserves. In Wei uint256 totalFeesETH, // user aggregated current outstanding fees in ETH. In Wei uint256 availableBorrowsETH, // user available amount to borrow in ETH uint256 currentLiquidationThreshold, // user current average liquidation threshold across all the collaterals deposited uint256 ltv, // user average Loan-to-Value between all the collaterals uint256 healthFactor // user current Health Factor ); /// @param _reserve underlying token address /// @param _user users address function getUserReserveData(address _reserve, address _user) external virtual view returns ( uint256 currentATokenBalance, // user current reserve aToken balance uint256 currentBorrowBalance, // user current reserve outstanding borrow balance uint256 principalBorrowBalance, // user balance of borrowed asset uint256 borrowRateMode, // user borrow rate mode either Stable or Variable uint256 borrowRate, // user current borrow rate APY uint256 liquidityRate, // user current earn rate on _reserve uint256 originationFee, // user outstanding loan origination fee uint256 variableBorrowIndex, // user variable cumulative index uint256 lastUpdateTimestamp, // Timestamp of the last data update bool usageAsCollateralEnabled // Whether the user's current reserve is enabled as a collateral ); function getReserveConfigurationData(address _reserve) external virtual view returns ( uint256 ltv, uint256 liquidationThreshold, uint256 liquidationBonus, address rateStrategyAddress, bool usageAsCollateralEnabled, bool borrowingEnabled, bool stableBorrowRateEnabled, bool isActive ); // ------------------ LendingPoolCoreData ------------------------ function getReserveATokenAddress(address _reserve) public virtual view returns (address); function getReserveConfiguration(address _reserve) external virtual view returns (uint256, uint256, uint256, bool); function getUserUnderlyingAssetBalance(address _reserve, address _user) public virtual view returns (uint256); function getReserveCurrentLiquidityRate(address _reserve) public virtual view returns (uint256); function getReserveCurrentVariableBorrowRate(address _reserve) public virtual view returns (uint256); function getReserveCurrentStableBorrowRate(address _reserve) public virtual view returns (uint256); function getReserveTotalLiquidity(address _reserve) public virtual view returns (uint256); function getReserveAvailableLiquidity(address _reserve) public virtual view returns (uint256); function getReserveTotalBorrowsVariable(address _reserve) public virtual view returns (uint256); function getReserveTotalBorrowsStable(address _reserve) public virtual view returns (uint256); // ---------------- LendingPoolDataProvider --------------------- function calculateUserGlobalData(address _user) public virtual view returns ( uint256 totalLiquidityBalanceETH, uint256 totalCollateralBalanceETH, uint256 totalBorrowBalanceETH, uint256 totalFeesETH, uint256 currentLtv, uint256 currentLiquidationThreshold, uint256 healthFactor, bool healthFactorBelowThreshold ); }
pragma solidity ^0.6.0; /** @title ILendingPoolAddressesProvider interface @notice provides the interface to fetch the LendingPoolCore address */ abstract contract ILendingPoolAddressesProvider { function getLendingPool() public virtual view returns (address); function getLendingPoolCore() public virtual view returns (address payable); function getLendingPoolConfigurator() public virtual view returns (address); function getLendingPoolDataProvider() public virtual view returns (address); function getLendingPoolParametersProvider() public virtual view returns (address); function getTokenDistributor() public virtual view returns (address); function getFeeProvider() public virtual view returns (address); function getLendingPoolLiquidationManager() public virtual view returns (address); function getLendingPoolManager() public virtual view returns (address); function getPriceOracle() public virtual view returns (address); function getLendingRateOracle() public virtual view returns (address); }
pragma solidity ^0.6.0; import "../interfaces/ERC20.sol"; import "./Address.sol"; import "./SafeMath.sol"; library SafeERC20 { using SafeMath for uint256; using Address for address; function safeTransfer(ERC20 token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } function safeTransferFrom(ERC20 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. */ function safeApprove(ERC20 token, address spender, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0)); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); } function safeIncreaseAllowance(ERC20 token, address spender, uint256 value) internal { uint256 newAllowance = token.allowance(address(this), spender).add(value); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } function safeDecreaseAllowance(ERC20 token, address spender, uint256 value) internal { uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } function _callOptionalReturn(ERC20 token, bytes memory data) private { bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); if (returndata.length > 0) { // Return data is optional // solhint-disable-next-line max-line-length require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); } } }
pragma solidity ^0.6.0; import "./ERC20.sol"; abstract contract GasTokenInterface is ERC20 { function free(uint256 value) public virtual returns (bool success); function freeUpTo(uint256 value) public virtual returns (uint256 freed); function freeFrom(address from, uint256 value) public virtual returns (bool success); function freeFromUpTo(address from, uint256 value) public virtual returns (uint256 freed); }
pragma solidity ^0.6.0; interface ERC20 { function totalSupply() external view returns (uint256 supply); function balanceOf(address _owner) external view returns (uint256 balance); function transfer(address _to, uint256 _value) external returns (bool success); function transferFrom(address _from, address _to, uint256 _value) external returns (bool success); function approve(address _spender, uint256 _value) external returns (bool success); function allowance(address _owner, address _spender) external view returns (uint256 remaining); function decimals() external view returns (uint256 digits); event Approval(address indexed _owner, address indexed _spender, uint256 _value); }
pragma solidity ^0.6.0; library Address { function isContract(address account) internal view returns (bool) { // According to EIP-1052, 0x0 is the value returned for not-yet created accounts // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned // for accounts without code, i.e. `keccak256('')` bytes32 codehash; bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; // solhint-disable-next-line no-inline-assembly assembly { codehash := extcodehash(account) } return (codehash != accountHash && codehash != 0x0); } function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); // solhint-disable-next-line avoid-low-level-calls, avoid-call-value (bool success, ) = recipient.call{ value: amount }(""); require(success, "Address: unable to send value, recipient may have reverted"); } function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCall(target, data, "Address: low-level call failed"); } function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { return _functionCallWithValue(target, data, 0, errorMessage); } 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"); } 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"); return _functionCallWithValue(target, data, value, errorMessage); } function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) { require(isContract(target), "Address: call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.call{ value: weiValue }(data); 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 // solhint-disable-next-line no-inline-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } } }
pragma solidity ^0.6.0; library SafeMath { function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } function sub(uint256 a, uint256 b) internal pure returns (uint256) { return sub(a, b, "SafeMath: subtraction overflow"); } function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b <= a, errorMessage); uint256 c = a - b; return c; } function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; } function div(uint256 a, uint256 b) internal pure returns (uint256) { return div(a, b, "SafeMath: division by zero"); } function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b > 0, errorMessage); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } function mod(uint256 a, uint256 b) internal pure returns (uint256) { return mod(a, b, "SafeMath: modulo by zero"); } function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b != 0, errorMessage); return a % b; } }
pragma solidity ^0.6.0; import "../interfaces/DSProxyInterface.sol"; import "./SafeERC20.sol"; /// @title Pulls a specified amount of tokens from the EOA owner account to the proxy contract PullTokensProxy { using SafeERC20 for ERC20; /// @notice Pulls a token from the proxyOwner -> proxy /// @dev Proxy owner must first give approve to the proxy address /// @param _tokenAddr Address of the ERC20 token /// @param _amount Amount of tokens which will be transfered to the proxy function pullTokens(address _tokenAddr, uint _amount) public { address proxyOwner = DSProxyInterface(address(this)).owner(); ERC20(_tokenAddr).safeTransferFrom(proxyOwner, address(this), _amount); } }
pragma solidity ^0.6.0; abstract contract DSProxyInterface { /// Truffle wont compile if this isn't commented // function execute(bytes memory _code, bytes memory _data) // public virtual // payable // returns (address, bytes32); function execute(address _target, bytes memory _data) public virtual payable returns (bytes32); function setCache(address _cacheAddr) public virtual payable returns (bool); function owner() public virtual returns (address); }
pragma solidity ^0.6.0; import "../auth/Auth.sol"; import "../interfaces/DSProxyInterface.sol"; // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. contract DFSProxy is Auth { string public constant NAME = "DFSProxy"; string public constant VERSION = "v0.1"; mapping(address => mapping(uint => bool)) public nonces; // --- EIP712 niceties --- bytes32 public DOMAIN_SEPARATOR; bytes32 public constant PERMIT_TYPEHASH = keccak256("callProxy(address _user,address _proxy,address _contract,bytes _txData,uint256 _nonce)"); constructor(uint256 chainId_) public { DOMAIN_SEPARATOR = keccak256(abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes(NAME)), keccak256(bytes(VERSION)), chainId_, address(this) )); } function callProxy(address _user, address _proxy, address _contract, bytes calldata _txData, uint256 _nonce, uint8 _v, bytes32 _r, bytes32 _s) external payable onlyAuthorized { bytes32 digest = keccak256(abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR, keccak256(abi.encode(PERMIT_TYPEHASH, _user, _proxy, _contract, _txData, _nonce)) )); // user must be proxy owner require(DSProxyInterface(_proxy).owner() == _user); require(_user == ecrecover(digest, _v, _r, _s), "DFSProxy/user-not-valid"); require(!nonces[_user][_nonce], "DFSProxy/invalid-nonce"); nonces[_user][_nonce] = true; DSProxyInterface(_proxy).execute{value: msg.value}(_contract, _txData); } }
pragma solidity ^0.6.0; import "./AdminAuth.sol"; contract Auth is AdminAuth { bool public ALL_AUTHORIZED = false; mapping(address => bool) public authorized; modifier onlyAuthorized() { require(ALL_AUTHORIZED || authorized[msg.sender]); _; } constructor() public { authorized[msg.sender] = true; } function setAuthorized(address _user, bool _approved) public onlyOwner { authorized[_user] = _approved; } function setAllAuthorized(bool _authorized) public onlyOwner { ALL_AUTHORIZED = _authorized; } }
pragma solidity ^0.6.0; import "../utils/SafeERC20.sol"; contract AdminAuth { using SafeERC20 for ERC20; address public owner; address public admin; modifier onlyOwner() { require(owner == msg.sender); _; } modifier onlyAdmin() { require(admin == msg.sender); _; } constructor() public { owner = msg.sender; admin = 0x25eFA336886C74eA8E282ac466BdCd0199f85BB9; } /// @notice Admin is set by owner first time, after that admin is super role and has permission to change owner /// @param _admin Address of multisig that becomes admin function setAdminByOwner(address _admin) public { require(msg.sender == owner); require(admin == address(0)); admin = _admin; } /// @notice Admin is able to set new admin /// @param _admin Address of multisig that becomes new admin function setAdminByAdmin(address _admin) public { require(msg.sender == admin); admin = _admin; } /// @notice Admin is able to change owner /// @param _owner Address of new owner function setOwnerByAdmin(address _owner) public { require(msg.sender == admin); owner = _owner; } /// @notice Destroy the contract function kill() public onlyOwner { selfdestruct(payable(owner)); } /// @notice withdraw stuck funds function withdrawStuckFunds(address _token, uint _amount) public onlyOwner { if (_token == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) { payable(owner).transfer(_amount); } else { ERC20(_token).safeTransfer(owner, _amount); } } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../interfaces/ILendingPool.sol"; import "../interfaces/CTokenInterface.sol"; import "../interfaces/ILoanShifter.sol"; import "../interfaces/DSProxyInterface.sol"; import "../interfaces/Vat.sol"; import "../interfaces/Manager.sol"; import "../interfaces/IMCDSubscriptions.sol"; import "../interfaces/ICompoundSubscriptions.sol"; import "../auth/AdminAuth.sol"; import "../auth/ProxyPermission.sol"; import "../exchangeV3/DFSExchangeData.sol"; import "./ShifterRegistry.sol"; import "../utils/GasBurner.sol"; import "../loggers/DefisaverLogger.sol"; /// @title LoanShifterTaker Entry point for using the shifting operation contract LoanShifterTaker is AdminAuth, ProxyPermission, GasBurner { ILendingPool public constant lendingPool = ILendingPool(0x398eC7346DcD622eDc5ae82352F02bE94C62d119); address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant CETH_ADDRESS = 0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5; address public constant DAI_ADDRESS = 0x6B175474E89094C44Da98b954EedeAC495271d0F; address public constant MCD_SUB_ADDRESS = 0xC45d4f6B6bf41b6EdAA58B01c4298B8d9078269a; address public constant COMPOUND_SUB_ADDRESS = 0x52015EFFD577E08f498a0CCc11905925D58D6207; address public constant MANAGER_ADDRESS = 0x5ef30b9986345249bc32d8928B7ee64DE9435E39; address public constant DEFISAVER_LOGGER = 0x5c55B921f590a89C1Ebe84dF170E655a82b62126; Manager public constant manager = Manager(MANAGER_ADDRESS); ShifterRegistry public constant shifterRegistry = ShifterRegistry(0x597C52281b31B9d949a9D8fEbA08F7A2530a965e); enum Protocols { MCD, COMPOUND } enum SwapType { NO_SWAP, COLL_SWAP, DEBT_SWAP } enum Unsub { NO_UNSUB, FIRST_UNSUB, SECOND_UNSUB, BOTH_UNSUB } struct LoanShiftData { Protocols fromProtocol; Protocols toProtocol; SwapType swapType; Unsub unsub; bool wholeDebt; uint collAmount; uint debtAmount; address debtAddr1; address debtAddr2; address addrLoan1; address addrLoan2; uint id1; uint id2; } /// @notice Main entry point, it will move or transform a loan /// @dev Called through DSProxy function moveLoan( DFSExchangeData.ExchangeData memory _exchangeData, LoanShiftData memory _loanShift ) public payable burnGas(20) { if (_isSameTypeVaults(_loanShift)) { _forkVault(_loanShift); logEvent(_exchangeData, _loanShift); return; } _callCloseAndOpen(_exchangeData, _loanShift); } //////////////////////// INTERNAL FUNCTIONS ////////////////////////// function _callCloseAndOpen( DFSExchangeData.ExchangeData memory _exchangeData, LoanShiftData memory _loanShift ) internal { address protoAddr = shifterRegistry.getAddr(getNameByProtocol(uint8(_loanShift.fromProtocol))); if (_loanShift.wholeDebt) { _loanShift.debtAmount = ILoanShifter(protoAddr).getLoanAmount(_loanShift.id1, _loanShift.debtAddr1); } // encode data bytes memory paramsData = abi.encode(_loanShift, _exchangeData, address(this)); address payable loanShifterReceiverAddr = payable(shifterRegistry.getAddr("LOAN_SHIFTER_RECEIVER")); loanShifterReceiverAddr.transfer(address(this).balance); // call FL givePermission(loanShifterReceiverAddr); lendingPool.flashLoan(loanShifterReceiverAddr, getLoanAddr(_loanShift.debtAddr1, _loanShift.fromProtocol), _loanShift.debtAmount, paramsData); removePermission(loanShifterReceiverAddr); unsubFromAutomation( _loanShift.unsub, _loanShift.id1, _loanShift.id2, _loanShift.fromProtocol, _loanShift.toProtocol ); logEvent(_exchangeData, _loanShift); } function _forkVault(LoanShiftData memory _loanShift) internal { // Create new Vault to move to if (_loanShift.id2 == 0) { _loanShift.id2 = manager.open(manager.ilks(_loanShift.id1), address(this)); } if (_loanShift.wholeDebt) { manager.shift(_loanShift.id1, _loanShift.id2); } } function _isSameTypeVaults(LoanShiftData memory _loanShift) internal pure returns (bool) { return _loanShift.fromProtocol == Protocols.MCD && _loanShift.toProtocol == Protocols.MCD && _loanShift.addrLoan1 == _loanShift.addrLoan2; } function getNameByProtocol(uint8 _proto) internal pure returns (string memory) { if (_proto == 0) { return "MCD_SHIFTER"; } else if (_proto == 1) { return "COMP_SHIFTER"; } } function getLoanAddr(address _address, Protocols _fromProtocol) internal returns (address) { if (_fromProtocol == Protocols.COMPOUND) { return getUnderlyingAddr(_address); } else if (_fromProtocol == Protocols.MCD) { return DAI_ADDRESS; } else { return address(0); } } function getUnderlyingAddr(address _cTokenAddress) internal returns (address) { if (_cTokenAddress == CETH_ADDRESS) { return ETH_ADDRESS; } else { return CTokenInterface(_cTokenAddress).underlying(); } } function logEvent( DFSExchangeData.ExchangeData memory _exchangeData, LoanShiftData memory _loanShift ) internal { address srcAddr = _exchangeData.srcAddr; address destAddr = _exchangeData.destAddr; uint collAmount = _exchangeData.srcAmount; uint debtAmount = _exchangeData.destAmount; if (_loanShift.swapType == SwapType.NO_SWAP) { srcAddr = _loanShift.addrLoan1; destAddr = _loanShift.debtAddr1; collAmount = _loanShift.collAmount; debtAmount = _loanShift.debtAmount; } DefisaverLogger(DEFISAVER_LOGGER) .Log(address(this), msg.sender, "LoanShifter", abi.encode( _loanShift.fromProtocol, _loanShift.toProtocol, _loanShift.swapType, srcAddr, destAddr, collAmount, debtAmount )); } function unsubFromAutomation(Unsub _unsub, uint _cdp1, uint _cdp2, Protocols _from, Protocols _to) internal { if (_unsub != Unsub.NO_UNSUB) { if (_unsub == Unsub.FIRST_UNSUB || _unsub == Unsub.BOTH_UNSUB) { unsubscribe(_cdp1, _from); } if (_unsub == Unsub.SECOND_UNSUB || _unsub == Unsub.BOTH_UNSUB) { unsubscribe(_cdp2, _to); } } } function unsubscribe(uint _cdpId, Protocols _protocol) internal { if (_cdpId != 0 && _protocol == Protocols.MCD) { IMCDSubscriptions(MCD_SUB_ADDRESS).unsubscribe(_cdpId); } if (_protocol == Protocols.COMPOUND) { ICompoundSubscriptions(COMPOUND_SUB_ADDRESS).unsubscribe(); } } }
pragma solidity ^0.6.0; import "./ERC20.sol"; abstract contract CTokenInterface is ERC20 { function mint(uint256 mintAmount) external virtual returns (uint256); // function mint() external virtual payable; function accrueInterest() public virtual returns (uint); function redeem(uint256 redeemTokens) external virtual returns (uint256); function redeemUnderlying(uint256 redeemAmount) external virtual returns (uint256); function borrow(uint256 borrowAmount) external virtual returns (uint256); function borrowIndex() public view virtual returns (uint); function borrowBalanceStored(address) public view virtual returns(uint); function repayBorrow(uint256 repayAmount) external virtual returns (uint256); function repayBorrow() external virtual payable; function repayBorrowBehalf(address borrower, uint256 repayAmount) external virtual returns (uint256); function repayBorrowBehalf(address borrower) external virtual payable; function liquidateBorrow(address borrower, uint256 repayAmount, address cTokenCollateral) external virtual returns (uint256); function liquidateBorrow(address borrower, address cTokenCollateral) external virtual payable; function exchangeRateCurrent() external virtual returns (uint256); function supplyRatePerBlock() external virtual returns (uint256); function borrowRatePerBlock() external virtual returns (uint256); function totalReserves() external virtual returns (uint256); function reserveFactorMantissa() external virtual returns (uint256); function borrowBalanceCurrent(address account) external virtual returns (uint256); function totalBorrowsCurrent() external virtual returns (uint256); function getCash() external virtual returns (uint256); function balanceOfUnderlying(address owner) external virtual returns (uint256); function underlying() external virtual returns (address); function getAccountSnapshot(address account) external virtual view returns (uint, uint, uint, uint); }
pragma solidity ^0.6.0; abstract contract ILoanShifter { function getLoanAmount(uint, address) public virtual returns (uint); function getUnderlyingAsset(address _addr) public view virtual returns (address); }
pragma solidity ^0.6.0; abstract contract Vat { struct Urn { uint256 ink; // Locked Collateral [wad] uint256 art; // Normalised Debt [wad] } struct Ilk { uint256 Art; // Total Normalised Debt [wad] uint256 rate; // Accumulated Rates [ray] uint256 spot; // Price with Safety Margin [ray] uint256 line; // Debt Ceiling [rad] uint256 dust; // Urn Debt Floor [rad] } mapping (bytes32 => mapping (address => Urn )) public urns; mapping (bytes32 => Ilk) public ilks; mapping (bytes32 => mapping (address => uint)) public gem; // [wad] function can(address, address) virtual public view returns (uint); function dai(address) virtual public view returns (uint); function frob(bytes32, address, address, address, int, int) virtual public; function hope(address) virtual public; function move(address, address, uint) virtual public; function fork(bytes32, address, address, int, int) virtual public; }
pragma solidity ^0.6.0; abstract contract Manager { function last(address) virtual public returns (uint); function cdpCan(address, uint, address) virtual public view returns (uint); function ilks(uint) virtual public view returns (bytes32); function owns(uint) virtual public view returns (address); function urns(uint) virtual public view returns (address); function vat() virtual public view returns (address); function open(bytes32, address) virtual public returns (uint); function give(uint, address) virtual public; function cdpAllow(uint, address, uint) virtual public; function urnAllow(address, uint) virtual public; function frob(uint, int, int) virtual public; function flux(uint, address, uint) virtual public; function move(uint, address, uint) virtual public; function exit(address, uint, address, uint) virtual public; function quit(uint, address) virtual public; function enter(address, uint) virtual public; function shift(uint, uint) virtual public; }
pragma solidity ^0.6.0; abstract contract IMCDSubscriptions { function unsubscribe(uint256 _cdpId) external virtual ; function subscribersPos(uint256 _cdpId) external virtual returns (uint256, bool); }
pragma solidity ^0.6.0; abstract contract ICompoundSubscriptions { function unsubscribe() external virtual ; }
pragma solidity ^0.6.0; import "../DS/DSGuard.sol"; import "../DS/DSAuth.sol"; contract ProxyPermission { address public constant FACTORY_ADDRESS = 0x5a15566417e6C1c9546523066500bDDBc53F88C7; /// @notice Called in the context of DSProxy to authorize an address /// @param _contractAddr Address which will be authorized function givePermission(address _contractAddr) public { address currAuthority = address(DSAuth(address(this)).authority()); DSGuard guard = DSGuard(currAuthority); if (currAuthority == address(0)) { guard = DSGuardFactory(FACTORY_ADDRESS).newGuard(); DSAuth(address(this)).setAuthority(DSAuthority(address(guard))); } guard.permit(_contractAddr, address(this), bytes4(keccak256("execute(address,bytes)"))); } /// @notice Called in the context of DSProxy to remove authority of an address /// @param _contractAddr Auth address which will be removed from authority list function removePermission(address _contractAddr) public { address currAuthority = address(DSAuth(address(this)).authority()); // if there is no authority, that means that contract doesn't have permission if (currAuthority == address(0)) { return; } DSGuard guard = DSGuard(currAuthority); guard.forbid(_contractAddr, address(this), bytes4(keccak256("execute(address,bytes)"))); } function proxyOwner() internal returns(address) { return DSAuth(address(this)).owner(); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; contract DFSExchangeData { // first is empty to keep the legacy order in place enum ExchangeType { _, OASIS, KYBER, UNISWAP, ZEROX } enum ActionType { SELL, BUY } struct OffchainData { address wrapper; address exchangeAddr; address allowanceTarget; uint256 price; uint256 protocolFee; bytes callData; } struct ExchangeData { address srcAddr; address destAddr; uint256 srcAmount; uint256 destAmount; uint256 minPrice; uint256 dfsFeeDivider; // service fee divider address user; // user to check special fee address wrapper; bytes wrapperData; OffchainData offchainData; } function packExchangeData(ExchangeData memory _exData) public pure returns(bytes memory) { return abi.encode(_exData); } function unpackExchangeData(bytes memory _data) public pure returns(ExchangeData memory _exData) { _exData = abi.decode(_data, (ExchangeData)); } }
pragma solidity ^0.6.0; import "../auth/AdminAuth.sol"; contract ShifterRegistry is AdminAuth { mapping (string => address) public contractAddresses; bool public finalized; function changeContractAddr(string memory _contractName, address _protoAddr) public onlyOwner { require(!finalized); contractAddresses[_contractName] = _protoAddr; } function lock() public onlyOwner { finalized = true; } function getAddr(string memory _contractName) public view returns (address contractAddr) { contractAddr = contractAddresses[_contractName]; require(contractAddr != address(0), "No contract address registred"); } }
pragma solidity ^0.6.0; contract DefisaverLogger { event LogEvent( address indexed contractAddress, address indexed caller, string indexed logName, bytes data ); // solhint-disable-next-line func-name-mixedcase function Log(address _contract, address _caller, string memory _logName, bytes memory _data) public { emit LogEvent(_contract, _caller, _logName, _data); } }
pragma solidity ^0.6.0; abstract contract DSGuard { function canCall(address src_, address dst_, bytes4 sig) public view virtual returns (bool); function permit(bytes32 src, bytes32 dst, bytes32 sig) public virtual; function forbid(bytes32 src, bytes32 dst, bytes32 sig) public virtual; function permit(address src, address dst, bytes32 sig) public virtual; function forbid(address src, address dst, bytes32 sig) public virtual; } abstract contract DSGuardFactory { function newGuard() public virtual returns (DSGuard guard); }
pragma solidity ^0.6.0; import "./DSAuthority.sol"; contract DSAuthEvents { event LogSetAuthority(address indexed authority); event LogSetOwner(address indexed owner); } contract DSAuth is DSAuthEvents { DSAuthority public authority; address public owner; constructor() public { owner = msg.sender; emit LogSetOwner(msg.sender); } function setOwner(address owner_) public auth { owner = owner_; emit LogSetOwner(owner); } function setAuthority(DSAuthority authority_) public auth { authority = authority_; emit LogSetAuthority(address(authority)); } modifier auth { require(isAuthorized(msg.sender, msg.sig)); _; } function isAuthorized(address src, bytes4 sig) internal view returns (bool) { if (src == address(this)) { return true; } else if (src == owner) { return true; } else if (authority == DSAuthority(0)) { return false; } else { return authority.canCall(src, address(this), sig); } } }
pragma solidity ^0.6.0; abstract contract DSAuthority { function canCall(address src, address dst, bytes4 sig) public virtual view returns (bool); }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../loggers/DefisaverLogger.sol"; import "../../utils/Discount.sol"; import "../../interfaces/reflexer/IOracleRelayer.sol"; import "../../interfaces/reflexer/ITaxCollector.sol"; import "../../interfaces/reflexer/ICoinJoin.sol"; import "./RAISaverProxyHelper.sol"; import "../../utils/BotRegistry.sol"; import "../../exchangeV3/DFSExchangeCore.sol"; /// @title Implements Boost and Repay for Reflexer Safes contract RAISaverProxy is DFSExchangeCore, RAISaverProxyHelper { uint public constant MANUAL_SERVICE_FEE = 400; // 0.25% Fee uint public constant AUTOMATIC_SERVICE_FEE = 333; // 0.3% Fee bytes32 public constant ETH_COLL_TYPE = 0x4554482d41000000000000000000000000000000000000000000000000000000; address public constant SAFE_ENGINE_ADDRESS = 0xCC88a9d330da1133Df3A7bD823B95e52511A6962; address public constant ORACLE_RELAYER_ADDRESS = 0x4ed9C0dCa0479bC64d8f4EB3007126D5791f7851; address public constant RAI_JOIN_ADDRESS = 0x0A5653CCa4DB1B6E265F47CAf6969e64f1CFdC45; address public constant TAX_COLLECTOR_ADDRESS = 0xcDB05aEda142a1B0D6044C09C64e4226c1a281EB; address public constant RAI_ADDRESS = 0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919; address public constant BOT_REGISTRY_ADDRESS = 0x637726f8b08a7ABE3aE3aCaB01A80E2d8ddeF77B; ISAFEEngine public constant safeEngine = ISAFEEngine(SAFE_ENGINE_ADDRESS); ICoinJoin public constant raiJoin = ICoinJoin(RAI_JOIN_ADDRESS); IOracleRelayer public constant oracleRelayer = IOracleRelayer(ORACLE_RELAYER_ADDRESS); DefisaverLogger public constant logger = DefisaverLogger(0x5c55B921f590a89C1Ebe84dF170E655a82b62126); /// @notice Repay - draws collateral, converts to Rai and repays the debt /// @dev Must be called by the DSProxy contract that owns the Safe function repay( ExchangeData memory _exchangeData, uint _safeId, uint _gasCost, address _joinAddr, ManagerType _managerType ) public payable { address managerAddr = getManagerAddr(_managerType); address user = getOwner(ISAFEManager(managerAddr), _safeId); bytes32 ilk = ISAFEManager(managerAddr).collateralTypes(_safeId); drawCollateral(managerAddr, _safeId, _joinAddr, _exchangeData.srcAmount, true); _exchangeData.user = user; _exchangeData.dfsFeeDivider = isAutomation() ? AUTOMATIC_SERVICE_FEE : MANUAL_SERVICE_FEE; (, uint raiAmount) = _sell(_exchangeData); raiAmount -= takeFee(_gasCost, raiAmount); paybackDebt(managerAddr, _safeId, ilk, raiAmount, user); // if there is some eth left (0x fee), return it to user if (address(this).balance > 0) { tx.origin.transfer(address(this).balance); } logger.Log(address(this), msg.sender, "RAIRepay", abi.encode(_safeId, user, _exchangeData.srcAmount, raiAmount)); } /// @notice Boost - draws Rai, converts to collateral and adds to Safe /// @dev Must be called by the DSProxy contract that owns the Safe function boost( ExchangeData memory _exchangeData, uint _safeId, uint _gasCost, address _joinAddr, ManagerType _managerType ) public payable { address managerAddr = getManagerAddr(_managerType); address user = getOwner(ISAFEManager(managerAddr), _safeId); bytes32 ilk = ISAFEManager(managerAddr).collateralTypes(_safeId); uint raiDrawn = drawRai(managerAddr, _safeId, ilk, _exchangeData.srcAmount); _exchangeData.user = user; _exchangeData.dfsFeeDivider = isAutomation() ? AUTOMATIC_SERVICE_FEE : MANUAL_SERVICE_FEE; _exchangeData.srcAmount = raiDrawn - takeFee(_gasCost, raiDrawn); (, uint swapedColl) = _sell(_exchangeData); addCollateral(managerAddr, _safeId, _joinAddr, swapedColl, true); // if there is some eth left (0x fee), return it to user if (address(this).balance > 0) { tx.origin.transfer(address(this).balance); } logger.Log(address(this), msg.sender, "RAIBoost", abi.encode(_safeId, user, _exchangeData.srcAmount, swapedColl)); } /// @notice Draws Rai from the Safe /// @dev If _raiAmount is bigger than max available we'll draw max /// @param _managerAddr Address of the Safe Manager /// @param _safeId Id of the Safe /// @param _collType Coll type of the Safe /// @param _raiAmount Amount of Rai to draw function drawRai(address _managerAddr, uint _safeId, bytes32 _collType, uint _raiAmount) internal returns (uint) { uint rate = ITaxCollector(TAX_COLLECTOR_ADDRESS).taxSingle(_collType); uint raiVatBalance = safeEngine.coinBalance(ISAFEManager(_managerAddr).safes(_safeId)); uint maxAmount = getMaxDebt(_managerAddr, _safeId, _collType); if (_raiAmount >= maxAmount) { _raiAmount = sub(maxAmount, 1); } ISAFEManager(_managerAddr).modifySAFECollateralization(_safeId, int(0), normalizeDrawAmount(_raiAmount, rate, raiVatBalance)); ISAFEManager(_managerAddr).transferInternalCoins(_safeId, address(this), toRad(_raiAmount)); if (safeEngine.safeRights(address(this), address(RAI_JOIN_ADDRESS)) == 0) { safeEngine.approveSAFEModification(RAI_JOIN_ADDRESS); } ICoinJoin(RAI_JOIN_ADDRESS).exit(address(this), _raiAmount); return _raiAmount; } /// @notice Adds collateral to the Safe /// @param _managerAddr Address of the Safe Manager /// @param _safeId Id of the Safe /// @param _joinAddr Address of the join contract for the Safe collateral /// @param _amount Amount of collateral to add /// @param _toWeth Should we convert to Weth function addCollateral(address _managerAddr, uint _safeId, address _joinAddr, uint _amount, bool _toWeth) internal { int convertAmount = 0; if (isEthJoinAddr(_joinAddr) && _toWeth) { TokenInterface(IBasicTokenAdapters(_joinAddr).collateral()).deposit{value: _amount}(); convertAmount = toPositiveInt(_amount); } else { convertAmount = toPositiveInt(convertTo18(_joinAddr, _amount)); } ERC20(address(IBasicTokenAdapters(_joinAddr).collateral())).safeApprove(_joinAddr, _amount); IBasicTokenAdapters(_joinAddr).join(address(this), _amount); safeEngine.modifySAFECollateralization( ISAFEManager(_managerAddr).collateralTypes(_safeId), ISAFEManager(_managerAddr).safes(_safeId), address(this), address(this), convertAmount, 0 ); } /// @notice Draws collateral and returns it to DSProxy /// @param _managerAddr Address of the Safe Manager /// @dev If _amount is bigger than max available we'll draw max /// @param _safeId Id of the Safe /// @param _joinAddr Address of the join contract for the Safe collateral /// @param _amount Amount of collateral to draw /// @param _toEth Boolean if we should unwrap Ether function drawCollateral(address _managerAddr, uint _safeId, address _joinAddr, uint _amount, bool _toEth) internal returns (uint) { uint frobAmount = _amount; if (IBasicTokenAdapters(_joinAddr).decimals() != 18) { frobAmount = _amount * (10 ** (18 - IBasicTokenAdapters(_joinAddr).decimals())); } ISAFEManager(_managerAddr).modifySAFECollateralization(_safeId, -toPositiveInt(frobAmount), 0); ISAFEManager(_managerAddr).transferCollateral(_safeId, address(this), frobAmount); IBasicTokenAdapters(_joinAddr).exit(address(this), _amount); if (isEthJoinAddr(_joinAddr) && _toEth) { TokenInterface(IBasicTokenAdapters(_joinAddr).collateral()).withdraw(_amount); // Weth -> Eth } return _amount; } /// @notice Paybacks Rai debt /// @param _managerAddr Address of the Safe Manager /// @dev If the _raiAmount is bigger than the whole debt, returns extra Rai /// @param _safeId Id of the Safe /// @param _collType Coll type of the Safe /// @param _raiAmount Amount of Rai to payback /// @param _owner Address that owns the DSProxy that owns the Safe function paybackDebt(address _managerAddr, uint _safeId, bytes32 _collType, uint _raiAmount, address _owner) internal { address urn = ISAFEManager(_managerAddr).safes(_safeId); uint wholeDebt = getAllDebt(SAFE_ENGINE_ADDRESS, urn, urn, _collType); if (_raiAmount > wholeDebt) { ERC20(RAI_ADDRESS).transfer(_owner, sub(_raiAmount, wholeDebt)); _raiAmount = wholeDebt; } if (ERC20(RAI_ADDRESS).allowance(address(this), RAI_JOIN_ADDRESS) == 0) { ERC20(RAI_ADDRESS).approve(RAI_JOIN_ADDRESS, uint(-1)); } raiJoin.join(urn, _raiAmount); int paybackAmnt = _getRepaidDeltaDebt(SAFE_ENGINE_ADDRESS, ISAFEEngine(safeEngine).coinBalance(urn), urn, _collType); ISAFEManager(_managerAddr).modifySAFECollateralization(_safeId, 0, paybackAmnt); } /// @notice Gets the maximum amount of collateral available to draw /// @param _managerAddr Address of the Safe Manager /// @param _safeId Id of the Safe /// @param _collType Coll type of the Safe /// @param _joinAddr Joind address of collateral /// @dev Substracts 1% to aviod rounding error later on function getMaxCollateral(address _managerAddr, uint _safeId, bytes32 _collType, address _joinAddr) public view returns (uint) { (uint collateral, uint debt) = getSafeInfo(ISAFEManager(_managerAddr), _safeId, _collType); (, , uint256 safetyPrice, , , ) = ISAFEEngine(SAFE_ENGINE_ADDRESS).collateralTypes(_collType); uint maxCollateral = sub(collateral, wmul(wdiv(RAY, safetyPrice), debt)); uint normalizeMaxCollateral = maxCollateral / (10 ** (18 - IBasicTokenAdapters(_joinAddr).decimals())); // take one percent due to precision issues return normalizeMaxCollateral * 99 / 100; } /// @notice Gets the maximum amount of debt available to generate /// @param _managerAddr Address of the Safe Manager /// @param _safeId Id of the Safe /// @param _collType Coll type of the Safe /// @dev Substracts 10 wei to aviod rounding error later on function getMaxDebt( address _managerAddr, uint256 _safeId, bytes32 _collType ) public view virtual returns (uint256) { (uint256 collateral, uint256 debt) = getSafeInfo(ISAFEManager(_managerAddr), _safeId, _collType); (, , uint256 safetyPrice, , , ) = ISAFEEngine(SAFE_ENGINE_ADDRESS).collateralTypes(_collType); return sub(sub(rmul(collateral, safetyPrice), debt), 10); } function getPrice(bytes32 _collType) public returns (uint256) { (, uint256 safetyCRatio) = IOracleRelayer(ORACLE_RELAYER_ADDRESS).collateralTypes(_collType); (, , uint256 safetyPrice, , , ) = ISAFEEngine(SAFE_ENGINE_ADDRESS).collateralTypes(_collType); uint256 redemptionPrice = IOracleRelayer(ORACLE_RELAYER_ADDRESS).redemptionPrice(); return rmul(rmul(safetyPrice, redemptionPrice), safetyCRatio); } function isAutomation() internal view returns(bool) { return BotRegistry(BOT_REGISTRY_ADDRESS).botList(tx.origin); } function takeFee(uint256 _gasCost, uint _amount) internal returns(uint) { if (_gasCost > 0) { uint ethRaiPrice = getPrice(ETH_COLL_TYPE); uint feeAmount = rmul(_gasCost, ethRaiPrice); if (feeAmount > _amount / 5) { feeAmount = _amount / 5; } address walletAddr = _feeRecipient.getFeeAddr(); ERC20(RAI_ADDRESS).transfer(walletAddr, feeAmount); return feeAmount; } return 0; } }
pragma solidity ^0.6.0; contract Discount { address public owner; mapping(address => CustomServiceFee) public serviceFees; uint256 constant MAX_SERVICE_FEE = 400; struct CustomServiceFee { bool active; uint256 amount; } constructor() public { owner = msg.sender; } function isCustomFeeSet(address _user) public view returns (bool) { return serviceFees[_user].active; } function getCustomServiceFee(address _user) public view returns (uint256) { return serviceFees[_user].amount; } function setServiceFee(address _user, uint256 _fee) public { require(msg.sender == owner, "Only owner"); require(_fee >= MAX_SERVICE_FEE || _fee == 0); serviceFees[_user] = CustomServiceFee({active: true, amount: _fee}); } function disableServiceFee(address _user) public { require(msg.sender == owner, "Only owner"); serviceFees[_user] = CustomServiceFee({active: false, amount: 0}); } }
pragma solidity ^0.6.0; abstract contract IOracleRelayer { struct CollateralType { address orcl; uint256 safetyCRatio; } mapping (bytes32 => CollateralType) public collateralTypes; function redemptionPrice() public virtual returns (uint256); uint256 public redemptionRate; }
pragma solidity ^0.6.0; abstract contract ITaxCollector { struct CollateralType { uint256 stabilityFee; uint256 updateTime; } mapping (bytes32 => CollateralType) public collateralTypes; function taxSingle(bytes32) public virtual returns (uint); }
pragma solidity ^0.6.0; abstract contract ICoinJoin { uint256 public decimals; function join(address account, uint256 wad) external virtual; function exit(address account, uint256 wad) external virtual; }
pragma solidity ^0.6.0; import "../../DS/DSMath.sol"; import "../../DS/DSProxy.sol"; import "../../interfaces/reflexer/IBasicTokenAdapters.sol"; import "../../interfaces/reflexer/ISAFEManager.sol"; import "../../interfaces/reflexer/ISAFEEngine.sol"; import "../../interfaces/reflexer/ITaxCollector.sol"; /// @title Helper methods for RAISaverProxy contract RAISaverProxyHelper is DSMath { enum ManagerType { RAI } /// @notice Returns a normalized debt _amount based on the current rate /// @param _amount Amount of dai to be normalized /// @param _rate Current rate of the stability fee /// @param _daiVatBalance Balance od Dai in the Vat for that Safe function normalizeDrawAmount(uint _amount, uint _rate, uint _daiVatBalance) internal pure returns (int dart) { if (_daiVatBalance < mul(_amount, RAY)) { dart = toPositiveInt(sub(mul(_amount, RAY), _daiVatBalance) / _rate); dart = mul(uint(dart), _rate) < mul(_amount, RAY) ? dart + 1 : dart; } } /// @notice Converts a number to Rad percision /// @param _wad The input number in wad percision function toRad(uint _wad) internal pure returns (uint) { return mul(_wad, 10 ** 27); } /// @notice Converts a number to 18 decimal percision /// @param _joinAddr Join address of the collateral /// @param _amount Number to be converted function convertTo18(address _joinAddr, uint256 _amount) internal view returns (uint256) { return mul(_amount, 10 ** (18 - IBasicTokenAdapters(_joinAddr).decimals())); } /// @notice Converts a uint to int and checks if positive /// @param _x Number to be converted function toPositiveInt(uint _x) internal pure returns (int y) { y = int(_x); require(y >= 0, "int-overflow"); } /// @notice Gets Dai amount in Vat which can be added to Safe /// @param _safeEngine Address of Vat contract /// @param _urn Urn of the Safe /// @param _collType CollType of the Safe function normalizePaybackAmount(address _safeEngine, address _urn, bytes32 _collType) internal view returns (int amount) { uint dai = ISAFEEngine(_safeEngine).coinBalance(_urn); (, uint rate,,,,) = ISAFEEngine(_safeEngine).collateralTypes(_collType); (, uint art) = ISAFEEngine(_safeEngine).safes(_collType, _urn); amount = toPositiveInt(dai / rate); amount = uint(amount) <= art ? - amount : - toPositiveInt(art); } /// @notice Gets delta debt generated (Total Safe debt minus available safeHandler COIN balance) /// @param safeEngine address /// @param taxCollector address /// @param safeHandler address /// @param collateralType bytes32 /// @return deltaDebt function _getGeneratedDeltaDebt( address safeEngine, address taxCollector, address safeHandler, bytes32 collateralType, uint wad ) internal returns (int deltaDebt) { // Updates stability fee rate uint rate = ITaxCollector(taxCollector).taxSingle(collateralType); require(rate > 0, "invalid-collateral-type"); // Gets COIN balance of the handler in the safeEngine uint coin = ISAFEEngine(safeEngine).coinBalance(safeHandler); // If there was already enough COIN in the safeEngine balance, just exits it without adding more debt if (coin < mul(wad, RAY)) { // Calculates the needed deltaDebt so together with the existing coins in the safeEngine is enough to exit wad amount of COIN tokens deltaDebt = toPositiveInt(sub(mul(wad, RAY), coin) / rate); // This is neeeded due lack of precision. It might need to sum an extra deltaDebt wei (for the given COIN wad amount) deltaDebt = mul(uint(deltaDebt), rate) < mul(wad, RAY) ? deltaDebt + 1 : deltaDebt; } } function _getRepaidDeltaDebt( address safeEngine, uint coin, address safe, bytes32 collateralType ) internal view returns (int deltaDebt) { // Gets actual rate from the safeEngine (, uint rate,,,,) = ISAFEEngine(safeEngine).collateralTypes(collateralType); require(rate > 0, "invalid-collateral-type"); // Gets actual generatedDebt value of the safe (, uint generatedDebt) = ISAFEEngine(safeEngine).safes(collateralType, safe); // Uses the whole coin balance in the safeEngine to reduce the debt deltaDebt = toPositiveInt(coin / rate); // Checks the calculated deltaDebt is not higher than safe.generatedDebt (total debt), otherwise uses its value deltaDebt = uint(deltaDebt) <= generatedDebt ? - deltaDebt : - toPositiveInt(generatedDebt); } /// @notice Gets the whole debt of the Safe /// @param _safeEngine Address of Vat contract /// @param _usr Address of the Dai holder /// @param _urn Urn of the Safe /// @param _collType CollType of the Safe function getAllDebt(address _safeEngine, address _usr, address _urn, bytes32 _collType) internal view returns (uint daiAmount) { (, uint rate,,,,) = ISAFEEngine(_safeEngine).collateralTypes(_collType); (, uint art) = ISAFEEngine(_safeEngine).safes(_collType, _urn); uint dai = ISAFEEngine(_safeEngine).coinBalance(_usr); uint rad = sub(mul(art, rate), dai); daiAmount = rad / RAY; daiAmount = mul(daiAmount, RAY) < rad ? daiAmount + 1 : daiAmount; } /// @notice Gets the token address from the Join contract /// @param _joinAddr Address of the Join contract function getCollateralAddr(address _joinAddr) internal view returns (address) { return address(IBasicTokenAdapters(_joinAddr).collateral()); } /// @notice Checks if the join address is one of the Ether coll. types /// @param _joinAddr Join address to check function isEthJoinAddr(address _joinAddr) internal view returns (bool) { // if it's dai_join_addr don't check gem() it will fail if (_joinAddr == 0x0A5653CCa4DB1B6E265F47CAf6969e64f1CFdC45) return false; // if coll is weth it's and eth type coll if (address(IBasicTokenAdapters(_joinAddr).collateral()) == 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) { return true; } return false; } /// @notice Gets Safe info (collateral, debt) /// @param _manager Manager contract /// @param _safeId Id of the Safe /// @param _collType CollType of the Safe function getSafeInfo(ISAFEManager _manager, uint _safeId, bytes32 _collType) public view returns (uint, uint) { address vat = _manager.safeEngine(); address urn = _manager.safes(_safeId); (uint collateral, uint debt) = ISAFEEngine(vat).safes(_collType, urn); (,uint rate,,,,) = ISAFEEngine(vat).collateralTypes(_collType); return (collateral, rmul(debt, rate)); } /// @notice Address that owns the DSProxy that owns the Safe /// @param _manager Manager contract /// @param _safeId Id of the Safe function getOwner(ISAFEManager _manager, uint _safeId) public view returns (address) { DSProxy proxy = DSProxy(uint160(_manager.ownsSAFE(_safeId))); return proxy.owner(); } /// @notice Based on the manager type returns the address /// @param _managerType Type of vault manager to use function getManagerAddr(ManagerType _managerType) public pure returns (address) { if (_managerType == ManagerType.RAI) { return 0xEfe0B4cA532769a3AE758fD82E1426a03A94F185; } } }
pragma solidity ^0.6.0; import "../auth/AdminAuth.sol"; contract BotRegistry is AdminAuth { mapping (address => bool) public botList; constructor() public { botList[0x776B4a13093e30B05781F97F6A4565B6aa8BE330] = true; botList[0xAED662abcC4FA3314985E67Ea993CAD064a7F5cF] = true; botList[0xa5d330F6619d6bF892A5B87D80272e1607b3e34D] = true; botList[0x5feB4DeE5150B589a7f567EA7CADa2759794A90A] = true; botList[0x7ca06417c1d6f480d3bB195B80692F95A6B66158] = true; } function setBot(address _botAddr, bool _state) public onlyOwner { botList[_botAddr] = _state; } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../DS/DSMath.sol"; import "../interfaces/TokenInterface.sol"; import "../interfaces/ExchangeInterfaceV3.sol"; import "../utils/ZrxAllowlist.sol"; import "./DFSExchangeData.sol"; import "./DFSExchangeHelper.sol"; import "../exchange/SaverExchangeRegistry.sol"; import "../interfaces/OffchainWrapperInterface.sol"; contract DFSExchangeCore is DFSExchangeHelper, DSMath, DFSExchangeData { string public constant ERR_SLIPPAGE_HIT = "Slippage hit"; string public constant ERR_DEST_AMOUNT_MISSING = "Dest amount missing"; string public constant ERR_WRAPPER_INVALID = "Wrapper invalid"; string public constant ERR_NOT_ZEROX_EXCHANGE = "Zerox exchange invalid"; /// @notice Internal method that preforms a sell on 0x/on-chain /// @dev Usefull for other DFS contract to integrate for exchanging /// @param exData Exchange data struct /// @return (address, uint) Address of the wrapper used and destAmount function _sell(ExchangeData memory exData) internal returns (address, uint) { address wrapper; uint swapedTokens; bool success; // if selling eth, convert to weth if (exData.srcAddr == KYBER_ETH_ADDRESS) { exData.srcAddr = ethToWethAddr(exData.srcAddr); TokenInterface(EXCHANGE_WETH_ADDRESS).deposit{value: exData.srcAmount}(); } exData.srcAmount -= getFee(exData.srcAmount, exData.user, exData.srcAddr, exData.dfsFeeDivider); // Try 0x first and then fallback on specific wrapper if (exData.offchainData.price > 0) { (success, swapedTokens) = takeOrder(exData, ActionType.SELL); if (success) { wrapper = exData.offchainData.exchangeAddr; } } // fallback to desired wrapper if 0x failed if (!success) { swapedTokens = saverSwap(exData, ActionType.SELL); wrapper = exData.wrapper; } // if anything is left in weth, pull it to user as eth if (getBalance(EXCHANGE_WETH_ADDRESS) > 0) { TokenInterface(EXCHANGE_WETH_ADDRESS).withdraw( TokenInterface(EXCHANGE_WETH_ADDRESS).balanceOf(address(this)) ); } if (exData.destAddr == EXCHANGE_WETH_ADDRESS) { require(getBalance(KYBER_ETH_ADDRESS) >= wmul(exData.minPrice, exData.srcAmount), ERR_SLIPPAGE_HIT); } else { require(getBalance(exData.destAddr) >= wmul(exData.minPrice, exData.srcAmount), ERR_SLIPPAGE_HIT); } return (wrapper, swapedTokens); } /// @notice Internal method that preforms a buy on 0x/on-chain /// @dev Usefull for other DFS contract to integrate for exchanging /// @param exData Exchange data struct /// @return (address, uint) Address of the wrapper used and srcAmount function _buy(ExchangeData memory exData) internal returns (address, uint) { address wrapper; uint swapedTokens; bool success; require(exData.destAmount != 0, ERR_DEST_AMOUNT_MISSING); exData.srcAmount -= getFee(exData.srcAmount, exData.user, exData.srcAddr, exData.dfsFeeDivider); // if selling eth, convert to weth if (exData.srcAddr == KYBER_ETH_ADDRESS) { exData.srcAddr = ethToWethAddr(exData.srcAddr); TokenInterface(EXCHANGE_WETH_ADDRESS).deposit{value: exData.srcAmount}(); } if (exData.offchainData.price > 0) { (success, swapedTokens) = takeOrder(exData, ActionType.BUY); if (success) { wrapper = exData.offchainData.exchangeAddr; } } // fallback to desired wrapper if 0x failed if (!success) { swapedTokens = saverSwap(exData, ActionType.BUY); wrapper = exData.wrapper; } // if anything is left in weth, pull it to user as eth if (getBalance(EXCHANGE_WETH_ADDRESS) > 0) { TokenInterface(EXCHANGE_WETH_ADDRESS).withdraw( TokenInterface(EXCHANGE_WETH_ADDRESS).balanceOf(address(this)) ); } if (exData.destAddr == EXCHANGE_WETH_ADDRESS) { require(getBalance(KYBER_ETH_ADDRESS) >= exData.destAmount, ERR_SLIPPAGE_HIT); } else { require(getBalance(exData.destAddr) >= exData.destAmount, ERR_SLIPPAGE_HIT); } return (wrapper, getBalance(exData.destAddr)); } /// @notice Takes order from 0x and returns bool indicating if it is successful /// @param _exData Exchange data function takeOrder( ExchangeData memory _exData, ActionType _type ) private returns (bool success, uint256) { if (!ZrxAllowlist(ZRX_ALLOWLIST_ADDR).isZrxAddr(_exData.offchainData.exchangeAddr)) { return (false, 0); } if (!SaverExchangeRegistry(SAVER_EXCHANGE_REGISTRY).isWrapper(_exData.offchainData.wrapper)) { return (false, 0); } // send src amount ERC20(_exData.srcAddr).safeTransfer(_exData.offchainData.wrapper, _exData.srcAmount); return OffchainWrapperInterface(_exData.offchainData.wrapper).takeOrder{value: _exData.offchainData.protocolFee}(_exData, _type); } /// @notice Calls wraper contract for exchage to preform an on-chain swap /// @param _exData Exchange data struct /// @param _type Type of action SELL|BUY /// @return swapedTokens For Sell that the destAmount, for Buy thats the srcAmount function saverSwap(ExchangeData memory _exData, ActionType _type) internal returns (uint swapedTokens) { require(SaverExchangeRegistry(SAVER_EXCHANGE_REGISTRY).isWrapper(_exData.wrapper), ERR_WRAPPER_INVALID); ERC20(_exData.srcAddr).safeTransfer(_exData.wrapper, _exData.srcAmount); if (_type == ActionType.SELL) { swapedTokens = ExchangeInterfaceV3(_exData.wrapper). sell(_exData.srcAddr, _exData.destAddr, _exData.srcAmount, _exData.wrapperData); } else { swapedTokens = ExchangeInterfaceV3(_exData.wrapper). buy(_exData.srcAddr, _exData.destAddr, _exData.destAmount, _exData.wrapperData); } } // solhint-disable-next-line no-empty-blocks receive() external virtual payable {} }
pragma solidity ^0.6.0; contract DSMath { function add(uint256 x, uint256 y) internal pure returns (uint256 z) { require((z = x + y) >= x); } function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { require((z = x - y) <= x); } function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { require(y == 0 || (z = x * y) / y == x); } function div(uint256 x, uint256 y) internal pure returns (uint256 z) { return x / y; } function min(uint256 x, uint256 y) internal pure returns (uint256 z) { return x <= y ? x : y; } function max(uint256 x, uint256 y) internal pure returns (uint256 z) { return x >= y ? x : y; } function imin(int256 x, int256 y) internal pure returns (int256 z) { return x <= y ? x : y; } function imax(int256 x, int256 y) internal pure returns (int256 z) { return x >= y ? x : y; } uint256 constant WAD = 10**18; uint256 constant RAY = 10**27; function wmul(uint256 x, uint256 y) internal pure returns (uint256 z) { z = add(mul(x, y), WAD / 2) / WAD; } function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) { z = add(mul(x, y), RAY / 2) / RAY; } function wdiv(uint256 x, uint256 y) internal pure returns (uint256 z) { z = add(mul(x, WAD), y / 2) / y; } function rdiv(uint256 x, uint256 y) internal pure returns (uint256 z) { z = add(mul(x, RAY), y / 2) / y; } // This famous algorithm is called "exponentiation by squaring" // and calculates x^n with x as fixed-point and n as regular unsigned. // // It's O(log n), instead of O(n) for naive repeated multiplication. // // These facts are why it works: // // If n is even, then x^n = (x^2)^(n/2). // If n is odd, then x^n = x * x^(n-1), // and applying the equation for even x gives // x^n = x * (x^2)^((n-1) / 2). // // Also, EVM division is flooring and // floor[(n-1) / 2] = floor[n / 2]. // function rpow(uint256 x, uint256 n) internal pure returns (uint256 z) { z = n % 2 != 0 ? x : RAY; for (n /= 2; n != 0; n /= 2) { x = rmul(x, x); if (n % 2 != 0) { z = rmul(z, x); } } } }
pragma solidity ^0.6.0; import "./DSAuth.sol"; import "./DSNote.sol"; abstract contract DSProxy is DSAuth, DSNote { DSProxyCache public cache; // global cache for contracts constructor(address _cacheAddr) public { require(setCache(_cacheAddr)); } // solhint-disable-next-line no-empty-blocks receive() external payable {} // use the proxy to execute calldata _data on contract _code // function execute(bytes memory _code, bytes memory _data) // public // payable // virtual // returns (address target, bytes32 response); function execute(address _target, bytes memory _data) public payable virtual returns (bytes32 response); //set new cache function setCache(address _cacheAddr) public virtual payable returns (bool); } contract DSProxyCache { mapping(bytes32 => address) cache; function read(bytes memory _code) public view returns (address) { bytes32 hash = keccak256(_code); return cache[hash]; } function write(bytes memory _code) public returns (address target) { assembly { target := create(0, add(_code, 0x20), mload(_code)) switch iszero(extcodesize(target)) case 1 { // throw if contract failed to deploy revert(0, 0) } } bytes32 hash = keccak256(_code); cache[hash] = target; } }
pragma solidity ^0.6.0; abstract contract IBasicTokenAdapters { bytes32 public collateralType; function decimals() virtual public view returns (uint); function collateral() virtual public view returns (address); function join(address, uint) virtual public payable; function exit(address, uint) virtual public; }
pragma solidity ^0.6.0; abstract contract ISAFEManager { function lastSAFEID(address) virtual public returns (uint); function safeCan(address, uint, address) virtual public view returns (uint); function collateralTypes(uint) virtual public view returns (bytes32); function ownsSAFE(uint) virtual public view returns (address); function safes(uint) virtual public view returns (address); function safeEngine() virtual public view returns (address); function openSAFE(bytes32, address) virtual public returns (uint); function transferSAFEOwnership(uint, address) virtual public; function allowSAFE(uint, address, uint) virtual public; function handlerAllowed(address, uint) virtual public; function modifySAFECollateralization(uint, int, int) virtual public; function transferCollateral(uint, address, uint) virtual public; function transferInternalCoins(uint, address, uint) virtual public; function quitSystem(uint, address) virtual public; function enterSystem(address, uint) virtual public; function moveSAFE(uint, uint) virtual public; }
pragma solidity ^0.6.0; abstract contract ISAFEEngine { struct SAFE { uint256 lockedCollateral; uint256 generatedDebt; } struct CollateralType { // Total debt issued for this specific collateral type uint256 debtAmount; // [wad] // Accumulator for interest accrued on this collateral type uint256 accumulatedRate; // [ray] // Floor price at which a SAFE is allowed to generate debt uint256 safetyPrice; // [ray] // Maximum amount of debt that can be generated with this collateral type uint256 debtCeiling; // [rad] // Minimum amount of debt that must be generated by a SAFE using this collateral uint256 debtFloor; // [rad] // Price at which a SAFE gets liquidated uint256 liquidationPrice; // [ray] } mapping (bytes32 => mapping (address => SAFE )) public safes; mapping (bytes32 => CollateralType) public collateralTypes; mapping (bytes32 => mapping (address => uint)) public tokenCollateral; function safeRights(address, address) virtual public view returns (uint); function coinBalance(address) virtual public view returns (uint); function modifySAFECollateralization(bytes32, address, address, address, int, int) virtual public; function approveSAFEModification(address) virtual public; function transferInternalCoins(address, address, uint) virtual public; function transferSAFECollateralAndDebt(bytes32, address, address, int, int) virtual public; }
pragma solidity ^0.6.0; contract DSNote { event LogNote( bytes4 indexed sig, address indexed guy, bytes32 indexed foo, bytes32 indexed bar, uint256 wad, bytes fax ) anonymous; modifier note { bytes32 foo; bytes32 bar; assembly { foo := calldataload(4) bar := calldataload(36) } emit LogNote(msg.sig, msg.sender, foo, bar, msg.value, msg.data); _; } }
pragma solidity ^0.6.0; abstract contract TokenInterface { address public constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; function allowance(address, address) public virtual returns (uint256); function balanceOf(address) public virtual returns (uint256); function approve(address, uint256) public virtual; function transfer(address, uint256) public virtual returns (bool); function transferFrom(address, address, uint256) public virtual returns (bool); function deposit() public virtual payable; function withdraw(uint256) public virtual; }
pragma solidity ^0.6.0; interface ExchangeInterfaceV3 { function sell(address _srcAddr, address _destAddr, uint _srcAmount, bytes memory _additionalData) external payable returns (uint); function buy(address _srcAddr, address _destAddr, uint _destAmount, bytes memory _additionalData) external payable returns(uint); function getSellRate(address _srcAddr, address _destAddr, uint _srcAmount, bytes memory _additionalData) external view returns (uint); function getBuyRate(address _srcAddr, address _destAddr, uint _srcAmount, bytes memory _additionalData) external view returns (uint); }
pragma solidity ^0.6.0; import "../auth/AdminAuth.sol"; contract ZrxAllowlist is AdminAuth { mapping (address => bool) public zrxAllowlist; mapping(address => bool) private nonPayableAddrs; constructor() public { zrxAllowlist[0x6958F5e95332D93D21af0D7B9Ca85B8212fEE0A5] = true; zrxAllowlist[0x61935CbDd02287B511119DDb11Aeb42F1593b7Ef] = true; zrxAllowlist[0xDef1C0ded9bec7F1a1670819833240f027b25EfF] = true; zrxAllowlist[0x080bf510FCbF18b91105470639e9561022937712] = true; nonPayableAddrs[0x080bf510FCbF18b91105470639e9561022937712] = true; } function setAllowlistAddr(address _zrxAddr, bool _state) public onlyOwner { zrxAllowlist[_zrxAddr] = _state; } function isZrxAddr(address _zrxAddr) public view returns (bool) { return zrxAllowlist[_zrxAddr]; } function addNonPayableAddr(address _nonPayableAddr) public onlyOwner { nonPayableAddrs[_nonPayableAddr] = true; } function removeNonPayableAddr(address _nonPayableAddr) public onlyOwner { nonPayableAddrs[_nonPayableAddr] = false; } function isNonPayableAddr(address _addr) public view returns(bool) { return nonPayableAddrs[_addr]; } }
pragma solidity ^0.6.0; import "../utils/SafeERC20.sol"; import "../utils/Discount.sol"; import "../interfaces/IFeeRecipient.sol"; contract DFSExchangeHelper { string public constant ERR_OFFCHAIN_DATA_INVALID = "Offchain data invalid"; using SafeERC20 for ERC20; address public constant KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant EXCHANGE_WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; IFeeRecipient public constant _feeRecipient = IFeeRecipient(0x39C4a92Dc506300c3Ea4c67ca4CA611102ee6F2A); address public constant DISCOUNT_ADDRESS = 0x1b14E8D511c9A4395425314f849bD737BAF8208F; address public constant SAVER_EXCHANGE_REGISTRY = 0x25dd3F51e0C3c3Ff164DDC02A8E4D65Bb9cBB12D; address public constant ZRX_ALLOWLIST_ADDR = 0x4BA1f38427b33B8ab7Bb0490200dAE1F1C36823F; function getDecimals(address _token) internal view returns (uint256) { if (_token == KYBER_ETH_ADDRESS) return 18; return ERC20(_token).decimals(); } function getBalance(address _tokenAddr) internal view returns (uint balance) { if (_tokenAddr == KYBER_ETH_ADDRESS) { balance = address(this).balance; } else { balance = ERC20(_tokenAddr).balanceOf(address(this)); } } function sendLeftover(address _srcAddr, address _destAddr, address payable _to) internal { // send back any leftover ether or tokens if (address(this).balance > 0) { _to.transfer(address(this).balance); } if (getBalance(_srcAddr) > 0) { ERC20(_srcAddr).safeTransfer(_to, getBalance(_srcAddr)); } if (getBalance(_destAddr) > 0) { ERC20(_destAddr).safeTransfer(_to, getBalance(_destAddr)); } } /// @notice Takes a feePercentage and sends it to wallet /// @param _amount Dai amount of the whole trade /// @param _user Address of the user /// @param _token Address of the token /// @param _dfsFeeDivider Dfs fee divider /// @return feeAmount Amount in Dai owner earned on the fee function getFee(uint256 _amount, address _user, address _token, uint256 _dfsFeeDivider) internal returns (uint256 feeAmount) { if (_dfsFeeDivider != 0 && Discount(DISCOUNT_ADDRESS).isCustomFeeSet(_user)) { _dfsFeeDivider = Discount(DISCOUNT_ADDRESS).getCustomServiceFee(_user); } if (_dfsFeeDivider == 0) { feeAmount = 0; } else { feeAmount = _amount / _dfsFeeDivider; // fee can't go over 10% of the whole amount if (feeAmount > (_amount / 10)) { feeAmount = _amount / 10; } address walletAddr = _feeRecipient.getFeeAddr(); if (_token == KYBER_ETH_ADDRESS) { payable(walletAddr).transfer(feeAmount); } else { ERC20(_token).safeTransfer(walletAddr, feeAmount); } } } function sliceUint(bytes memory bs, uint256 start) internal pure returns (uint256) { require(bs.length >= start + 32, "slicing out of range"); uint256 x; assembly { x := mload(add(bs, add(0x20, start))) } return x; } function writeUint256(bytes memory _b, uint256 _index, uint _input) internal pure { if (_b.length < _index + 32) { revert(ERR_OFFCHAIN_DATA_INVALID); } bytes32 input = bytes32(_input); _index += 32; // Read the bytes32 from array memory assembly { mstore(add(_b, _index), input) } } /// @notice Converts Kybers Eth address -> Weth /// @param _src Input address function ethToWethAddr(address _src) internal pure returns (address) { return _src == KYBER_ETH_ADDRESS ? EXCHANGE_WETH_ADDRESS : _src; } }
pragma solidity ^0.6.0; import "../auth/AdminAuth.sol"; contract SaverExchangeRegistry is AdminAuth { mapping(address => bool) private wrappers; constructor() public { wrappers[0x880A845A85F843a5c67DB2061623c6Fc3bB4c511] = true; wrappers[0x4c9B55f2083629A1F7aDa257ae984E03096eCD25] = true; wrappers[0x42A9237b872368E1bec4Ca8D26A928D7d39d338C] = true; } function addWrapper(address _wrapper) public onlyOwner { wrappers[_wrapper] = true; } function removeWrapper(address _wrapper) public onlyOwner { wrappers[_wrapper] = false; } function isWrapper(address _wrapper) public view returns(bool) { return wrappers[_wrapper]; } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../exchangeV3/DFSExchangeData.sol"; abstract contract OffchainWrapperInterface is DFSExchangeData { function takeOrder( ExchangeData memory _exData, ActionType _type ) virtual public payable returns (bool success, uint256); }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; abstract contract IFeeRecipient { function getFeeAddr() public view virtual returns (address); function changeWalletAddr(address _newWallet) public virtual; }
pragma solidity ^0.6.0; import "./SafeERC20.sol"; interface IFlashLoanReceiver { function executeOperation(address _reserve, uint256 _amount, uint256 _fee, bytes calldata _params) external; } abstract contract ILendingPoolAddressesProvider { function getLendingPool() public view virtual returns (address); function setLendingPoolImpl(address _pool) public virtual; function getLendingPoolCore() public virtual view returns (address payable); function setLendingPoolCoreImpl(address _lendingPoolCore) public virtual; function getLendingPoolConfigurator() public virtual view returns (address); function setLendingPoolConfiguratorImpl(address _configurator) public virtual; function getLendingPoolDataProvider() public virtual view returns (address); function setLendingPoolDataProviderImpl(address _provider) public virtual; function getLendingPoolParametersProvider() public virtual view returns (address); function setLendingPoolParametersProviderImpl(address _parametersProvider) public virtual; function getTokenDistributor() public virtual view returns (address); function setTokenDistributor(address _tokenDistributor) public virtual; function getFeeProvider() public virtual view returns (address); function setFeeProviderImpl(address _feeProvider) public virtual; function getLendingPoolLiquidationManager() public virtual view returns (address); function setLendingPoolLiquidationManager(address _manager) public virtual; function getLendingPoolManager() public virtual view returns (address); function setLendingPoolManager(address _lendingPoolManager) public virtual; function getPriceOracle() public virtual view returns (address); function setPriceOracle(address _priceOracle) public virtual; function getLendingRateOracle() public view virtual returns (address); function setLendingRateOracle(address _lendingRateOracle) public virtual; } library EthAddressLib { function ethAddress() internal pure returns(address) { return 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; } } abstract contract FlashLoanReceiverBase is IFlashLoanReceiver { using SafeERC20 for ERC20; using SafeMath for uint256; ILendingPoolAddressesProvider public addressesProvider; constructor(ILendingPoolAddressesProvider _provider) public { addressesProvider = _provider; } receive () external virtual payable {} function transferFundsBackToPoolInternal(address _reserve, uint256 _amount) internal { address payable core = addressesProvider.getLendingPoolCore(); transferInternal(core,_reserve, _amount); } function transferInternal(address payable _destination, address _reserve, uint256 _amount) internal { if(_reserve == EthAddressLib.ethAddress()) { //solium-disable-next-line _destination.call{value: _amount}(""); return; } ERC20(_reserve).safeTransfer(_destination, _amount); } function getBalanceInternal(address _target, address _reserve) internal view returns(uint256) { if(_reserve == EthAddressLib.ethAddress()) { return _target.balance; } return ERC20(_reserve).balanceOf(_target); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../DS/DSProxy.sol"; import "../../utils/FlashLoanReceiverBase.sol"; import "../../interfaces/DSProxyInterface.sol"; import "../../exchangeV3/DFSExchangeCore.sol"; import "../../shifter/ShifterRegistry.sol"; import "./CompoundCreateTaker.sol"; /// @title Contract that receives the FL from Aave for Creating loans contract CompoundCreateReceiver is FlashLoanReceiverBase, DFSExchangeCore { ILendingPoolAddressesProvider public LENDING_POOL_ADDRESS_PROVIDER = ILendingPoolAddressesProvider(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8); ShifterRegistry public constant shifterRegistry = ShifterRegistry(0x597C52281b31B9d949a9D8fEbA08F7A2530a965e); address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant DISCOUNT_ADDR = 0x1b14E8D511c9A4395425314f849bD737BAF8208F; uint public constant SERVICE_FEE = 400; // 0.25% Fee // solhint-disable-next-line no-empty-blocks constructor() public FlashLoanReceiverBase(LENDING_POOL_ADDRESS_PROVIDER) {} struct CompCreateData { address payable proxyAddr; bytes proxyData; address cCollAddr; address cDebtAddr; } /// @notice Called by Aave when sending back the FL amount /// @param _reserve The address of the borrowed token /// @param _amount Amount of FL tokens received /// @param _fee FL Aave fee /// @param _params The params that are sent from the original FL caller contract function executeOperation( address _reserve, uint256 _amount, uint256 _fee, bytes calldata _params) external override { // Format the call data for DSProxy (CompCreateData memory compCreate, ExchangeData memory exchangeData) = packFunctionCall(_amount, _fee, _params); address leveragedAsset = _reserve; // If the assets are different if (compCreate.cCollAddr != compCreate.cDebtAddr) { exchangeData.dfsFeeDivider = SERVICE_FEE; exchangeData.user = DSProxyInterface(compCreate.proxyAddr).owner(); _sell(exchangeData); leveragedAsset = exchangeData.destAddr; } // Send amount to DSProxy sendToProxy(compCreate.proxyAddr, leveragedAsset); address compOpenProxy = shifterRegistry.getAddr("COMP_SHIFTER"); // Execute the DSProxy call DSProxyInterface(compCreate.proxyAddr).execute(compOpenProxy, compCreate.proxyData); // Repay the loan with the money DSProxy sent back transferFundsBackToPoolInternal(_reserve, _amount.add(_fee)); // if there is some eth left (0x fee), return it to user if (address(this).balance > 0) { // solhint-disable-next-line avoid-tx-origin tx.origin.transfer(address(this).balance); } } /// @notice Formats function data call so we can call it through DSProxy /// @param _amount Amount of FL /// @param _fee Fee of the FL /// @param _params Saver proxy params function packFunctionCall(uint _amount, uint _fee, bytes memory _params) internal pure returns (CompCreateData memory compCreate, ExchangeData memory exchangeData) { CompoundCreateTaker.CreateInfo memory createData; address proxy; (createData , exchangeData, proxy)= abi.decode(_params, (CompoundCreateTaker.CreateInfo, ExchangeData, address)); bytes memory proxyData = abi.encodeWithSignature( "open(address,address,uint256)", createData.cCollAddress, createData.cBorrowAddress, (_amount + _fee)); compCreate = CompCreateData({ proxyAddr: payable(proxy), proxyData: proxyData, cCollAddr: createData.cCollAddress, cDebtAddr: createData.cBorrowAddress }); return (compCreate, exchangeData); } /// @notice Send the FL funds received to DSProxy /// @param _proxy DSProxy address /// @param _reserve Token address function sendToProxy(address payable _proxy, address _reserve) internal { if (_reserve != ETH_ADDRESS) { ERC20(_reserve).safeTransfer(_proxy, ERC20(_reserve).balanceOf(address(this))); } else { _proxy.transfer(address(this).balance); } } // solhint-disable-next-line no-empty-blocks receive() external override(FlashLoanReceiverBase, DFSExchangeCore) payable {} }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../interfaces/ILendingPool.sol"; import "../../loggers/DefisaverLogger.sol"; import "../../auth/ProxyPermission.sol"; import "../../exchangeV3/DFSExchangeData.sol"; import "../../utils/SafeERC20.sol"; /// @title Opens compound positions with a leverage contract CompoundCreateTaker is ProxyPermission { using SafeERC20 for ERC20; address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; ILendingPool public constant lendingPool = ILendingPool(0x398eC7346DcD622eDc5ae82352F02bE94C62d119); // solhint-disable-next-line const-name-snakecase DefisaverLogger public constant logger = DefisaverLogger(0x5c55B921f590a89C1Ebe84dF170E655a82b62126); struct CreateInfo { address cCollAddress; address cBorrowAddress; uint depositAmount; } /// @notice Main function which will take a FL and open a leverage position /// @dev Call through DSProxy, if _exchangeData.destAddr is a token approve DSProxy /// @param _createInfo [cCollAddress, cBorrowAddress, depositAmount] /// @param _exchangeData Exchange data struct function openLeveragedLoan( CreateInfo memory _createInfo, DFSExchangeData.ExchangeData memory _exchangeData, address payable _compReceiver ) public payable { uint loanAmount = _exchangeData.srcAmount; // Pull tokens from user if (_exchangeData.destAddr != ETH_ADDRESS) { ERC20(_exchangeData.destAddr).safeTransferFrom(msg.sender, address(this), _createInfo.depositAmount); } else { require(msg.value >= _createInfo.depositAmount, "Must send correct amount of eth"); } // Send tokens to FL receiver sendDeposit(_compReceiver, _exchangeData.destAddr); bytes memory paramsData = abi.encode(_createInfo, _exchangeData, address(this)); givePermission(_compReceiver); lendingPool.flashLoan(_compReceiver, _exchangeData.srcAddr, loanAmount, paramsData); removePermission(_compReceiver); logger.Log(address(this), msg.sender, "CompoundLeveragedLoan", abi.encode(_exchangeData.srcAddr, _exchangeData.destAddr, _exchangeData.srcAmount, _exchangeData.destAmount)); } function sendDeposit(address payable _compoundReceiver, address _token) internal { if (_token != ETH_ADDRESS) { ERC20(_token).safeTransfer(_compoundReceiver, ERC20(_token).balanceOf(address(this))); } _compoundReceiver.transfer(address(this).balance); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../saver/RAISaverProxy.sol"; import "../../exchangeV3/DFSExchangeData.sol"; import "../../utils/GasBurner.sol"; import "../../interfaces/ILendingPool.sol"; import "../../utils/DydxFlashLoanBase.sol"; contract RAISaverTaker is RAISaverProxy, DydxFlashLoanBase, GasBurner { address public constant WETH_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; struct SaverData { uint256 flAmount; bool isRepay; uint256 safeId; uint256 gasCost; address joinAddr; ManagerType managerType; } function boostWithLoan( ExchangeData memory _exchangeData, uint256 _safeId, uint256 _gasCost, address _joinAddr, ManagerType _managerType, address _raiSaverFlashLoan ) public payable burnGas(25) { address managerAddr = getManagerAddr(_managerType); uint256 maxDebt = getMaxDebt(managerAddr, _safeId, ISAFEManager(managerAddr).collateralTypes(_safeId)); if (maxDebt >= _exchangeData.srcAmount) { if (_exchangeData.srcAmount > maxDebt) { _exchangeData.srcAmount = maxDebt; } boost(_exchangeData, _safeId, _gasCost, _joinAddr, _managerType); return; } uint256 loanAmount = getAvailableEthLiquidity(); SaverData memory saverData = SaverData({ flAmount: loanAmount, isRepay: false, safeId: _safeId, gasCost: _gasCost, joinAddr: _joinAddr, managerType: _managerType }); _flashLoan(_raiSaverFlashLoan, _exchangeData, saverData); } function repayWithLoan( ExchangeData memory _exchangeData, uint256 _safeId, uint256 _gasCost, address _joinAddr, ManagerType _managerType, address _raiSaverFlashLoan ) public payable burnGas(25) { address managerAddr = getManagerAddr(_managerType); uint256 maxColl = getMaxCollateral( managerAddr, _safeId, ISAFEManager(managerAddr).collateralTypes(_safeId), _joinAddr ); if (maxColl >= _exchangeData.srcAmount) { if (_exchangeData.srcAmount > maxColl) { _exchangeData.srcAmount = maxColl; } repay(_exchangeData, _safeId, _gasCost, _joinAddr, _managerType); return; } uint256 loanAmount = _exchangeData.srcAmount; SaverData memory saverData = SaverData({ flAmount: loanAmount, isRepay: true, safeId: _safeId, gasCost: _gasCost, joinAddr: _joinAddr, managerType: _managerType }); _flashLoan(_raiSaverFlashLoan, _exchangeData, saverData); } /// @notice Gets the maximum amount of debt available to generate /// @param _managerAddr Address of the CDP Manager /// @param _safeId Id of the CDP /// @param _collType Coll type of the CDP function getMaxDebt( address _managerAddr, uint256 _safeId, bytes32 _collType ) public override view returns (uint256) { (uint256 collateral, uint256 debt) = getSafeInfo(ISAFEManager(_managerAddr), _safeId, _collType); (, , uint256 safetyPrice, , , ) = ISAFEEngine(SAFE_ENGINE_ADDRESS).collateralTypes(_collType); return sub(rmul(collateral, safetyPrice), debt); } /// @notice Fetches Eth Dydx liqudity function getAvailableEthLiquidity() internal view returns (uint256 liquidity) { liquidity = ERC20(WETH_ADDR).balanceOf(SOLO_MARGIN_ADDRESS); } /// @notice Starts the process to move users position 1 collateral and 1 borrow /// @dev User must send 2 wei with this transaction function _flashLoan(address RAI_SAVER_FLASH_LOAN, ExchangeData memory _exchangeData, SaverData memory _saverData) internal { ISoloMargin solo = ISoloMargin(SOLO_MARGIN_ADDRESS); address managerAddr = getManagerAddr(_saverData.managerType); // Get marketId from token address uint256 marketId = _getMarketIdFromTokenAddress(WETH_ADDR); // Calculate repay amount (_amount + (2 wei)) // Approve transfer from uint256 repayAmount = _getRepaymentAmountInternal(_saverData.flAmount); ERC20(WETH_ADDR).approve(SOLO_MARGIN_ADDRESS, repayAmount); Actions.ActionArgs[] memory operations = new Actions.ActionArgs[](3); operations[0] = _getWithdrawAction(marketId, _saverData.flAmount, RAI_SAVER_FLASH_LOAN); payable(RAI_SAVER_FLASH_LOAN).transfer(msg.value); // 0x fee bytes memory exchangeData = packExchangeData(_exchangeData); operations[1] = _getCallAction(abi.encode(exchangeData, _saverData), RAI_SAVER_FLASH_LOAN); operations[2] = _getDepositAction(marketId, repayAmount, address(this)); Account.Info[] memory accountInfos = new Account.Info[](1); accountInfos[0] = _getAccountInfo(); ISAFEManager(managerAddr).allowSAFE(_saverData.safeId, RAI_SAVER_FLASH_LOAN, 1); solo.operate(accountInfos, operations); ISAFEManager(managerAddr).allowSAFE(_saverData.safeId, RAI_SAVER_FLASH_LOAN, 0); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../utils/SafeMath.sol"; import "../savings/dydx/ISoloMargin.sol"; contract DydxFlashLoanBase { using SafeMath for uint256; address public constant SOLO_MARGIN_ADDRESS = 0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e; function _getMarketIdFromTokenAddress(address token) internal view returns (uint256) { return 0; } function _getRepaymentAmountInternal(uint256 amount) internal view returns (uint256) { // Needs to be overcollateralize // Needs to provide +2 wei to be safe return amount.add(2); } function _getAccountInfo() internal view returns (Account.Info memory) { return Account.Info({owner: address(this), number: 1}); } function _getWithdrawAction(uint marketId, uint256 amount, address contractAddr) internal view returns (Actions.ActionArgs memory) { return Actions.ActionArgs({ actionType: Actions.ActionType.Withdraw, accountId: 0, amount: Types.AssetAmount({ sign: false, denomination: Types.AssetDenomination.Wei, ref: Types.AssetReference.Delta, value: amount }), primaryMarketId: marketId, secondaryMarketId: 0, otherAddress: contractAddr, otherAccountId: 0, data: "" }); } function _getCallAction(bytes memory data, address contractAddr) internal view returns (Actions.ActionArgs memory) { return Actions.ActionArgs({ actionType: Actions.ActionType.Call, accountId: 0, amount: Types.AssetAmount({ sign: false, denomination: Types.AssetDenomination.Wei, ref: Types.AssetReference.Delta, value: 0 }), primaryMarketId: 0, secondaryMarketId: 0, otherAddress: contractAddr, otherAccountId: 0, data: data }); } function _getDepositAction(uint marketId, uint256 amount, address contractAddr) internal view returns (Actions.ActionArgs memory) { return Actions.ActionArgs({ actionType: Actions.ActionType.Deposit, accountId: 0, amount: Types.AssetAmount({ sign: true, denomination: Types.AssetDenomination.Wei, ref: Types.AssetReference.Delta, value: amount }), primaryMarketId: marketId, secondaryMarketId: 0, otherAddress: contractAddr, otherAccountId: 0, data: "" }); } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; library Account { enum Status {Normal, Liquid, Vapor} struct Info { address owner; // The address that owns the account uint256 number; // A nonce that allows a single address to control many accounts } struct Storage { mapping(uint256 => Types.Par) balances; // Mapping from marketId to principal Status status; } } library Actions { enum ActionType { Deposit, // supply tokens Withdraw, // borrow tokens Transfer, // transfer balance between accounts Buy, // buy an amount of some token (public virtually) Sell, // sell an amount of some token (public virtually) Trade, // trade tokens against another account Liquidate, // liquidate an undercollateralized or expiring account Vaporize, // use excess tokens to zero-out a completely negative account Call // send arbitrary data to an address } enum AccountLayout {OnePrimary, TwoPrimary, PrimaryAndSecondary} enum MarketLayout {ZeroMarkets, OneMarket, TwoMarkets} struct ActionArgs { ActionType actionType; uint256 accountId; Types.AssetAmount amount; uint256 primaryMarketId; uint256 secondaryMarketId; address otherAddress; uint256 otherAccountId; bytes data; } struct DepositArgs { Types.AssetAmount amount; Account.Info account; uint256 market; address from; } struct WithdrawArgs { Types.AssetAmount amount; Account.Info account; uint256 market; address to; } struct TransferArgs { Types.AssetAmount amount; Account.Info accountOne; Account.Info accountTwo; uint256 market; } struct BuyArgs { Types.AssetAmount amount; Account.Info account; uint256 makerMarket; uint256 takerMarket; address exchangeWrapper; bytes orderData; } struct SellArgs { Types.AssetAmount amount; Account.Info account; uint256 takerMarket; uint256 makerMarket; address exchangeWrapper; bytes orderData; } struct TradeArgs { Types.AssetAmount amount; Account.Info takerAccount; Account.Info makerAccount; uint256 inputMarket; uint256 outputMarket; address autoTrader; bytes tradeData; } struct LiquidateArgs { Types.AssetAmount amount; Account.Info solidAccount; Account.Info liquidAccount; uint256 owedMarket; uint256 heldMarket; } struct VaporizeArgs { Types.AssetAmount amount; Account.Info solidAccount; Account.Info vaporAccount; uint256 owedMarket; uint256 heldMarket; } struct CallArgs { Account.Info account; address callee; bytes data; } } library Decimal { struct D256 { uint256 value; } } library Interest { struct Rate { uint256 value; } struct Index { uint96 borrow; uint96 supply; uint32 lastUpdate; } } library Monetary { struct Price { uint256 value; } struct Value { uint256 value; } } library Storage { // All information necessary for tracking a market struct Market { // Contract address of the associated ERC20 token address token; // Total aggregated supply and borrow amount of the entire market Types.TotalPar totalPar; // Interest index of the market Interest.Index index; // Contract address of the price oracle for this market address priceOracle; // Contract address of the interest setter for this market address interestSetter; // Multiplier on the marginRatio for this market Decimal.D256 marginPremium; // Multiplier on the liquidationSpread for this market Decimal.D256 spreadPremium; // Whether additional borrows are allowed for this market bool isClosing; } // The global risk parameters that govern the health and security of the system struct RiskParams { // Required ratio of over-collateralization Decimal.D256 marginRatio; // Percentage penalty incurred by liquidated accounts Decimal.D256 liquidationSpread; // Percentage of the borrower's interest fee that gets passed to the suppliers Decimal.D256 earningsRate; // The minimum absolute borrow value of an account // There must be sufficient incentivize to liquidate undercollateralized accounts Monetary.Value minBorrowedValue; } // The maximum RiskParam values that can be set struct RiskLimits { uint64 marginRatioMax; uint64 liquidationSpreadMax; uint64 earningsRateMax; uint64 marginPremiumMax; uint64 spreadPremiumMax; uint128 minBorrowedValueMax; } // The entire storage state of Solo struct State { // number of markets uint256 numMarkets; // marketId => Market mapping(uint256 => Market) markets; // owner => account number => Account mapping(address => mapping(uint256 => Account.Storage)) accounts; // Addresses that can control other users accounts mapping(address => mapping(address => bool)) operators; // Addresses that can control all users accounts mapping(address => bool) globalOperators; // mutable risk parameters of the system RiskParams riskParams; // immutable risk limits of the system RiskLimits riskLimits; } } library Types { enum AssetDenomination { Wei, // the amount is denominated in wei Par // the amount is denominated in par } enum AssetReference { Delta, // the amount is given as a delta from the current value Target // the amount is given as an exact number to end up at } struct AssetAmount { bool sign; // true if positive AssetDenomination denomination; AssetReference ref; uint256 value; } struct TotalPar { uint128 borrow; uint128 supply; } struct Par { bool sign; // true if positive uint128 value; } struct Wei { bool sign; // true if positive uint256 value; } } abstract contract ISoloMargin { struct OperatorArg { address operator; bool trusted; } function ownerSetSpreadPremium( uint256 marketId, Decimal.D256 memory spreadPremium ) public virtual; function getIsGlobalOperator(address operator) public virtual view returns (bool); function getMarketTokenAddress(uint256 marketId) public virtual view returns (address); function ownerSetInterestSetter(uint256 marketId, address interestSetter) public virtual; function getAccountValues(Account.Info memory account) public virtual view returns (Monetary.Value memory, Monetary.Value memory); function getMarketPriceOracle(uint256 marketId) public virtual view returns (address); function getMarketInterestSetter(uint256 marketId) public virtual view returns (address); function getMarketSpreadPremium(uint256 marketId) public virtual view returns (Decimal.D256 memory); function getNumMarkets() public virtual view returns (uint256); function ownerWithdrawUnsupportedTokens(address token, address recipient) public virtual returns (uint256); function ownerSetMinBorrowedValue(Monetary.Value memory minBorrowedValue) public virtual; function ownerSetLiquidationSpread(Decimal.D256 memory spread) public virtual; function ownerSetEarningsRate(Decimal.D256 memory earningsRate) public virtual; function getIsLocalOperator(address owner, address operator) public virtual view returns (bool); function getAccountPar(Account.Info memory account, uint256 marketId) public virtual view returns (Types.Par memory); function ownerSetMarginPremium( uint256 marketId, Decimal.D256 memory marginPremium ) public virtual; function getMarginRatio() public virtual view returns (Decimal.D256 memory); function getMarketCurrentIndex(uint256 marketId) public virtual view returns (Interest.Index memory); function getMarketIsClosing(uint256 marketId) public virtual view returns (bool); function getRiskParams() public virtual view returns (Storage.RiskParams memory); function getAccountBalances(Account.Info memory account) public virtual view returns (address[] memory, Types.Par[] memory, Types.Wei[] memory); function renounceOwnership() public virtual; function getMinBorrowedValue() public virtual view returns (Monetary.Value memory); function setOperators(OperatorArg[] memory args) public virtual; function getMarketPrice(uint256 marketId) public virtual view returns (address); function owner() public virtual view returns (address); function isOwner() public virtual view returns (bool); function ownerWithdrawExcessTokens(uint256 marketId, address recipient) public virtual returns (uint256); function ownerAddMarket( address token, address priceOracle, address interestSetter, Decimal.D256 memory marginPremium, Decimal.D256 memory spreadPremium ) public virtual; function operate( Account.Info[] memory accounts, Actions.ActionArgs[] memory actions ) public virtual; function getMarketWithInfo(uint256 marketId) public virtual view returns ( Storage.Market memory, Interest.Index memory, Monetary.Price memory, Interest.Rate memory ); function ownerSetMarginRatio(Decimal.D256 memory ratio) public virtual; function getLiquidationSpread() public virtual view returns (Decimal.D256 memory); function getAccountWei(Account.Info memory account, uint256 marketId) public virtual view returns (Types.Wei memory); function getMarketTotalPar(uint256 marketId) public virtual view returns (Types.TotalPar memory); function getLiquidationSpreadForPair( uint256 heldMarketId, uint256 owedMarketId ) public virtual view returns (Decimal.D256 memory); function getNumExcessTokens(uint256 marketId) public virtual view returns (Types.Wei memory); function getMarketCachedIndex(uint256 marketId) public virtual view returns (Interest.Index memory); function getAccountStatus(Account.Info memory account) public virtual view returns (uint8); function getEarningsRate() public virtual view returns (Decimal.D256 memory); function ownerSetPriceOracle(uint256 marketId, address priceOracle) public virtual; function getRiskLimits() public virtual view returns (Storage.RiskLimits memory); function getMarket(uint256 marketId) public virtual view returns (Storage.Market memory); function ownerSetIsClosing(uint256 marketId, bool isClosing) public virtual; function ownerSetGlobalOperator(address operator, bool approved) public virtual; function transferOwnership(address newOwner) public virtual; function getAdjustedAccountValues(Account.Info memory account) public virtual view returns (Monetary.Value memory, Monetary.Value memory); function getMarketMarginPremium(uint256 marketId) public virtual view returns (Decimal.D256 memory); function getMarketInterestRate(uint256 marketId) public virtual view returns (Interest.Rate memory); }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../auth/ProxyPermission.sol"; import "../utils/DydxFlashLoanBase.sol"; import "../loggers/DefisaverLogger.sol"; import "../interfaces/ERC20.sol"; /// @title Takes flash loan contract DyDxFlashLoanTaker is DydxFlashLoanBase, ProxyPermission { address public constant WETH_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address public constant DEFISAVER_LOGGER = 0x5c55B921f590a89C1Ebe84dF170E655a82b62126; /// @notice Takes flash loan for _receiver /// @dev Receiver must send back WETH + 2 wei after executing transaction /// @dev Method is meant to be called from proxy and proxy will give authorization to _receiver /// @param _receiver Address of funds receiver /// @param _ethAmount ETH amount that needs to be pulled from dydx /// @param _encodedData Bytes with packed data function takeLoan(address _receiver, uint256 _ethAmount, bytes memory _encodedData) public { ISoloMargin solo = ISoloMargin(SOLO_MARGIN_ADDRESS); // Get marketId from token address uint256 marketId = _getMarketIdFromTokenAddress(WETH_ADDR); // Calculate repay amount (_amount + (2 wei)) // Approve transfer from uint256 repayAmount = _getRepaymentAmountInternal(_ethAmount); ERC20(WETH_ADDR).approve(SOLO_MARGIN_ADDRESS, repayAmount); Actions.ActionArgs[] memory operations = new Actions.ActionArgs[](3); operations[0] = _getWithdrawAction(marketId, _ethAmount, _receiver); operations[1] = _getCallAction( _encodedData, _receiver ); operations[2] = _getDepositAction(marketId, repayAmount, address(this)); Account.Info[] memory accountInfos = new Account.Info[](1); accountInfos[0] = _getAccountInfo(); givePermission(_receiver); solo.operate(accountInfos, operations); removePermission(_receiver); DefisaverLogger(DEFISAVER_LOGGER).Log(address(this), msg.sender, "DyDxFlashLoanTaken", abi.encode(_receiver, _ethAmount, _encodedData)); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "./ProtocolInterface.sol"; import "../interfaces/ERC20.sol"; import "../interfaces/ITokenInterface.sol"; import "../interfaces/ComptrollerInterface.sol"; import "./dydx/ISoloMargin.sol"; import "./SavingsLogger.sol"; import "./dsr/DSRSavingsProtocol.sol"; import "./compound/CompoundSavingsProtocol.sol"; contract SavingsProxy is DSRSavingsProtocol, CompoundSavingsProtocol { address public constant ADAI_ADDRESS = 0xfC1E690f61EFd961294b3e1Ce3313fBD8aa4f85d; address public constant SAVINGS_DYDX_ADDRESS = 0x03b1565e070df392e48e7a8e01798C4B00E534A5; address public constant SAVINGS_AAVE_ADDRESS = 0x535B9035E9bA8D7efe0FeAEac885fb65b303E37C; address public constant NEW_IDAI_ADDRESS = 0x493C57C4763932315A328269E1ADaD09653B9081; address public constant COMP_ADDRESS = 0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B; address public constant SAVINGS_LOGGER_ADDRESS = 0x89b3635BD2bAD145C6f92E82C9e83f06D5654984; address public constant SOLO_MARGIN_ADDRESS = 0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e; enum SavingsProtocol {Compound, Dydx, Fulcrum, Dsr, Aave} function deposit(SavingsProtocol _protocol, uint256 _amount) public { if (_protocol == SavingsProtocol.Dsr) { dsrDeposit(_amount, true); } else if (_protocol == SavingsProtocol.Compound) { compDeposit(msg.sender, _amount); } else { _deposit(_protocol, _amount, true); } SavingsLogger(SAVINGS_LOGGER_ADDRESS).logDeposit(msg.sender, uint8(_protocol), _amount); } function withdraw(SavingsProtocol _protocol, uint256 _amount) public { if (_protocol == SavingsProtocol.Dsr) { dsrWithdraw(_amount, true); } else if (_protocol == SavingsProtocol.Compound) { compWithdraw(msg.sender, _amount); } else { _withdraw(_protocol, _amount, true); } SavingsLogger(SAVINGS_LOGGER_ADDRESS).logWithdraw(msg.sender, uint8(_protocol), _amount); } function swap(SavingsProtocol _from, SavingsProtocol _to, uint256 _amount) public { if (_from == SavingsProtocol.Dsr) { dsrWithdraw(_amount, false); } else if (_from == SavingsProtocol.Compound) { compWithdraw(msg.sender, _amount); } else { _withdraw(_from, _amount, false); } // possible to withdraw 1-2 wei less than actual amount due to division precision // so we deposit all amount on DSProxy uint256 amountToDeposit = ERC20(DAI_ADDRESS).balanceOf(address(this)); if (_to == SavingsProtocol.Dsr) { dsrDeposit(amountToDeposit, false); } else if (_from == SavingsProtocol.Compound) { compDeposit(msg.sender, _amount); } else { _deposit(_to, amountToDeposit, false); } SavingsLogger(SAVINGS_LOGGER_ADDRESS).logSwap( msg.sender, uint8(_from), uint8(_to), _amount ); } function withdrawDai() public { ERC20(DAI_ADDRESS).transfer(msg.sender, ERC20(DAI_ADDRESS).balanceOf(address(this))); } function claimComp() public { ComptrollerInterface(COMP_ADDRESS).claimComp(address(this)); } function getAddress(SavingsProtocol _protocol) public pure returns (address) { if (_protocol == SavingsProtocol.Dydx) { return SAVINGS_DYDX_ADDRESS; } if (_protocol == SavingsProtocol.Aave) { return SAVINGS_AAVE_ADDRESS; } } function _deposit(SavingsProtocol _protocol, uint256 _amount, bool _fromUser) internal { if (_fromUser) { ERC20(DAI_ADDRESS).transferFrom(msg.sender, address(this), _amount); } approveDeposit(_protocol); ProtocolInterface(getAddress(_protocol)).deposit(address(this), _amount); endAction(_protocol); } function _withdraw(SavingsProtocol _protocol, uint256 _amount, bool _toUser) public { approveWithdraw(_protocol); ProtocolInterface(getAddress(_protocol)).withdraw(address(this), _amount); endAction(_protocol); if (_toUser) { withdrawDai(); } } function endAction(SavingsProtocol _protocol) internal { if (_protocol == SavingsProtocol.Dydx) { setDydxOperator(false); } } function approveDeposit(SavingsProtocol _protocol) internal { if (_protocol == SavingsProtocol.Compound || _protocol == SavingsProtocol.Fulcrum || _protocol == SavingsProtocol.Aave) { ERC20(DAI_ADDRESS).approve(getAddress(_protocol), uint256(-1)); } if (_protocol == SavingsProtocol.Dydx) { ERC20(DAI_ADDRESS).approve(SOLO_MARGIN_ADDRESS, uint256(-1)); setDydxOperator(true); } } function approveWithdraw(SavingsProtocol _protocol) internal { if (_protocol == SavingsProtocol.Compound) { ERC20(NEW_CDAI_ADDRESS).approve(getAddress(_protocol), uint256(-1)); } if (_protocol == SavingsProtocol.Dydx) { setDydxOperator(true); } if (_protocol == SavingsProtocol.Fulcrum) { ERC20(NEW_IDAI_ADDRESS).approve(getAddress(_protocol), uint256(-1)); } if (_protocol == SavingsProtocol.Aave) { ERC20(ADAI_ADDRESS).approve(getAddress(_protocol), uint256(-1)); } } function setDydxOperator(bool _trusted) internal { ISoloMargin.OperatorArg[] memory operatorArgs = new ISoloMargin.OperatorArg[](1); operatorArgs[0] = ISoloMargin.OperatorArg({ operator: getAddress(SavingsProtocol.Dydx), trusted: _trusted }); ISoloMargin(SOLO_MARGIN_ADDRESS).setOperators(operatorArgs); } }
pragma solidity ^0.6.0; abstract contract ProtocolInterface { function deposit(address _user, uint256 _amount) public virtual; function withdraw(address _user, uint256 _amount) public virtual; }
pragma solidity ^0.6.0; import "./ERC20.sol"; abstract contract ITokenInterface is ERC20 { function assetBalanceOf(address _owner) public virtual view returns (uint256); function mint(address receiver, uint256 depositAmount) external virtual returns (uint256 mintAmount); function burn(address receiver, uint256 burnAmount) external virtual returns (uint256 loanAmountPaid); function tokenPrice() public virtual view returns (uint256 price); }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; abstract contract ComptrollerInterface { struct CompMarketState { uint224 index; uint32 block; } function claimComp(address holder) public virtual; function claimComp(address holder, address[] memory cTokens) public virtual; function claimComp(address[] memory holders, address[] memory cTokens, bool borrowers, bool suppliers) public virtual; function compSupplyState(address) public view virtual returns (CompMarketState memory); function compSupplierIndex(address,address) public view virtual returns (uint); function compAccrued(address) public view virtual returns (uint); function compBorrowState(address) public view virtual returns (CompMarketState memory); function compBorrowerIndex(address,address) public view virtual returns (uint); function enterMarkets(address[] calldata cTokens) external virtual returns (uint256[] memory); function exitMarket(address cToken) external virtual returns (uint256); function getAssetsIn(address account) external virtual view returns (address[] memory); function markets(address account) public virtual view returns (bool, uint256); function getAccountLiquidity(address account) external virtual view returns (uint256, uint256, uint256); function oracle() public virtual view returns (address); function borrowCaps(address) external virtual returns (uint256); }
pragma solidity ^0.6.0; contract SavingsLogger { event Deposit(address indexed sender, uint8 protocol, uint256 amount); event Withdraw(address indexed sender, uint8 protocol, uint256 amount); event Swap(address indexed sender, uint8 fromProtocol, uint8 toProtocol, uint256 amount); function logDeposit(address _sender, uint8 _protocol, uint256 _amount) external { emit Deposit(_sender, _protocol, _amount); } function logWithdraw(address _sender, uint8 _protocol, uint256 _amount) external { emit Withdraw(_sender, _protocol, _amount); } function logSwap(address _sender, uint8 _protocolFrom, uint8 _protocolTo, uint256 _amount) external { emit Swap(_sender, _protocolFrom, _protocolTo, _amount); } }
pragma solidity ^0.6.0; import "../../interfaces/Join.sol"; import "../../DS/DSMath.sol"; abstract contract VatLike { function can(address, address) virtual public view returns (uint); function ilks(bytes32) virtual public view returns (uint, uint, uint, uint, uint); function dai(address) virtual public view returns (uint); function urns(bytes32, address) virtual public view returns (uint, uint); function frob(bytes32, address, address, address, int, int) virtual public; function hope(address) virtual public; function move(address, address, uint) virtual public; } abstract contract PotLike { function pie(address) virtual public view returns (uint); function drip() virtual public returns (uint); function join(uint) virtual public; function exit(uint) virtual public; } abstract contract GemLike { function approve(address, uint) virtual public; function transfer(address, uint) virtual public; function transferFrom(address, address, uint) virtual public; function deposit() virtual public payable; function withdraw(uint) virtual public; } abstract contract DaiJoinLike { function vat() virtual public returns (VatLike); function dai() virtual public returns (GemLike); function join(address, uint) virtual public payable; function exit(address, uint) virtual public; } contract DSRSavingsProtocol is DSMath { // Mainnet address public constant POT_ADDRESS = 0x197E90f9FAD81970bA7976f33CbD77088E5D7cf7; address public constant DAI_JOIN_ADDRESS = 0x9759A6Ac90977b93B58547b4A71c78317f391A28; function dsrDeposit(uint _amount, bool _fromUser) internal { VatLike vat = DaiJoinLike(DAI_JOIN_ADDRESS).vat(); uint chi = PotLike(POT_ADDRESS).drip(); daiJoin_join(DAI_JOIN_ADDRESS, address(this), _amount, _fromUser); if (vat.can(address(this), address(POT_ADDRESS)) == 0) { vat.hope(POT_ADDRESS); } PotLike(POT_ADDRESS).join(mul(_amount, RAY) / chi); } function dsrWithdraw(uint _amount, bool _toUser) internal { VatLike vat = DaiJoinLike(DAI_JOIN_ADDRESS).vat(); uint chi = PotLike(POT_ADDRESS).drip(); uint pie = mul(_amount, RAY) / chi; PotLike(POT_ADDRESS).exit(pie); uint balance = DaiJoinLike(DAI_JOIN_ADDRESS).vat().dai(address(this)); if (vat.can(address(this), address(DAI_JOIN_ADDRESS)) == 0) { vat.hope(DAI_JOIN_ADDRESS); } address to; if (_toUser) { to = msg.sender; } else { to = address(this); } if (_amount == uint(-1)) { DaiJoinLike(DAI_JOIN_ADDRESS).exit(to, mul(chi, pie) / RAY); } else { DaiJoinLike(DAI_JOIN_ADDRESS).exit( to, balance >= mul(_amount, RAY) ? _amount : balance / RAY ); } } function daiJoin_join(address apt, address urn, uint wad, bool _fromUser) internal { if (_fromUser) { DaiJoinLike(apt).dai().transferFrom(msg.sender, address(this), wad); } DaiJoinLike(apt).dai().approve(apt, wad); DaiJoinLike(apt).join(urn, wad); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../ProtocolInterface.sol"; import "../../interfaces/CTokenInterface.sol"; import "../../compound/helpers/Exponential.sol"; import "../../interfaces/ERC20.sol"; contract CompoundSavingsProtocol { address public constant NEW_CDAI_ADDRESS = 0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643; address public constant DAI_ADDRESS = 0x6B175474E89094C44Da98b954EedeAC495271d0F; CTokenInterface public constant cDaiContract = CTokenInterface(NEW_CDAI_ADDRESS); function compDeposit(address _user, uint _amount) internal { // get dai from user require(ERC20(DAI_ADDRESS).transferFrom(_user, address(this), _amount)); // mainnet only ERC20(DAI_ADDRESS).approve(NEW_CDAI_ADDRESS, uint(-1)); // mint cDai require(cDaiContract.mint(_amount) == 0, "Failed Mint"); } function compWithdraw(address _user, uint _amount) internal { // transfer all users balance to this contract require(cDaiContract.transferFrom(_user, address(this), ERC20(NEW_CDAI_ADDRESS).balanceOf(_user))); // approve cDai to compound contract cDaiContract.approve(NEW_CDAI_ADDRESS, uint(-1)); // get dai from cDai contract require(cDaiContract.redeemUnderlying(_amount) == 0, "Reedem Failed"); // return to user balance we didn't spend uint cDaiBalance = cDaiContract.balanceOf(address(this)); if (cDaiBalance > 0) { cDaiContract.transfer(_user, cDaiBalance); } // return dai we have to user ERC20(DAI_ADDRESS).transfer(_user, _amount); } }
pragma solidity ^0.6.0; import "./Gem.sol"; abstract contract Join { bytes32 public ilk; function dec() virtual public view returns (uint); function gem() virtual public view returns (Gem); function join(address, uint) virtual public payable; function exit(address, uint) virtual public; }
pragma solidity ^0.6.0; abstract contract Gem { function dec() virtual public returns (uint); function gem() virtual public returns (Gem); function join(address, uint) virtual public payable; function exit(address, uint) virtual public; function approve(address, uint) virtual public; function transfer(address, uint) virtual public returns (bool); function transferFrom(address, address, uint) virtual public returns (bool); function deposit() virtual public payable; function withdraw(uint) virtual public; function allowance(address, address) virtual public returns (uint); }
pragma solidity ^0.6.0; import "./CarefulMath.sol"; contract Exponential is CarefulMath { uint constant expScale = 1e18; uint constant doubleScale = 1e36; uint constant halfExpScale = expScale/2; uint constant mantissaOne = expScale; struct Exp { uint mantissa; } struct Double { uint mantissa; } /** * @dev Creates an exponential from numerator and denominator values. * Note: Returns an error if (`num` * 10e18) > MAX_INT, * or if `denom` is zero. */ function getExp(uint num, uint denom) pure internal returns (MathError, Exp memory) { (MathError err0, uint scaledNumerator) = mulUInt(num, expScale); if (err0 != MathError.NO_ERROR) { return (err0, Exp({mantissa: 0})); } (MathError err1, uint rational) = divUInt(scaledNumerator, denom); if (err1 != MathError.NO_ERROR) { return (err1, Exp({mantissa: 0})); } return (MathError.NO_ERROR, Exp({mantissa: rational})); } /** * @dev Adds two exponentials, returning a new exponential. */ function addExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) { (MathError error, uint result) = addUInt(a.mantissa, b.mantissa); return (error, Exp({mantissa: result})); } /** * @dev Subtracts two exponentials, returning a new exponential. */ function subExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) { (MathError error, uint result) = subUInt(a.mantissa, b.mantissa); return (error, Exp({mantissa: result})); } /** * @dev Multiply an Exp by a scalar, returning a new Exp. */ function mulScalar(Exp memory a, uint scalar) pure internal returns (MathError, Exp memory) { (MathError err0, uint scaledMantissa) = mulUInt(a.mantissa, scalar); if (err0 != MathError.NO_ERROR) { return (err0, Exp({mantissa: 0})); } return (MathError.NO_ERROR, Exp({mantissa: scaledMantissa})); } /** * @dev Multiply an Exp by a scalar, then truncate to return an unsigned integer. */ function mulScalarTruncate(Exp memory a, uint scalar) pure internal returns (MathError, uint) { (MathError err, Exp memory product) = mulScalar(a, scalar); if (err != MathError.NO_ERROR) { return (err, 0); } return (MathError.NO_ERROR, truncate(product)); } /** * @dev Multiply an Exp by a scalar, truncate, then add an to an unsigned integer, returning an unsigned integer. */ function mulScalarTruncateAddUInt(Exp memory a, uint scalar, uint addend) pure internal returns (MathError, uint) { (MathError err, Exp memory product) = mulScalar(a, scalar); if (err != MathError.NO_ERROR) { return (err, 0); } return addUInt(truncate(product), addend); } /** * @dev Divide an Exp by a scalar, returning a new Exp. */ function divScalar(Exp memory a, uint scalar) pure internal returns (MathError, Exp memory) { (MathError err0, uint descaledMantissa) = divUInt(a.mantissa, scalar); if (err0 != MathError.NO_ERROR) { return (err0, Exp({mantissa: 0})); } return (MathError.NO_ERROR, Exp({mantissa: descaledMantissa})); } /** * @dev Divide a scalar by an Exp, returning a new Exp. */ function divScalarByExp(uint scalar, Exp memory divisor) pure internal returns (MathError, Exp memory) { /* We are doing this as: getExp(mulUInt(expScale, scalar), divisor.mantissa) How it works: Exp = a / b; Scalar = s; `s / (a / b)` = `b * s / a` and since for an Exp `a = mantissa, b = expScale` */ (MathError err0, uint numerator) = mulUInt(expScale, scalar); if (err0 != MathError.NO_ERROR) { return (err0, Exp({mantissa: 0})); } return getExp(numerator, divisor.mantissa); } /** * @dev Divide a scalar by an Exp, then truncate to return an unsigned integer. */ function divScalarByExpTruncate(uint scalar, Exp memory divisor) pure internal returns (MathError, uint) { (MathError err, Exp memory fraction) = divScalarByExp(scalar, divisor); if (err != MathError.NO_ERROR) { return (err, 0); } return (MathError.NO_ERROR, truncate(fraction)); } /** * @dev Multiplies two exponentials, returning a new exponential. */ function mulExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) { (MathError err0, uint doubleScaledProduct) = mulUInt(a.mantissa, b.mantissa); if (err0 != MathError.NO_ERROR) { return (err0, Exp({mantissa: 0})); } // We add half the scale before dividing so that we get rounding instead of truncation. // See "Listing 6" and text above it at https://accu.org/index.php/journals/1717 // Without this change, a result like 6.6...e-19 will be truncated to 0 instead of being rounded to 1e-18. (MathError err1, uint doubleScaledProductWithHalfScale) = addUInt(halfExpScale, doubleScaledProduct); if (err1 != MathError.NO_ERROR) { return (err1, Exp({mantissa: 0})); } (MathError err2, uint product) = divUInt(doubleScaledProductWithHalfScale, expScale); // The only error `div` can return is MathError.DIVISION_BY_ZERO but we control `expScale` and it is not zero. assert(err2 == MathError.NO_ERROR); return (MathError.NO_ERROR, Exp({mantissa: product})); } /** * @dev Multiplies two exponentials given their mantissas, returning a new exponential. */ function mulExp(uint a, uint b) pure internal returns (MathError, Exp memory) { return mulExp(Exp({mantissa: a}), Exp({mantissa: b})); } /** * @dev Multiplies three exponentials, returning a new exponential. */ function mulExp3(Exp memory a, Exp memory b, Exp memory c) pure internal returns (MathError, Exp memory) { (MathError err, Exp memory ab) = mulExp(a, b); if (err != MathError.NO_ERROR) { return (err, ab); } return mulExp(ab, c); } /** * @dev Divides two exponentials, returning a new exponential. * (a/scale) / (b/scale) = (a/scale) * (scale/b) = a/b, * which we can scale as an Exp by calling getExp(a.mantissa, b.mantissa) */ function divExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) { return getExp(a.mantissa, b.mantissa); } /** * @dev Truncates the given exp to a whole number value. * For example, truncate(Exp{mantissa: 15 * expScale}) = 15 */ function truncate(Exp memory exp) pure internal returns (uint) { // Note: We are not using careful math here as we're performing a division that cannot fail return exp.mantissa / expScale; } /** * @dev Checks if first Exp is less than second Exp. */ function lessThanExp(Exp memory left, Exp memory right) pure internal returns (bool) { return left.mantissa < right.mantissa; } /** * @dev Checks if left Exp <= right Exp. */ function lessThanOrEqualExp(Exp memory left, Exp memory right) pure internal returns (bool) { return left.mantissa <= right.mantissa; } /** * @dev Checks if left Exp > right Exp. */ function greaterThanExp(Exp memory left, Exp memory right) pure internal returns (bool) { return left.mantissa > right.mantissa; } /** * @dev returns true if Exp is exactly zero */ function isZeroExp(Exp memory value) pure internal returns (bool) { return value.mantissa == 0; } function sub_(Exp memory a, Exp memory b) pure internal returns (Exp memory) { return Exp({mantissa: sub_(a.mantissa, b.mantissa)}); } function sub_(Double memory a, Double memory b) pure internal returns (Double memory) { return Double({mantissa: sub_(a.mantissa, b.mantissa)}); } function sub_(uint a, uint b) pure internal returns (uint) { return sub_(a, b, "subtraction underflow"); } function sub_(uint a, uint b, string memory errorMessage) pure internal returns (uint) { require(b <= a, errorMessage); return a - b; } function mul_(Exp memory a, Exp memory b) pure internal returns (Exp memory) { return Exp({mantissa: mul_(a.mantissa, b.mantissa) / expScale}); } function mul_(Exp memory a, uint b) pure internal returns (Exp memory) { return Exp({mantissa: mul_(a.mantissa, b)}); } function mul_(uint a, Exp memory b) pure internal returns (uint) { return mul_(a, b.mantissa) / expScale; } function mul_(Double memory a, Double memory b) pure internal returns (Double memory) { return Double({mantissa: mul_(a.mantissa, b.mantissa) / doubleScale}); } function mul_(Double memory a, uint b) pure internal returns (Double memory) { return Double({mantissa: mul_(a.mantissa, b)}); } function mul_(uint a, Double memory b) pure internal returns (uint) { return mul_(a, b.mantissa) / doubleScale; } function mul_(uint a, uint b) pure internal returns (uint) { return mul_(a, b, "multiplication overflow"); } function mul_(uint a, uint b, string memory errorMessage) pure internal returns (uint) { if (a == 0 || b == 0) { return 0; } uint c = a * b; require(c / a == b, errorMessage); return c; } function div_(Exp memory a, Exp memory b) pure internal returns (Exp memory) { return Exp({mantissa: div_(mul_(a.mantissa, expScale), b.mantissa)}); } function div_(Exp memory a, uint b) pure internal returns (Exp memory) { return Exp({mantissa: div_(a.mantissa, b)}); } function div_(uint a, Exp memory b) pure internal returns (uint) { return div_(mul_(a, expScale), b.mantissa); } function div_(Double memory a, Double memory b) pure internal returns (Double memory) { return Double({mantissa: div_(mul_(a.mantissa, doubleScale), b.mantissa)}); } function div_(Double memory a, uint b) pure internal returns (Double memory) { return Double({mantissa: div_(a.mantissa, b)}); } function div_(uint a, Double memory b) pure internal returns (uint) { return div_(mul_(a, doubleScale), b.mantissa); } function div_(uint a, uint b) pure internal returns (uint) { return div_(a, b, "divide by zero"); } function div_(uint a, uint b, string memory errorMessage) pure internal returns (uint) { require(b > 0, errorMessage); return a / b; } function add_(Exp memory a, Exp memory b) pure internal returns (Exp memory) { return Exp({mantissa: add_(a.mantissa, b.mantissa)}); } function add_(Double memory a, Double memory b) pure internal returns (Double memory) { return Double({mantissa: add_(a.mantissa, b.mantissa)}); } function add_(uint a, uint b) pure internal returns (uint) { return add_(a, b, "addition overflow"); } function add_(uint a, uint b, string memory errorMessage) pure internal returns (uint) { uint c = a + b; require(c >= a, errorMessage); return c; } }
pragma solidity ^0.6.0; contract CarefulMath { /** * @dev Possible error codes that we can return */ enum MathError { NO_ERROR, DIVISION_BY_ZERO, INTEGER_OVERFLOW, INTEGER_UNDERFLOW } /** * @dev Multiplies two numbers, returns an error on overflow. */ function mulUInt(uint a, uint b) internal pure returns (MathError, uint) { if (a == 0) { return (MathError.NO_ERROR, 0); } uint c = a * b; if (c / a != b) { return (MathError.INTEGER_OVERFLOW, 0); } else { return (MathError.NO_ERROR, c); } } /** * @dev Integer division of two numbers, truncating the quotient. */ function divUInt(uint a, uint b) internal pure returns (MathError, uint) { if (b == 0) { return (MathError.DIVISION_BY_ZERO, 0); } return (MathError.NO_ERROR, a / b); } /** * @dev Subtracts two numbers, returns an error on overflow (i.e. if subtrahend is greater than minuend). */ function subUInt(uint a, uint b) internal pure returns (MathError, uint) { if (b <= a) { return (MathError.NO_ERROR, a - b); } else { return (MathError.INTEGER_UNDERFLOW, 0); } } /** * @dev Adds two numbers, returns an error on overflow. */ function addUInt(uint a, uint b) internal pure returns (MathError, uint) { uint c = a + b; if (c >= a) { return (MathError.NO_ERROR, c); } else { return (MathError.INTEGER_OVERFLOW, 0); } } /** * @dev add a and b and then subtract c */ function addThenSubUInt(uint a, uint b, uint c) internal pure returns (MathError, uint) { (MathError err0, uint sum) = addUInt(a, b); if (err0 != MathError.NO_ERROR) { return (err0, 0); } return subUInt(sum, c); } }
pragma solidity ^0.6.0; import "../../interfaces/CEtherInterface.sol"; import "../../interfaces/CompoundOracleInterface.sol"; import "../../interfaces/CTokenInterface.sol"; import "../../interfaces/ComptrollerInterface.sol"; import "../../interfaces/IFeeRecipient.sol"; import "../../utils/Discount.sol"; import "../../DS/DSMath.sol"; import "../../DS/DSProxy.sol"; import "../../compound/helpers/Exponential.sol"; import "../../utils/BotRegistry.sol"; import "../../utils/SafeERC20.sol"; /// @title Utlity functions for cream contracts contract CreamSaverHelper is DSMath, Exponential { using SafeERC20 for ERC20; IFeeRecipient public constant feeRecipient = IFeeRecipient(0x39C4a92Dc506300c3Ea4c67ca4CA611102ee6F2A); address public constant DISCOUNT_ADDR = 0x1b14E8D511c9A4395425314f849bD737BAF8208F; uint public constant MANUAL_SERVICE_FEE = 400; // 0.25% Fee uint public constant AUTOMATIC_SERVICE_FEE = 333; // 0.3% Fee address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant CETH_ADDRESS = 0xD06527D5e56A3495252A528C4987003b712860eE; address public constant COMPTROLLER = 0x3d5BC3c8d13dcB8bF317092d84783c2697AE9258; address public constant BOT_REGISTRY_ADDRESS = 0x637726f8b08a7ABE3aE3aCaB01A80E2d8ddeF77B; /// @notice Helper method to payback the cream debt /// @dev If amount is bigger it will repay the whole debt and send the extra to the _user /// @param _amount Amount of tokens we want to repay /// @param _cBorrowToken Ctoken address we are repaying /// @param _borrowToken Token address we are repaying /// @param _user Owner of the cream position we are paying back function paybackDebt(uint _amount, address _cBorrowToken, address _borrowToken, address payable _user) internal { uint wholeDebt = CTokenInterface(_cBorrowToken).borrowBalanceCurrent(address(this)); if (_amount > wholeDebt) { if (_borrowToken == ETH_ADDRESS) { _user.transfer((_amount - wholeDebt)); } else { ERC20(_borrowToken).safeTransfer(_user, (_amount - wholeDebt)); } _amount = wholeDebt; } approveCToken(_borrowToken, _cBorrowToken); if (_borrowToken == ETH_ADDRESS) { CEtherInterface(_cBorrowToken).repayBorrow{value: _amount}(); } else { require(CTokenInterface(_cBorrowToken).repayBorrow(_amount) == 0); } } /// @notice Calculates the fee amount /// @param _amount Amount that is converted /// @param _user Actuall user addr not DSProxy /// @param _gasCost Ether amount of gas we are spending for tx /// @param _cTokenAddr CToken addr. of token we are getting for the fee /// @return feeAmount The amount we took for the fee function getFee(uint _amount, address _user, uint _gasCost, address _cTokenAddr) internal returns (uint feeAmount) { uint fee = MANUAL_SERVICE_FEE; if (BotRegistry(BOT_REGISTRY_ADDRESS).botList(tx.origin)) { fee = AUTOMATIC_SERVICE_FEE; } address tokenAddr = getUnderlyingAddr(_cTokenAddr); if (Discount(DISCOUNT_ADDR).isCustomFeeSet(_user)) { fee = Discount(DISCOUNT_ADDR).getCustomServiceFee(_user); } feeAmount = (fee == 0) ? 0 : (_amount / fee); if (_gasCost != 0) { address oracle = ComptrollerInterface(COMPTROLLER).oracle(); uint ethTokenPrice = CompoundOracleInterface(oracle).getUnderlyingPrice(_cTokenAddr); _gasCost = wdiv(_gasCost, ethTokenPrice); feeAmount = add(feeAmount, _gasCost); } // fee can't go over 20% of the whole amount if (feeAmount > (_amount / 5)) { feeAmount = _amount / 5; } address walletAddr = feeRecipient.getFeeAddr(); if (tokenAddr == ETH_ADDRESS) { payable(walletAddr).transfer(feeAmount); } else { ERC20(tokenAddr).safeTransfer(walletAddr, feeAmount); } } /// @notice Calculates the gas cost of transaction and send it to wallet /// @param _amount Amount that is converted /// @param _gasCost Ether amount of gas we are spending for tx /// @param _cTokenAddr CToken addr. of token we are getting for the fee /// @return feeAmount The amount we took for the fee function getGasCost(uint _amount, uint _gasCost, address _cTokenAddr) internal returns (uint feeAmount) { address tokenAddr = getUnderlyingAddr(_cTokenAddr); if (_gasCost != 0) { address oracle = ComptrollerInterface(COMPTROLLER).oracle(); uint ethTokenPrice = CompoundOracleInterface(oracle).getUnderlyingPrice(_cTokenAddr); feeAmount = wdiv(_gasCost, ethTokenPrice); } // fee can't go over 20% of the whole amount if (feeAmount > (_amount / 5)) { feeAmount = _amount / 5; } address walletAddr = feeRecipient.getFeeAddr(); if (tokenAddr == ETH_ADDRESS) { payable(walletAddr).transfer(feeAmount); } else { ERC20(tokenAddr).safeTransfer(walletAddr, feeAmount); } } /// @notice Enters the market for the collatera and borrow tokens /// @param _cTokenAddrColl Collateral address we are entering the market in /// @param _cTokenAddrBorrow Borrow address we are entering the market in function enterMarket(address _cTokenAddrColl, address _cTokenAddrBorrow) internal { address[] memory markets = new address[](2); markets[0] = _cTokenAddrColl; markets[1] = _cTokenAddrBorrow; ComptrollerInterface(COMPTROLLER).enterMarkets(markets); } /// @notice Approves CToken contract to pull underlying tokens from the DSProxy /// @param _tokenAddr Token we are trying to approve /// @param _cTokenAddr Address which will gain the approval function approveCToken(address _tokenAddr, address _cTokenAddr) internal { if (_tokenAddr != ETH_ADDRESS) { ERC20(_tokenAddr).safeApprove(_cTokenAddr, uint(-1)); } } /// @notice Returns the underlying address of the cToken asset /// @param _cTokenAddress cToken address /// @return Token address of the cToken specified function getUnderlyingAddr(address _cTokenAddress) internal returns (address) { if (_cTokenAddress == CETH_ADDRESS) { return ETH_ADDRESS; } else { return CTokenInterface(_cTokenAddress).underlying(); } } /// @notice Returns the owner of the DSProxy that called the contract function getUserAddress() internal view returns (address) { DSProxy proxy = DSProxy(uint160(address(this))); return proxy.owner(); } /// @notice Returns the maximum amount of collateral available to withdraw /// @dev Due to rounding errors the result is - 1% wei from the exact amount /// @param _cCollAddress Collateral we are getting the max value of /// @param _account Users account /// @return Returns the max. collateral amount in that token function getMaxCollateral(address _cCollAddress, address _account) public returns (uint) { (, uint liquidityInEth, ) = ComptrollerInterface(COMPTROLLER).getAccountLiquidity(_account); uint usersBalance = CTokenInterface(_cCollAddress).balanceOfUnderlying(_account); address oracle = ComptrollerInterface(COMPTROLLER).oracle(); if (liquidityInEth == 0) return usersBalance; CTokenInterface(_cCollAddress).accrueInterest(); if (_cCollAddress == CETH_ADDRESS) { if (liquidityInEth > usersBalance) return usersBalance; return sub(liquidityInEth, (liquidityInEth / 100)); } uint ethPrice = CompoundOracleInterface(oracle).getUnderlyingPrice(_cCollAddress); uint liquidityInToken = wdiv(liquidityInEth, ethPrice); if (liquidityInToken > usersBalance) return usersBalance; return sub(liquidityInToken, (liquidityInToken / 100)); // cut off 1% due to rounding issues } /// @notice Returns the maximum amount of borrow amount available /// @dev Due to rounding errors the result is - 1% wei from the exact amount /// @param _cBorrowAddress Borrow token we are getting the max value of /// @param _account Users account /// @return Returns the max. borrow amount in that token function getMaxBorrow(address _cBorrowAddress, address _account) public returns (uint) { (, uint liquidityInEth, ) = ComptrollerInterface(COMPTROLLER).getAccountLiquidity(_account); address oracle = ComptrollerInterface(COMPTROLLER).oracle(); CTokenInterface(_cBorrowAddress).accrueInterest(); if (_cBorrowAddress == CETH_ADDRESS) return sub(liquidityInEth, (liquidityInEth / 100)); uint ethPrice = CompoundOracleInterface(oracle).getUnderlyingPrice(_cBorrowAddress); uint liquidityInToken = wdiv(liquidityInEth, ethPrice); return sub(liquidityInToken, (liquidityInToken / 100)); // cut off 1% due to rounding issues } }
pragma solidity ^0.6.0; abstract contract CEtherInterface { function mint() external virtual payable; function repayBorrow() external virtual payable; }
pragma solidity ^0.6.0; abstract contract CompoundOracleInterface { function getUnderlyingPrice(address cToken) external view virtual returns (uint); }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../utils/SafeERC20.sol"; import "../../exchange/SaverExchangeCore.sol"; import "../../interfaces/CTokenInterface.sol"; import "../../utils/Discount.sol"; import "../helpers/CreamSaverHelper.sol"; import "../../loggers/DefisaverLogger.sol"; /// @title Implements the actual logic of Repay/Boost with FL contract CreamSaverFlashProxy is SaverExchangeCore, CreamSaverHelper { address public constant DEFISAVER_LOGGER = 0x5c55B921f590a89C1Ebe84dF170E655a82b62126; using SafeERC20 for ERC20; /// @notice Repays the position and sends tokens back for FL /// @param _exData Exchange data /// @param _cAddresses cTokens addreses and exchange [cCollAddress, cBorrowAddress] /// @param _gasCost Gas cost for transaction /// @param _flashLoanData Data about FL [amount, fee] function flashRepay( ExchangeData memory _exData, address[2] memory _cAddresses, // cCollAddress, cBorrowAddress uint256 _gasCost, uint[2] memory _flashLoanData // amount, fee ) public payable { enterMarket(_cAddresses[0], _cAddresses[1]); address payable user = payable(getUserAddress()); uint flashBorrowed = _flashLoanData[0] + _flashLoanData[1]; uint maxColl = getMaxCollateral(_cAddresses[0], address(this)); // draw max coll require(CTokenInterface(_cAddresses[0]).redeemUnderlying(maxColl) == 0); address collToken = getUnderlyingAddr(_cAddresses[0]); address borrowToken = getUnderlyingAddr(_cAddresses[1]); uint swapAmount = 0; if (collToken != borrowToken) { // swap max coll + loanAmount _exData.srcAmount = maxColl + _flashLoanData[0]; (,swapAmount) = _sell(_exData); // get fee swapAmount -= getFee(swapAmount, user, _gasCost, _cAddresses[1]); } else { swapAmount = (maxColl + _flashLoanData[0]); swapAmount -= getGasCost(swapAmount, _gasCost, _cAddresses[1]); } // payback debt paybackDebt(swapAmount, _cAddresses[1], borrowToken, user); // draw collateral for loanAmount + loanFee require(CTokenInterface(_cAddresses[0]).redeemUnderlying(flashBorrowed) == 0); // repay flash loan returnFlashLoan(collToken, flashBorrowed); DefisaverLogger(DEFISAVER_LOGGER).Log(address(this), msg.sender, "CreamRepay", abi.encode(_exData.srcAmount, swapAmount, collToken, borrowToken)); } /// @notice Boosts the position and sends tokens back for FL /// @param _exData Exchange data /// @param _cAddresses cTokens addreses and exchange [cCollAddress, cBorrowAddress] /// @param _gasCost Gas cost for specific transaction /// @param _flashLoanData Data about FL [amount, fee] function flashBoost( ExchangeData memory _exData, address[2] memory _cAddresses, // cCollAddress, cBorrowAddress uint256 _gasCost, uint[2] memory _flashLoanData // amount, fee ) public payable { enterMarket(_cAddresses[0], _cAddresses[1]); address payable user = payable(getUserAddress()); uint flashBorrowed = _flashLoanData[0] + _flashLoanData[1]; // borrow max amount uint borrowAmount = getMaxBorrow(_cAddresses[1], address(this)); require(CTokenInterface(_cAddresses[1]).borrow(borrowAmount) == 0); address collToken = getUnderlyingAddr(_cAddresses[0]); address borrowToken = getUnderlyingAddr(_cAddresses[1]); uint swapAmount = 0; if (collToken != borrowToken) { // get dfs fee borrowAmount -= getFee((borrowAmount + _flashLoanData[0]), user, _gasCost, _cAddresses[1]); _exData.srcAmount = (borrowAmount + _flashLoanData[0]); (,swapAmount) = _sell(_exData); } else { swapAmount = (borrowAmount + _flashLoanData[0]); swapAmount -= getGasCost(swapAmount, _gasCost, _cAddresses[1]); } // deposit swaped collateral depositCollateral(collToken, _cAddresses[0], swapAmount); // borrow token to repay flash loan require(CTokenInterface(_cAddresses[1]).borrow(flashBorrowed) == 0); // repay flash loan returnFlashLoan(borrowToken, flashBorrowed); DefisaverLogger(DEFISAVER_LOGGER).Log(address(this), msg.sender, "CreamBoost", abi.encode(_exData.srcAmount, swapAmount, collToken, borrowToken)); } /// @notice Helper method to deposit tokens in Compound /// @param _collToken Token address of the collateral /// @param _cCollToken CToken address of the collateral /// @param _depositAmount Amount to deposit function depositCollateral(address _collToken, address _cCollToken, uint _depositAmount) internal { approveCToken(_collToken, _cCollToken); if (_collToken != ETH_ADDRESS) { require(CTokenInterface(_cCollToken).mint(_depositAmount) == 0); } else { CEtherInterface(_cCollToken).mint{value: _depositAmount}(); // reverts on fail } } /// @notice Returns the tokens/ether to the msg.sender which is the FL contract /// @param _tokenAddr Address of token which we return /// @param _amount Amount to return function returnFlashLoan(address _tokenAddr, uint _amount) internal { if (_tokenAddr != ETH_ADDRESS) { ERC20(_tokenAddr).safeTransfer(msg.sender, _amount); } msg.sender.transfer(address(this).balance); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../DS/DSMath.sol"; import "../interfaces/TokenInterface.sol"; import "../interfaces/ExchangeInterfaceV2.sol"; import "../utils/ZrxAllowlist.sol"; import "./SaverExchangeHelper.sol"; import "./SaverExchangeRegistry.sol"; contract SaverExchangeCore is SaverExchangeHelper, DSMath { // first is empty to keep the legacy order in place enum ExchangeType { _, OASIS, KYBER, UNISWAP, ZEROX } enum ActionType { SELL, BUY } struct ExchangeData { address srcAddr; address destAddr; uint srcAmount; uint destAmount; uint minPrice; address wrapper; address exchangeAddr; bytes callData; uint256 price0x; } /// @notice Internal method that preforms a sell on 0x/on-chain /// @dev Usefull for other DFS contract to integrate for exchanging /// @param exData Exchange data struct /// @return (address, uint) Address of the wrapper used and destAmount function _sell(ExchangeData memory exData) internal returns (address, uint) { address wrapper; uint swapedTokens; bool success; uint tokensLeft = exData.srcAmount; // if selling eth, convert to weth if (exData.srcAddr == KYBER_ETH_ADDRESS) { exData.srcAddr = ethToWethAddr(exData.srcAddr); TokenInterface(WETH_ADDRESS).deposit.value(exData.srcAmount)(); } // Try 0x first and then fallback on specific wrapper if (exData.price0x > 0) { approve0xProxy(exData.srcAddr, exData.srcAmount); uint ethAmount = getProtocolFee(exData.srcAddr, exData.srcAmount); (success, swapedTokens, tokensLeft) = takeOrder(exData, ethAmount, ActionType.SELL); if (success) { wrapper = exData.exchangeAddr; } } // fallback to desired wrapper if 0x failed if (!success) { swapedTokens = saverSwap(exData, ActionType.SELL); wrapper = exData.wrapper; } require(getBalance(exData.destAddr) >= wmul(exData.minPrice, exData.srcAmount), "Final amount isn't correct"); // if anything is left in weth, pull it to user as eth if (getBalance(WETH_ADDRESS) > 0) { TokenInterface(WETH_ADDRESS).withdraw( TokenInterface(WETH_ADDRESS).balanceOf(address(this)) ); } return (wrapper, swapedTokens); } /// @notice Internal method that preforms a buy on 0x/on-chain /// @dev Usefull for other DFS contract to integrate for exchanging /// @param exData Exchange data struct /// @return (address, uint) Address of the wrapper used and srcAmount function _buy(ExchangeData memory exData) internal returns (address, uint) { address wrapper; uint swapedTokens; bool success; require(exData.destAmount != 0, "Dest amount must be specified"); // if selling eth, convert to weth if (exData.srcAddr == KYBER_ETH_ADDRESS) { exData.srcAddr = ethToWethAddr(exData.srcAddr); TokenInterface(WETH_ADDRESS).deposit.value(exData.srcAmount)(); } if (exData.price0x > 0) { approve0xProxy(exData.srcAddr, exData.srcAmount); uint ethAmount = getProtocolFee(exData.srcAddr, exData.srcAmount); (success, swapedTokens,) = takeOrder(exData, ethAmount, ActionType.BUY); if (success) { wrapper = exData.exchangeAddr; } } // fallback to desired wrapper if 0x failed if (!success) { swapedTokens = saverSwap(exData, ActionType.BUY); wrapper = exData.wrapper; } require(getBalance(exData.destAddr) >= exData.destAmount, "Final amount isn't correct"); // if anything is left in weth, pull it to user as eth if (getBalance(WETH_ADDRESS) > 0) { TokenInterface(WETH_ADDRESS).withdraw( TokenInterface(WETH_ADDRESS).balanceOf(address(this)) ); } return (wrapper, getBalance(exData.destAddr)); } /// @notice Takes order from 0x and returns bool indicating if it is successful /// @param _exData Exchange data /// @param _ethAmount Ether fee needed for 0x order function takeOrder( ExchangeData memory _exData, uint256 _ethAmount, ActionType _type ) private returns (bool success, uint256, uint256) { // write in the exact amount we are selling/buing in an order if (_type == ActionType.SELL) { writeUint256(_exData.callData, 36, _exData.srcAmount); } else { writeUint256(_exData.callData, 36, _exData.destAmount); } if (ZrxAllowlist(ZRX_ALLOWLIST_ADDR).isNonPayableAddr(_exData.exchangeAddr)) { _ethAmount = 0; } uint256 tokensBefore = getBalance(_exData.destAddr); if (ZrxAllowlist(ZRX_ALLOWLIST_ADDR).isZrxAddr(_exData.exchangeAddr)) { (success, ) = _exData.exchangeAddr.call{value: _ethAmount}(_exData.callData); } else { success = false; } uint256 tokensSwaped = 0; uint256 tokensLeft = _exData.srcAmount; if (success) { // check to see if any _src tokens are left over after exchange tokensLeft = getBalance(_exData.srcAddr); // convert weth -> eth if needed if (_exData.destAddr == KYBER_ETH_ADDRESS) { TokenInterface(WETH_ADDRESS).withdraw( TokenInterface(WETH_ADDRESS).balanceOf(address(this)) ); } // get the current balance of the swaped tokens tokensSwaped = getBalance(_exData.destAddr) - tokensBefore; } return (success, tokensSwaped, tokensLeft); } /// @notice Calls wraper contract for exchage to preform an on-chain swap /// @param _exData Exchange data struct /// @param _type Type of action SELL|BUY /// @return swapedTokens For Sell that the destAmount, for Buy thats the srcAmount function saverSwap(ExchangeData memory _exData, ActionType _type) internal returns (uint swapedTokens) { require(SaverExchangeRegistry(SAVER_EXCHANGE_REGISTRY).isWrapper(_exData.wrapper), "Wrapper is not valid"); uint ethValue = 0; ERC20(_exData.srcAddr).safeTransfer(_exData.wrapper, _exData.srcAmount); if (_type == ActionType.SELL) { swapedTokens = ExchangeInterfaceV2(_exData.wrapper). sell{value: ethValue}(_exData.srcAddr, _exData.destAddr, _exData.srcAmount); } else { swapedTokens = ExchangeInterfaceV2(_exData.wrapper). buy{value: ethValue}(_exData.srcAddr, _exData.destAddr, _exData.destAmount); } } function writeUint256(bytes memory _b, uint256 _index, uint _input) internal pure { if (_b.length < _index + 32) { revert("Incorrent lengt while writting bytes32"); } bytes32 input = bytes32(_input); _index += 32; // Read the bytes32 from array memory assembly { mstore(add(_b, _index), input) } } /// @notice Converts Kybers Eth address -> Weth /// @param _src Input address function ethToWethAddr(address _src) internal pure returns (address) { return _src == KYBER_ETH_ADDRESS ? WETH_ADDRESS : _src; } /// @notice Calculates protocol fee /// @param _srcAddr selling token address (if eth should be WETH) /// @param _srcAmount amount we are selling function getProtocolFee(address _srcAddr, uint256 _srcAmount) internal view returns(uint256) { // if we are not selling ETH msg value is always the protocol fee if (_srcAddr != WETH_ADDRESS) return address(this).balance; // if msg value is larger than srcAmount, that means that msg value is protocol fee + srcAmount, so we subsctract srcAmount from msg value // we have an edge case here when protocol fee is higher than selling amount if (address(this).balance > _srcAmount) return address(this).balance - _srcAmount; // if msg value is lower than src amount, that means that srcAmount isn't included in msg value, so we return msg value return address(this).balance; } function packExchangeData(ExchangeData memory _exData) public pure returns(bytes memory) { // splitting in two different bytes and encoding all because of stack too deep in decoding part bytes memory part1 = abi.encode( _exData.srcAddr, _exData.destAddr, _exData.srcAmount, _exData.destAmount ); bytes memory part2 = abi.encode( _exData.minPrice, _exData.wrapper, _exData.exchangeAddr, _exData.callData, _exData.price0x ); return abi.encode(part1, part2); } function unpackExchangeData(bytes memory _data) public pure returns(ExchangeData memory _exData) { ( bytes memory part1, bytes memory part2 ) = abi.decode(_data, (bytes,bytes)); ( _exData.srcAddr, _exData.destAddr, _exData.srcAmount, _exData.destAmount ) = abi.decode(part1, (address,address,uint256,uint256)); ( _exData.minPrice, _exData.wrapper, _exData.exchangeAddr, _exData.callData, _exData.price0x ) = abi.decode(part2, (uint256,address,address,bytes,uint256)); } // solhint-disable-next-line no-empty-blocks receive() external virtual payable {} }
pragma solidity ^0.6.0; interface ExchangeInterfaceV2 { function sell(address _srcAddr, address _destAddr, uint _srcAmount) external payable returns (uint); function buy(address _srcAddr, address _destAddr, uint _destAmount) external payable returns(uint); function getSellRate(address _srcAddr, address _destAddr, uint _srcAmount) external view returns (uint); function getBuyRate(address _srcAddr, address _destAddr, uint _srcAmount) external view returns (uint); }
pragma solidity ^0.6.0; import "../utils/SafeERC20.sol"; import "../utils/Discount.sol"; contract SaverExchangeHelper { using SafeERC20 for ERC20; address public constant KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address public constant DISCOUNT_ADDRESS = 0x1b14E8D511c9A4395425314f849bD737BAF8208F; address public constant SAVER_EXCHANGE_REGISTRY = 0x25dd3F51e0C3c3Ff164DDC02A8E4D65Bb9cBB12D; address public constant ERC20_PROXY_0X = 0x95E6F48254609A6ee006F7D493c8e5fB97094ceF; address public constant ZRX_ALLOWLIST_ADDR = 0x4BA1f38427b33B8ab7Bb0490200dAE1F1C36823F; function getDecimals(address _token) internal view returns (uint256) { if (_token == KYBER_ETH_ADDRESS) return 18; return ERC20(_token).decimals(); } function getBalance(address _tokenAddr) internal view returns (uint balance) { if (_tokenAddr == KYBER_ETH_ADDRESS) { balance = address(this).balance; } else { balance = ERC20(_tokenAddr).balanceOf(address(this)); } } function approve0xProxy(address _tokenAddr, uint _amount) internal { if (_tokenAddr != KYBER_ETH_ADDRESS) { ERC20(_tokenAddr).safeApprove(address(ERC20_PROXY_0X), _amount); } } function sendLeftover(address _srcAddr, address _destAddr, address payable _to) internal { // send back any leftover ether or tokens if (address(this).balance > 0) { _to.transfer(address(this).balance); } if (getBalance(_srcAddr) > 0) { ERC20(_srcAddr).safeTransfer(_to, getBalance(_srcAddr)); } if (getBalance(_destAddr) > 0) { ERC20(_destAddr).safeTransfer(_to, getBalance(_destAddr)); } } function sliceUint(bytes memory bs, uint256 start) internal pure returns (uint256) { require(bs.length >= start + 32, "slicing out of range"); uint256 x; assembly { x := mload(add(bs, add(0x20, start))) } return x; } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../loggers/DefisaverLogger.sol"; import "../../utils/Discount.sol"; import "../../interfaces/Spotter.sol"; import "../../interfaces/Jug.sol"; import "../../interfaces/DaiJoin.sol"; import "../../interfaces/Join.sol"; import "./MCDSaverProxyHelper.sol"; import "../../utils/BotRegistry.sol"; import "../../exchangeV3/DFSExchangeCore.sol"; /// @title Implements Boost and Repay for MCD CDPs contract MCDSaverProxy is DFSExchangeCore, MCDSaverProxyHelper { uint public constant MANUAL_SERVICE_FEE = 400; // 0.25% Fee uint public constant AUTOMATIC_SERVICE_FEE = 333; // 0.3% Fee bytes32 public constant ETH_ILK = 0x4554482d41000000000000000000000000000000000000000000000000000000; address public constant VAT_ADDRESS = 0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B; address public constant SPOTTER_ADDRESS = 0x65C79fcB50Ca1594B025960e539eD7A9a6D434A3; address public constant DAI_JOIN_ADDRESS = 0x9759A6Ac90977b93B58547b4A71c78317f391A28; address public constant JUG_ADDRESS = 0x19c0976f590D67707E62397C87829d896Dc0f1F1; address public constant DAI_ADDRESS = 0x6B175474E89094C44Da98b954EedeAC495271d0F; address public constant BOT_REGISTRY_ADDRESS = 0x637726f8b08a7ABE3aE3aCaB01A80E2d8ddeF77B; Vat public constant vat = Vat(VAT_ADDRESS); DaiJoin public constant daiJoin = DaiJoin(DAI_JOIN_ADDRESS); Spotter public constant spotter = Spotter(SPOTTER_ADDRESS); DefisaverLogger public constant logger = DefisaverLogger(0x5c55B921f590a89C1Ebe84dF170E655a82b62126); /// @notice Repay - draws collateral, converts to Dai and repays the debt /// @dev Must be called by the DSProxy contract that owns the CDP function repay( ExchangeData memory _exchangeData, uint _cdpId, uint _gasCost, address _joinAddr, ManagerType _managerType ) public payable { address managerAddr = getManagerAddr(_managerType); address user = getOwner(Manager(managerAddr), _cdpId); bytes32 ilk = Manager(managerAddr).ilks(_cdpId); drawCollateral(managerAddr, _cdpId, _joinAddr, _exchangeData.srcAmount); _exchangeData.user = user; _exchangeData.dfsFeeDivider = isAutomation() ? AUTOMATIC_SERVICE_FEE : MANUAL_SERVICE_FEE; (, uint daiAmount) = _sell(_exchangeData); daiAmount -= takeFee(_gasCost, daiAmount); paybackDebt(managerAddr, _cdpId, ilk, daiAmount, user); // if there is some eth left (0x fee), return it to user if (address(this).balance > 0) { tx.origin.transfer(address(this).balance); } logger.Log(address(this), msg.sender, "MCDRepay", abi.encode(_cdpId, user, _exchangeData.srcAmount, daiAmount)); } /// @notice Boost - draws Dai, converts to collateral and adds to CDP /// @dev Must be called by the DSProxy contract that owns the CDP function boost( ExchangeData memory _exchangeData, uint _cdpId, uint _gasCost, address _joinAddr, ManagerType _managerType ) public payable { address managerAddr = getManagerAddr(_managerType); address user = getOwner(Manager(managerAddr), _cdpId); bytes32 ilk = Manager(managerAddr).ilks(_cdpId); uint daiDrawn = drawDai(managerAddr, _cdpId, ilk, _exchangeData.srcAmount); _exchangeData.user = user; _exchangeData.dfsFeeDivider = isAutomation() ? AUTOMATIC_SERVICE_FEE : MANUAL_SERVICE_FEE; _exchangeData.srcAmount = daiDrawn - takeFee(_gasCost, daiDrawn); (, uint swapedColl) = _sell(_exchangeData); addCollateral(managerAddr, _cdpId, _joinAddr, swapedColl); // if there is some eth left (0x fee), return it to user if (address(this).balance > 0) { tx.origin.transfer(address(this).balance); } logger.Log(address(this), msg.sender, "MCDBoost", abi.encode(_cdpId, user, _exchangeData.srcAmount, swapedColl)); } /// @notice Draws Dai from the CDP /// @dev If _daiAmount is bigger than max available we'll draw max /// @param _managerAddr Address of the CDP Manager /// @param _cdpId Id of the CDP /// @param _ilk Ilk of the CDP /// @param _daiAmount Amount of Dai to draw function drawDai(address _managerAddr, uint _cdpId, bytes32 _ilk, uint _daiAmount) internal returns (uint) { uint rate = Jug(JUG_ADDRESS).drip(_ilk); uint daiVatBalance = vat.dai(Manager(_managerAddr).urns(_cdpId)); uint maxAmount = getMaxDebt(_managerAddr, _cdpId, _ilk); if (_daiAmount >= maxAmount) { _daiAmount = sub(maxAmount, 1); } Manager(_managerAddr).frob(_cdpId, int(0), normalizeDrawAmount(_daiAmount, rate, daiVatBalance)); Manager(_managerAddr).move(_cdpId, address(this), toRad(_daiAmount)); if (vat.can(address(this), address(DAI_JOIN_ADDRESS)) == 0) { vat.hope(DAI_JOIN_ADDRESS); } DaiJoin(DAI_JOIN_ADDRESS).exit(address(this), _daiAmount); return _daiAmount; } /// @notice Adds collateral to the CDP /// @param _managerAddr Address of the CDP Manager /// @param _cdpId Id of the CDP /// @param _joinAddr Address of the join contract for the CDP collateral /// @param _amount Amount of collateral to add function addCollateral(address _managerAddr, uint _cdpId, address _joinAddr, uint _amount) internal { int convertAmount = 0; if (isEthJoinAddr(_joinAddr)) { Join(_joinAddr).gem().deposit{value: _amount}(); convertAmount = toPositiveInt(_amount); } else { convertAmount = toPositiveInt(convertTo18(_joinAddr, _amount)); } ERC20(address(Join(_joinAddr).gem())).safeApprove(_joinAddr, _amount); Join(_joinAddr).join(address(this), _amount); vat.frob( Manager(_managerAddr).ilks(_cdpId), Manager(_managerAddr).urns(_cdpId), address(this), address(this), convertAmount, 0 ); } /// @notice Draws collateral and returns it to DSProxy /// @param _managerAddr Address of the CDP Manager /// @dev If _amount is bigger than max available we'll draw max /// @param _cdpId Id of the CDP /// @param _joinAddr Address of the join contract for the CDP collateral /// @param _amount Amount of collateral to draw function drawCollateral(address _managerAddr, uint _cdpId, address _joinAddr, uint _amount) internal returns (uint) { uint frobAmount = _amount; if (Join(_joinAddr).dec() != 18) { frobAmount = _amount * (10 ** (18 - Join(_joinAddr).dec())); } Manager(_managerAddr).frob(_cdpId, -toPositiveInt(frobAmount), 0); Manager(_managerAddr).flux(_cdpId, address(this), frobAmount); Join(_joinAddr).exit(address(this), _amount); if (isEthJoinAddr(_joinAddr)) { Join(_joinAddr).gem().withdraw(_amount); // Weth -> Eth } return _amount; } /// @notice Paybacks Dai debt /// @param _managerAddr Address of the CDP Manager /// @dev If the _daiAmount is bigger than the whole debt, returns extra Dai /// @param _cdpId Id of the CDP /// @param _ilk Ilk of the CDP /// @param _daiAmount Amount of Dai to payback /// @param _owner Address that owns the DSProxy that owns the CDP function paybackDebt(address _managerAddr, uint _cdpId, bytes32 _ilk, uint _daiAmount, address _owner) internal { address urn = Manager(_managerAddr).urns(_cdpId); uint wholeDebt = getAllDebt(VAT_ADDRESS, urn, urn, _ilk); if (_daiAmount > wholeDebt) { ERC20(DAI_ADDRESS).transfer(_owner, sub(_daiAmount, wholeDebt)); _daiAmount = wholeDebt; } if (ERC20(DAI_ADDRESS).allowance(address(this), DAI_JOIN_ADDRESS) == 0) { ERC20(DAI_ADDRESS).approve(DAI_JOIN_ADDRESS, uint(-1)); } daiJoin.join(urn, _daiAmount); Manager(_managerAddr).frob(_cdpId, 0, normalizePaybackAmount(VAT_ADDRESS, urn, _ilk)); } /// @notice Gets the maximum amount of collateral available to draw /// @param _managerAddr Address of the CDP Manager /// @param _cdpId Id of the CDP /// @param _ilk Ilk of the CDP /// @param _joinAddr Joind address of collateral /// @dev Substracts 10 wei to aviod rounding error later on function getMaxCollateral(address _managerAddr, uint _cdpId, bytes32 _ilk, address _joinAddr) public view returns (uint) { uint price = getPrice(_ilk); (uint collateral, uint debt) = getCdpInfo(Manager(_managerAddr), _cdpId, _ilk); (, uint mat) = Spotter(SPOTTER_ADDRESS).ilks(_ilk); uint maxCollateral = sub(collateral, (div(mul(mat, debt), price))); uint normalizeMaxCollateral = maxCollateral / (10 ** (18 - Join(_joinAddr).dec())); // take one percent due to precision issues return normalizeMaxCollateral * 99 / 100; } /// @notice Gets the maximum amount of debt available to generate /// @param _managerAddr Address of the CDP Manager /// @param _cdpId Id of the CDP /// @param _ilk Ilk of the CDP /// @dev Substracts 10 wei to aviod rounding error later on function getMaxDebt(address _managerAddr, uint _cdpId, bytes32 _ilk) public virtual view returns (uint) { uint price = getPrice(_ilk); (, uint mat) = spotter.ilks(_ilk); (uint collateral, uint debt) = getCdpInfo(Manager(_managerAddr), _cdpId, _ilk); return sub(sub(div(mul(collateral, price), mat), debt), 10); } /// @notice Gets a price of the asset /// @param _ilk Ilk of the CDP function getPrice(bytes32 _ilk) public view returns (uint) { (, uint mat) = spotter.ilks(_ilk); (,,uint spot,,) = vat.ilks(_ilk); return rmul(rmul(spot, spotter.par()), mat); } function isAutomation() internal view returns(bool) { return BotRegistry(BOT_REGISTRY_ADDRESS).botList(tx.origin); } function takeFee(uint256 _gasCost, uint _amount) internal returns(uint) { if (_gasCost > 0) { uint ethDaiPrice = getPrice(ETH_ILK); uint feeAmount = rmul(_gasCost, ethDaiPrice); if (feeAmount > _amount / 5) { feeAmount = _amount / 5; } address walletAddr = _feeRecipient.getFeeAddr(); ERC20(DAI_ADDRESS).transfer(walletAddr, feeAmount); return feeAmount; } return 0; } }
pragma solidity ^0.6.0; import "./PipInterface.sol"; abstract contract Spotter { struct Ilk { PipInterface pip; uint256 mat; } mapping (bytes32 => Ilk) public ilks; uint256 public par; }
pragma solidity ^0.6.0; abstract contract Jug { struct Ilk { uint256 duty; uint256 rho; } mapping (bytes32 => Ilk) public ilks; function drip(bytes32) public virtual returns (uint); }
pragma solidity ^0.6.0; import "./Vat.sol"; import "./Gem.sol"; abstract contract DaiJoin { function vat() public virtual returns (Vat); function dai() public virtual returns (Gem); function join(address, uint) public virtual payable; function exit(address, uint) public virtual; }
pragma solidity ^0.6.0; import "../../DS/DSMath.sol"; import "../../DS/DSProxy.sol"; import "../../interfaces/Manager.sol"; import "../../interfaces/Join.sol"; import "../../interfaces/Vat.sol"; /// @title Helper methods for MCDSaverProxy contract MCDSaverProxyHelper is DSMath { enum ManagerType { MCD, BPROTOCOL } /// @notice Returns a normalized debt _amount based on the current rate /// @param _amount Amount of dai to be normalized /// @param _rate Current rate of the stability fee /// @param _daiVatBalance Balance od Dai in the Vat for that CDP function normalizeDrawAmount(uint _amount, uint _rate, uint _daiVatBalance) internal pure returns (int dart) { if (_daiVatBalance < mul(_amount, RAY)) { dart = toPositiveInt(sub(mul(_amount, RAY), _daiVatBalance) / _rate); dart = mul(uint(dart), _rate) < mul(_amount, RAY) ? dart + 1 : dart; } } /// @notice Converts a number to Rad percision /// @param _wad The input number in wad percision function toRad(uint _wad) internal pure returns (uint) { return mul(_wad, 10 ** 27); } /// @notice Converts a number to 18 decimal percision /// @param _joinAddr Join address of the collateral /// @param _amount Number to be converted function convertTo18(address _joinAddr, uint256 _amount) internal view returns (uint256) { return mul(_amount, 10 ** (18 - Join(_joinAddr).dec())); } /// @notice Converts a uint to int and checks if positive /// @param _x Number to be converted function toPositiveInt(uint _x) internal pure returns (int y) { y = int(_x); require(y >= 0, "int-overflow"); } /// @notice Gets Dai amount in Vat which can be added to Cdp /// @param _vat Address of Vat contract /// @param _urn Urn of the Cdp /// @param _ilk Ilk of the Cdp function normalizePaybackAmount(address _vat, address _urn, bytes32 _ilk) internal view returns (int amount) { uint dai = Vat(_vat).dai(_urn); (, uint rate,,,) = Vat(_vat).ilks(_ilk); (, uint art) = Vat(_vat).urns(_ilk, _urn); amount = toPositiveInt(dai / rate); amount = uint(amount) <= art ? - amount : - toPositiveInt(art); } /// @notice Gets the whole debt of the CDP /// @param _vat Address of Vat contract /// @param _usr Address of the Dai holder /// @param _urn Urn of the Cdp /// @param _ilk Ilk of the Cdp function getAllDebt(address _vat, address _usr, address _urn, bytes32 _ilk) internal view returns (uint daiAmount) { (, uint rate,,,) = Vat(_vat).ilks(_ilk); (, uint art) = Vat(_vat).urns(_ilk, _urn); uint dai = Vat(_vat).dai(_usr); uint rad = sub(mul(art, rate), dai); daiAmount = rad / RAY; daiAmount = mul(daiAmount, RAY) < rad ? daiAmount + 1 : daiAmount; } /// @notice Gets the token address from the Join contract /// @param _joinAddr Address of the Join contract function getCollateralAddr(address _joinAddr) internal view returns (address) { return address(Join(_joinAddr).gem()); } /// @notice Checks if the join address is one of the Ether coll. types /// @param _joinAddr Join address to check function isEthJoinAddr(address _joinAddr) internal view returns (bool) { // if it's dai_join_addr don't check gem() it will fail if (_joinAddr == 0x9759A6Ac90977b93B58547b4A71c78317f391A28) return false; // if coll is weth it's and eth type coll if (address(Join(_joinAddr).gem()) == 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) { return true; } return false; } /// @notice Gets CDP info (collateral, debt) /// @param _manager Manager contract /// @param _cdpId Id of the CDP /// @param _ilk Ilk of the CDP function getCdpInfo(Manager _manager, uint _cdpId, bytes32 _ilk) public view returns (uint, uint) { address vat = _manager.vat(); address urn = _manager.urns(_cdpId); (uint collateral, uint debt) = Vat(vat).urns(_ilk, urn); (,uint rate,,,) = Vat(vat).ilks(_ilk); return (collateral, rmul(debt, rate)); } /// @notice Address that owns the DSProxy that owns the CDP /// @param _manager Manager contract /// @param _cdpId Id of the CDP function getOwner(Manager _manager, uint _cdpId) public view returns (address) { DSProxy proxy = DSProxy(uint160(_manager.owns(_cdpId))); return proxy.owner(); } /// @notice Based on the manager type returns the address /// @param _managerType Type of vault manager to use function getManagerAddr(ManagerType _managerType) public pure returns (address) { if (_managerType == ManagerType.MCD) { return 0x5ef30b9986345249bc32d8928B7ee64DE9435E39; } else if (_managerType == ManagerType.BPROTOCOL) { return 0x3f30c2381CD8B917Dd96EB2f1A4F96D91324BBed; } } }
pragma solidity ^0.6.0; abstract contract PipInterface { function read() public virtual returns (bytes32); }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../interfaces/ILoanShifter.sol"; import "../../mcd/saver/MCDSaverProxy.sol"; import "../../mcd/create/MCDCreateProxyActions.sol"; contract McdShifter is MCDSaverProxy { using SafeERC20 for ERC20; Manager manager = Manager(0x5ef30b9986345249bc32d8928B7ee64DE9435E39); address public constant OPEN_PROXY_ACTIONS = 0x6d0984E80a86f26c0dd564ca0CF74a8E9Da03305; function getLoanAmount(uint _cdpId, address _joinAddr) public view virtual returns(uint loanAmount) { bytes32 ilk = manager.ilks(_cdpId); (, uint rate,,,) = vat.ilks(ilk); (, uint art) = vat.urns(ilk, manager.urns(_cdpId)); uint dai = vat.dai(manager.urns(_cdpId)); uint rad = sub(mul(art, rate), dai); loanAmount = rad / RAY; loanAmount = mul(loanAmount, RAY) < rad ? loanAmount + 1 : loanAmount; } function close( uint _cdpId, address _joinAddr, uint _loanAmount, uint _collateral ) public { address owner = getOwner(manager, _cdpId); bytes32 ilk = manager.ilks(_cdpId); (uint maxColl, ) = getCdpInfo(manager, _cdpId, ilk); // repay dai debt cdp paybackDebt(address(manager), _cdpId, ilk, _loanAmount, owner); maxColl = _collateral > maxColl ? maxColl : _collateral; // withdraw collateral from cdp drawCollateral(address(manager), _cdpId, _joinAddr, maxColl); // send back to msg.sender if (isEthJoinAddr(_joinAddr)) { msg.sender.transfer(address(this).balance); } else { ERC20 collToken = ERC20(getCollateralAddr(_joinAddr)); collToken.safeTransfer(msg.sender, collToken.balanceOf(address(this))); } } function open( uint _cdpId, address _joinAddr, uint _debtAmount ) public { uint collAmount = 0; if (isEthJoinAddr(_joinAddr)) { collAmount = address(this).balance; } else { collAmount = ERC20(address(Join(_joinAddr).gem())).balanceOf(address(this)); } if (_cdpId == 0) { openAndWithdraw(collAmount, _debtAmount, address(this), _joinAddr); } else { // add collateral addCollateral(address(manager), _cdpId, _joinAddr, collAmount); // draw debt drawDai(address(manager), _cdpId, manager.ilks(_cdpId), _debtAmount); } // transfer to repay FL ERC20(DAI_ADDRESS).transfer(msg.sender, ERC20(DAI_ADDRESS).balanceOf(address(this))); if (address(this).balance > 0) { tx.origin.transfer(address(this).balance); } } function openAndWithdraw(uint _collAmount, uint _debtAmount, address _proxy, address _joinAddrTo) internal { bytes32 ilk = Join(_joinAddrTo).ilk(); if (isEthJoinAddr(_joinAddrTo)) { MCDCreateProxyActions(OPEN_PROXY_ACTIONS).openLockETHAndDraw{value: address(this).balance}( address(manager), JUG_ADDRESS, _joinAddrTo, DAI_JOIN_ADDRESS, ilk, _debtAmount, _proxy ); } else { ERC20(getCollateralAddr(_joinAddrTo)).approve(OPEN_PROXY_ACTIONS, uint256(-1)); MCDCreateProxyActions(OPEN_PROXY_ACTIONS).openLockGemAndDraw( address(manager), JUG_ADDRESS, _joinAddrTo, DAI_JOIN_ADDRESS, ilk, _collAmount, _debtAmount, true, _proxy ); } } }
pragma solidity ^0.6.0; abstract contract GemLike { function approve(address, uint256) public virtual; function transfer(address, uint256) public virtual; function transferFrom(address, address, uint256) public virtual; function deposit() public virtual payable; function withdraw(uint256) public virtual; } abstract contract ManagerLike { function cdpCan(address, uint256, address) public virtual view returns (uint256); function ilks(uint256) public virtual view returns (bytes32); function owns(uint256) public virtual view returns (address); function urns(uint256) public virtual view returns (address); function vat() public virtual view returns (address); function open(bytes32, address) public virtual returns (uint256); function give(uint256, address) public virtual; function cdpAllow(uint256, address, uint256) public virtual; function urnAllow(address, uint256) public virtual; function frob(uint256, int256, int256) public virtual; function flux(uint256, address, uint256) public virtual; function move(uint256, address, uint256) public virtual; function exit(address, uint256, address, uint256) public virtual; function quit(uint256, address) public virtual; function enter(address, uint256) public virtual; function shift(uint256, uint256) public virtual; } abstract contract VatLike { function can(address, address) public virtual view returns (uint256); function ilks(bytes32) public virtual view returns (uint256, uint256, uint256, uint256, uint256); function dai(address) public virtual view returns (uint256); function urns(bytes32, address) public virtual view returns (uint256, uint256); function frob(bytes32, address, address, address, int256, int256) public virtual; function hope(address) public virtual; function move(address, address, uint256) public virtual; } abstract contract GemJoinLike { function dec() public virtual returns (uint256); function gem() public virtual returns (GemLike); function join(address, uint256) public virtual payable; function exit(address, uint256) public virtual; } abstract contract GNTJoinLike { function bags(address) public virtual view returns (address); function make(address) public virtual returns (address); } abstract contract DaiJoinLike { function vat() public virtual returns (VatLike); function dai() public virtual returns (GemLike); function join(address, uint256) public virtual payable; function exit(address, uint256) public virtual; } abstract contract HopeLike { function hope(address) public virtual; function nope(address) public virtual; } abstract contract ProxyRegistryInterface { function build(address) public virtual returns (address); } abstract contract EndLike { function fix(bytes32) public virtual view returns (uint256); function cash(bytes32, uint256) public virtual; function free(bytes32) public virtual; function pack(uint256) public virtual; function skim(bytes32, address) public virtual; } abstract contract JugLike { function drip(bytes32) public virtual returns (uint256); } abstract contract PotLike { function pie(address) public virtual view returns (uint256); function drip() public virtual returns (uint256); function join(uint256) public virtual; function exit(uint256) public virtual; } abstract contract ProxyRegistryLike { function proxies(address) public virtual view returns (address); function build(address) public virtual returns (address); } abstract contract ProxyLike { function owner() public virtual view returns (address); } // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // WARNING: These functions meant to be used as a a library for a DSProxy. Some are unsafe if you call them directly. // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! contract Common { uint256 constant RAY = 10**27; // Internal functions function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { require(y == 0 || (z = x * y) / y == x, "mul-overflow"); } // Public functions // solhint-disable-next-line func-name-mixedcase function daiJoin_join(address apt, address urn, uint256 wad) public { // Gets DAI from the user's wallet DaiJoinLike(apt).dai().transferFrom(msg.sender, address(this), wad); // Approves adapter to take the DAI amount DaiJoinLike(apt).dai().approve(apt, wad); // Joins DAI into the vat DaiJoinLike(apt).join(urn, wad); } } contract MCDCreateProxyActions is Common { // Internal functions function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { require((z = x - y) <= x, "sub-overflow"); } function toInt(uint256 x) internal pure returns (int256 y) { y = int256(x); require(y >= 0, "int-overflow"); } function toRad(uint256 wad) internal pure returns (uint256 rad) { rad = mul(wad, 10**27); } function convertTo18(address gemJoin, uint256 amt) internal returns (uint256 wad) { // For those collaterals that have less than 18 decimals precision we need to do the conversion before passing to frob function // Adapters will automatically handle the difference of precision wad = mul(amt, 10**(18 - GemJoinLike(gemJoin).dec())); } function _getDrawDart(address vat, address jug, address urn, bytes32 ilk, uint256 wad) internal returns (int256 dart) { // Updates stability fee rate uint256 rate = JugLike(jug).drip(ilk); // Gets DAI balance of the urn in the vat uint256 dai = VatLike(vat).dai(urn); // If there was already enough DAI in the vat balance, just exits it without adding more debt if (dai < mul(wad, RAY)) { // Calculates the needed dart so together with the existing dai in the vat is enough to exit wad amount of DAI tokens dart = toInt(sub(mul(wad, RAY), dai) / rate); // This is neeeded due lack of precision. It might need to sum an extra dart wei (for the given DAI wad amount) dart = mul(uint256(dart), rate) < mul(wad, RAY) ? dart + 1 : dart; } } function _getWipeDart(address vat, uint256 dai, address urn, bytes32 ilk) internal view returns (int256 dart) { // Gets actual rate from the vat (, uint256 rate, , , ) = VatLike(vat).ilks(ilk); // Gets actual art value of the urn (, uint256 art) = VatLike(vat).urns(ilk, urn); // Uses the whole dai balance in the vat to reduce the debt dart = toInt(dai / rate); // Checks the calculated dart is not higher than urn.art (total debt), otherwise uses its value dart = uint256(dart) <= art ? -dart : -toInt(art); } function _getWipeAllWad(address vat, address usr, address urn, bytes32 ilk) internal view returns (uint256 wad) { // Gets actual rate from the vat (, uint256 rate, , , ) = VatLike(vat).ilks(ilk); // Gets actual art value of the urn (, uint256 art) = VatLike(vat).urns(ilk, urn); // Gets actual dai amount in the urn uint256 dai = VatLike(vat).dai(usr); uint256 rad = sub(mul(art, rate), dai); wad = rad / RAY; // If the rad precision has some dust, it will need to request for 1 extra wad wei wad = mul(wad, RAY) < rad ? wad + 1 : wad; } // Public functions function transfer(address gem, address dst, uint256 wad) public { GemLike(gem).transfer(dst, wad); } // solhint-disable-next-line func-name-mixedcase function ethJoin_join(address apt, address urn) public payable { // Wraps ETH in WETH GemJoinLike(apt).gem().deposit{value: msg.value}(); // Approves adapter to take the WETH amount GemJoinLike(apt).gem().approve(address(apt), msg.value); // Joins WETH collateral into the vat GemJoinLike(apt).join(urn, msg.value); } // solhint-disable-next-line func-name-mixedcase function gemJoin_join(address apt, address urn, uint256 wad, bool transferFrom) public { // Only executes for tokens that have approval/transferFrom implementation if (transferFrom) { // Gets token from the user's wallet GemJoinLike(apt).gem().transferFrom(msg.sender, address(this), wad); // Approves adapter to take the token amount GemJoinLike(apt).gem().approve(apt, 0); GemJoinLike(apt).gem().approve(apt, wad); } // Joins token collateral into the vat GemJoinLike(apt).join(urn, wad); } function hope(address obj, address usr) public { HopeLike(obj).hope(usr); } function nope(address obj, address usr) public { HopeLike(obj).nope(usr); } function open(address manager, bytes32 ilk, address usr) public returns (uint256 cdp) { cdp = ManagerLike(manager).open(ilk, usr); } function give(address manager, uint256 cdp, address usr) public { ManagerLike(manager).give(cdp, usr); } function move(address manager, uint256 cdp, address dst, uint256 rad) public { ManagerLike(manager).move(cdp, dst, rad); } function frob(address manager, uint256 cdp, int256 dink, int256 dart) public { ManagerLike(manager).frob(cdp, dink, dart); } function lockETH(address manager, address ethJoin, uint256 cdp) public payable { // Receives ETH amount, converts it to WETH and joins it into the vat ethJoin_join(ethJoin, address(this)); // Locks WETH amount into the CDP VatLike(ManagerLike(manager).vat()).frob( ManagerLike(manager).ilks(cdp), ManagerLike(manager).urns(cdp), address(this), address(this), toInt(msg.value), 0 ); } function lockGem(address manager, address gemJoin, uint256 cdp, uint256 wad, bool transferFrom) public { // Takes token amount from user's wallet and joins into the vat gemJoin_join(gemJoin, address(this), wad, transferFrom); // Locks token amount into the CDP VatLike(ManagerLike(manager).vat()).frob( ManagerLike(manager).ilks(cdp), ManagerLike(manager).urns(cdp), address(this), address(this), toInt(convertTo18(gemJoin, wad)), 0 ); } function draw(address manager, address jug, address daiJoin, uint256 cdp, uint256 wad) public { address urn = ManagerLike(manager).urns(cdp); address vat = ManagerLike(manager).vat(); bytes32 ilk = ManagerLike(manager).ilks(cdp); // Generates debt in the CDP frob(manager, cdp, 0, _getDrawDart(vat, jug, urn, ilk, wad)); // Moves the DAI amount (balance in the vat in rad) to proxy's address move(manager, cdp, address(this), toRad(wad)); // Allows adapter to access to proxy's DAI balance in the vat if (VatLike(vat).can(address(this), address(daiJoin)) == 0) { VatLike(vat).hope(daiJoin); } // Exits DAI to the user's wallet as a token DaiJoinLike(daiJoin).exit(msg.sender, wad); } function lockETHAndDraw( address manager, address jug, address ethJoin, address daiJoin, uint256 cdp, uint256 wadD ) public payable { address urn = ManagerLike(manager).urns(cdp); address vat = ManagerLike(manager).vat(); bytes32 ilk = ManagerLike(manager).ilks(cdp); // Receives ETH amount, converts it to WETH and joins it into the vat ethJoin_join(ethJoin, urn); // Locks WETH amount into the CDP and generates debt frob(manager, cdp, toInt(msg.value), _getDrawDart(vat, jug, urn, ilk, wadD)); // Moves the DAI amount (balance in the vat in rad) to proxy's address move(manager, cdp, address(this), toRad(wadD)); // Allows adapter to access to proxy's DAI balance in the vat if (VatLike(vat).can(address(this), address(daiJoin)) == 0) { VatLike(vat).hope(daiJoin); } // Exits DAI to the user's wallet as a token DaiJoinLike(daiJoin).exit(msg.sender, wadD); } function openLockETHAndDraw( address manager, address jug, address ethJoin, address daiJoin, bytes32 ilk, uint256 wadD, address owner ) public payable returns (uint256 cdp) { cdp = open(manager, ilk, address(this)); lockETHAndDraw(manager, jug, ethJoin, daiJoin, cdp, wadD); give(manager, cdp, owner); } function lockGemAndDraw( address manager, address jug, address gemJoin, address daiJoin, uint256 cdp, uint256 wadC, uint256 wadD, bool transferFrom ) public { address urn = ManagerLike(manager).urns(cdp); address vat = ManagerLike(manager).vat(); bytes32 ilk = ManagerLike(manager).ilks(cdp); // Takes token amount from user's wallet and joins into the vat gemJoin_join(gemJoin, urn, wadC, transferFrom); // Locks token amount into the CDP and generates debt frob( manager, cdp, toInt(convertTo18(gemJoin, wadC)), _getDrawDart(vat, jug, urn, ilk, wadD) ); // Moves the DAI amount (balance in the vat in rad) to proxy's address move(manager, cdp, address(this), toRad(wadD)); // Allows adapter to access to proxy's DAI balance in the vat if (VatLike(vat).can(address(this), address(daiJoin)) == 0) { VatLike(vat).hope(daiJoin); } // Exits DAI to the user's wallet as a token DaiJoinLike(daiJoin).exit(msg.sender, wadD); } function openLockGemAndDraw( address manager, address jug, address gemJoin, address daiJoin, bytes32 ilk, uint256 wadC, uint256 wadD, bool transferFrom, address owner ) public returns (uint256 cdp) { cdp = open(manager, ilk, address(this)); lockGemAndDraw(manager, jug, gemJoin, daiJoin, cdp, wadC, wadD, transferFrom); give(manager, cdp, owner); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../exchangeV3/DFSExchangeCore.sol"; import "./MCDCreateProxyActions.sol"; import "../../utils/FlashLoanReceiverBase.sol"; import "../../interfaces/Manager.sol"; import "../../interfaces/Join.sol"; import "../../DS/DSProxy.sol"; import "./MCDCreateTaker.sol"; contract MCDCreateFlashLoan is DFSExchangeCore, AdminAuth, FlashLoanReceiverBase { address public constant CREATE_PROXY_ACTIONS = 0x6d0984E80a86f26c0dd564ca0CF74a8E9Da03305; uint public constant SERVICE_FEE = 400; // 0.25% Fee address public constant DAI_ADDRESS = 0x6B175474E89094C44Da98b954EedeAC495271d0F; ILendingPoolAddressesProvider public LENDING_POOL_ADDRESS_PROVIDER = ILendingPoolAddressesProvider(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8); address public constant DAI_JOIN_ADDRESS = 0x9759A6Ac90977b93B58547b4A71c78317f391A28; address public constant JUG_ADDRESS = 0x19c0976f590D67707E62397C87829d896Dc0f1F1; address public constant MANAGER_ADDRESS = 0x5ef30b9986345249bc32d8928B7ee64DE9435E39; constructor() FlashLoanReceiverBase(LENDING_POOL_ADDRESS_PROVIDER) public {} function executeOperation( address _reserve, uint256 _amount, uint256 _fee, bytes calldata _params) external override { //check the contract has the specified balance require(_amount <= getBalanceInternal(address(this), _reserve), "Invalid balance for the contract"); (address proxy, bytes memory packedData) = abi.decode(_params, (address,bytes)); (MCDCreateTaker.CreateData memory createData, ExchangeData memory exchangeData) = abi.decode(packedData, (MCDCreateTaker.CreateData,ExchangeData)); exchangeData.dfsFeeDivider = SERVICE_FEE; exchangeData.user = DSProxy(payable(proxy)).owner(); openAndLeverage(createData.collAmount, createData.daiAmount + _fee, createData.joinAddr, proxy, exchangeData); transferFundsBackToPoolInternal(_reserve, _amount.add(_fee)); // if there is some eth left (0x fee), return it to user if (address(this).balance > 0) { tx.origin.transfer(address(this).balance); } } function openAndLeverage( uint _collAmount, uint _daiAmountAndFee, address _joinAddr, address _proxy, ExchangeData memory _exchangeData ) public { (, uint256 collSwaped) = _sell(_exchangeData); bytes32 ilk = Join(_joinAddr).ilk(); if (isEthJoinAddr(_joinAddr)) { MCDCreateProxyActions(CREATE_PROXY_ACTIONS).openLockETHAndDraw{value: address(this).balance}( MANAGER_ADDRESS, JUG_ADDRESS, _joinAddr, DAI_JOIN_ADDRESS, ilk, _daiAmountAndFee, _proxy ); } else { ERC20(address(Join(_joinAddr).gem())).safeApprove(CREATE_PROXY_ACTIONS, (_collAmount + collSwaped)); MCDCreateProxyActions(CREATE_PROXY_ACTIONS).openLockGemAndDraw( MANAGER_ADDRESS, JUG_ADDRESS, _joinAddr, DAI_JOIN_ADDRESS, ilk, (_collAmount + collSwaped), _daiAmountAndFee, true, _proxy ); } } /// @notice Checks if the join address is one of the Ether coll. types /// @param _joinAddr Join address to check function isEthJoinAddr(address _joinAddr) internal view returns (bool) { // if it's dai_join_addr don't check gem() it will fail if (_joinAddr == 0x9759A6Ac90977b93B58547b4A71c78317f391A28) return false; // if coll is weth it's and eth type coll if (address(Join(_joinAddr).gem()) == 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) { return true; } return false; } receive() external override(FlashLoanReceiverBase, DFSExchangeCore) payable {} }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../mcd/saver/MCDSaverProxy.sol"; import "../../loggers/DefisaverLogger.sol"; import "../../interfaces/ILendingPool.sol"; import "../../exchangeV3/DFSExchangeData.sol"; import "../../utils/SafeERC20.sol"; import "../../utils/GasBurner.sol"; contract MCDCreateTaker is GasBurner { using SafeERC20 for ERC20; address payable public constant MCD_CREATE_FLASH_LOAN = 0x409F216aa8034a12135ab6b74Bf6444335004BBd; address public constant DAI_ADDRESS = 0x6B175474E89094C44Da98b954EedeAC495271d0F; ILendingPool public constant lendingPool = ILendingPool(0x398eC7346DcD622eDc5ae82352F02bE94C62d119); // solhint-disable-next-line const-name-snakecase Manager public constant manager = Manager(0x5ef30b9986345249bc32d8928B7ee64DE9435E39); // solhint-disable-next-line const-name-snakecase DefisaverLogger public constant logger = DefisaverLogger(0x5c55B921f590a89C1Ebe84dF170E655a82b62126); struct CreateData { uint collAmount; uint daiAmount; address joinAddr; } function openWithLoan( DFSExchangeData.ExchangeData memory _exchangeData, CreateData memory _createData ) public payable burnGas(20) { MCD_CREATE_FLASH_LOAN.transfer(msg.value); //0x fee if (!isEthJoinAddr(_createData.joinAddr)) { ERC20(getCollateralAddr(_createData.joinAddr)).safeTransferFrom(msg.sender, address(this), _createData.collAmount); ERC20(getCollateralAddr(_createData.joinAddr)).safeTransfer(MCD_CREATE_FLASH_LOAN, _createData.collAmount); } bytes memory packedData = _packData(_createData, _exchangeData); bytes memory paramsData = abi.encode(address(this), packedData); lendingPool.flashLoan(MCD_CREATE_FLASH_LOAN, DAI_ADDRESS, _createData.daiAmount, paramsData); logger.Log(address(this), msg.sender, "MCDCreate", abi.encode(manager.last(address(this)), _createData.collAmount, _createData.daiAmount)); } function getCollateralAddr(address _joinAddr) internal view returns (address) { return address(Join(_joinAddr).gem()); } /// @notice Checks if the join address is one of the Ether coll. types /// @param _joinAddr Join address to check function isEthJoinAddr(address _joinAddr) internal view returns (bool) { // if it's dai_join_addr don't check gem() it will fail if (_joinAddr == 0x9759A6Ac90977b93B58547b4A71c78317f391A28) return false; // if coll is weth it's and eth type coll if (address(Join(_joinAddr).gem()) == 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) { return true; } return false; } function _packData( CreateData memory _createData, DFSExchangeData.ExchangeData memory _exchangeData ) internal pure returns (bytes memory) { return abi.encode(_createData, _exchangeData); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../saver/MCDSaverProxy.sol"; import "../../exchangeV3/DFSExchangeData.sol"; import "../../utils/GasBurner.sol"; import "../../interfaces/ILendingPool.sol"; contract MCDSaverTaker is MCDSaverProxy, GasBurner { address payable public constant MCD_SAVER_FLASH_LOAN = 0xcBb5DbBCcFbf6aF8AF75d0cbD5646C73d847cd15; address public constant AAVE_POOL_CORE = 0x3dfd23A6c5E8BbcFc9581d2E864a68feb6a076d3; ILendingPool public constant lendingPool = ILendingPool(0x398eC7346DcD622eDc5ae82352F02bE94C62d119); function boostWithLoan( ExchangeData memory _exchangeData, uint _cdpId, uint _gasCost, address _joinAddr, ManagerType _managerType ) public payable burnGas(25) { address managerAddr = getManagerAddr(_managerType); uint256 maxDebt = getMaxDebt(managerAddr, _cdpId, Manager(managerAddr).ilks(_cdpId)); uint maxLiq = getAvailableLiquidity(DAI_JOIN_ADDRESS); if (maxDebt >= _exchangeData.srcAmount || maxLiq == 0) { if (_exchangeData.srcAmount > maxDebt) { _exchangeData.srcAmount = maxDebt; } boost(_exchangeData, _cdpId, _gasCost, _joinAddr, _managerType); return; } uint256 loanAmount = sub(_exchangeData.srcAmount, maxDebt); loanAmount = loanAmount > maxLiq ? maxLiq : loanAmount; MCD_SAVER_FLASH_LOAN.transfer(msg.value); // 0x fee Manager(managerAddr).cdpAllow(_cdpId, MCD_SAVER_FLASH_LOAN, 1); bytes memory paramsData = abi.encode(packExchangeData(_exchangeData), _cdpId, _gasCost, _joinAddr, false, uint8(_managerType)); lendingPool.flashLoan(MCD_SAVER_FLASH_LOAN, DAI_ADDRESS, loanAmount, paramsData); Manager(managerAddr).cdpAllow(_cdpId, MCD_SAVER_FLASH_LOAN, 0); } function repayWithLoan( ExchangeData memory _exchangeData, uint _cdpId, uint _gasCost, address _joinAddr, ManagerType _managerType ) public payable burnGas(25) { address managerAddr = getManagerAddr(_managerType); uint256 maxColl = getMaxCollateral(managerAddr, _cdpId, Manager(managerAddr).ilks(_cdpId), _joinAddr); uint maxLiq = getAvailableLiquidity(_joinAddr); if (maxColl >= _exchangeData.srcAmount || maxLiq == 0) { if (_exchangeData.srcAmount > maxColl) { _exchangeData.srcAmount = maxColl; } repay(_exchangeData, _cdpId, _gasCost, _joinAddr, _managerType); return; } uint256 loanAmount = sub(_exchangeData.srcAmount, maxColl); loanAmount = loanAmount > maxLiq ? maxLiq : loanAmount; MCD_SAVER_FLASH_LOAN.transfer(msg.value); // 0x fee Manager(managerAddr).cdpAllow(_cdpId, MCD_SAVER_FLASH_LOAN, 1); bytes memory paramsData = abi.encode(packExchangeData(_exchangeData), _cdpId, _gasCost, _joinAddr, true, uint8(_managerType)); lendingPool.flashLoan(MCD_SAVER_FLASH_LOAN, getAaveCollAddr(_joinAddr), loanAmount, paramsData); Manager(managerAddr).cdpAllow(_cdpId, MCD_SAVER_FLASH_LOAN, 0); } /// @notice Gets the maximum amount of debt available to generate /// @param _managerAddr Address of the CDP Manager /// @param _cdpId Id of the CDP /// @param _ilk Ilk of the CDP function getMaxDebt(address _managerAddr, uint256 _cdpId, bytes32 _ilk) public override view returns (uint256) { uint256 price = getPrice(_ilk); (, uint256 mat) = spotter.ilks(_ilk); (uint256 collateral, uint256 debt) = getCdpInfo(Manager(_managerAddr), _cdpId, _ilk); return sub(wdiv(wmul(collateral, price), mat), debt); } function getAaveCollAddr(address _joinAddr) internal view returns (address) { if (isEthJoinAddr(_joinAddr) || _joinAddr == 0x775787933e92b709f2a3C70aa87999696e74A9F8) { return KYBER_ETH_ADDRESS; } else if (_joinAddr == DAI_JOIN_ADDRESS) { return DAI_ADDRESS; } else { return getCollateralAddr(_joinAddr); } } function getAvailableLiquidity(address _joinAddr) internal view returns (uint liquidity) { address tokenAddr = getAaveCollAddr(_joinAddr); if (tokenAddr == KYBER_ETH_ADDRESS) { liquidity = AAVE_POOL_CORE.balance; } else { liquidity = ERC20(tokenAddr).balanceOf(AAVE_POOL_CORE); } } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../ProtocolInterface.sol"; import "../../interfaces/IAToken.sol"; import "../../interfaces/ILendingPool.sol"; import "../../interfaces/ERC20.sol"; import "../../DS/DSAuth.sol"; contract AaveSavingsProtocol is ProtocolInterface, DSAuth { address public constant ADAI_ADDRESS = 0xfC1E690f61EFd961294b3e1Ce3313fBD8aa4f85d; address public constant AAVE_LENDING_POOL = 0x398eC7346DcD622eDc5ae82352F02bE94C62d119; address public constant AAVE_LENDING_POOL_CORE = 0x3dfd23A6c5E8BbcFc9581d2E864a68feb6a076d3; address public constant DAI_ADDRESS = 0x6B175474E89094C44Da98b954EedeAC495271d0F; function deposit(address _user, uint _amount) public override { require(msg.sender == _user); // get dai from user require(ERC20(DAI_ADDRESS).transferFrom(_user, address(this), _amount)); ERC20(DAI_ADDRESS).approve(AAVE_LENDING_POOL_CORE, uint(-1)); ILendingPool(AAVE_LENDING_POOL).deposit(DAI_ADDRESS, _amount, 0); ERC20(ADAI_ADDRESS).transfer(_user, ERC20(ADAI_ADDRESS).balanceOf(address(this))); } function withdraw(address _user, uint _amount) public override { require(msg.sender == _user); require(ERC20(ADAI_ADDRESS).transferFrom(_user, address(this), _amount)); IAToken(ADAI_ADDRESS).redeem(_amount); // return dai we have to user ERC20(DAI_ADDRESS).transfer(_user, _amount); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../ProtocolInterface.sol"; import "../../interfaces/ERC20.sol"; import "../../interfaces/ITokenInterface.sol"; import "../../DS/DSAuth.sol"; contract FulcrumSavingsProtocol is ProtocolInterface, DSAuth { address public constant NEW_IDAI_ADDRESS = 0x493C57C4763932315A328269E1ADaD09653B9081; address public constant DAI_ADDRESS = 0x6B175474E89094C44Da98b954EedeAC495271d0F; address public savingsProxy; uint public decimals = 10 ** 18; function addSavingsProxy(address _savingsProxy) public auth { savingsProxy = _savingsProxy; } function deposit(address _user, uint _amount) public override { require(msg.sender == _user); // get dai from user require(ERC20(DAI_ADDRESS).transferFrom(_user, address(this), _amount)); // approve dai to Fulcrum ERC20(DAI_ADDRESS).approve(NEW_IDAI_ADDRESS, uint(-1)); // mint iDai ITokenInterface(NEW_IDAI_ADDRESS).mint(_user, _amount); } function withdraw(address _user, uint _amount) public override { require(msg.sender == _user); // transfer all users tokens to our contract require(ERC20(NEW_IDAI_ADDRESS).transferFrom(_user, address(this), ITokenInterface(NEW_IDAI_ADDRESS).balanceOf(_user))); // approve iDai to that contract ERC20(NEW_IDAI_ADDRESS).approve(NEW_IDAI_ADDRESS, uint(-1)); uint tokenPrice = ITokenInterface(NEW_IDAI_ADDRESS).tokenPrice(); // get dai from iDai contract ITokenInterface(NEW_IDAI_ADDRESS).burn(_user, _amount * decimals / tokenPrice); // return all remaining tokens back to user require(ERC20(NEW_IDAI_ADDRESS).transfer(_user, ITokenInterface(NEW_IDAI_ADDRESS).balanceOf(address(this)))); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../ProtocolInterface.sol"; import "./ISoloMargin.sol"; import "../../interfaces/ERC20.sol"; import "../../DS/DSAuth.sol"; contract DydxSavingsProtocol is ProtocolInterface, DSAuth { address public constant SOLO_MARGIN_ADDRESS = 0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e; ISoloMargin public soloMargin; address public savingsProxy; uint daiMarketId = 3; constructor() public { soloMargin = ISoloMargin(SOLO_MARGIN_ADDRESS); } function addSavingsProxy(address _savingsProxy) public auth { savingsProxy = _savingsProxy; } function deposit(address _user, uint _amount) public override { require(msg.sender == _user); Account.Info[] memory accounts = new Account.Info[](1); accounts[0] = getAccount(_user, 0); Actions.ActionArgs[] memory actions = new Actions.ActionArgs[](1); Types.AssetAmount memory amount = Types.AssetAmount({ sign: true, denomination: Types.AssetDenomination.Wei, ref: Types.AssetReference.Delta, value: _amount }); actions[0] = Actions.ActionArgs({ actionType: Actions.ActionType.Deposit, accountId: 0, amount: amount, primaryMarketId: daiMarketId, otherAddress: _user, secondaryMarketId: 0, //not used otherAccountId: 0, //not used data: "" //not used }); soloMargin.operate(accounts, actions); } function withdraw(address _user, uint _amount) public override { require(msg.sender == _user); Account.Info[] memory accounts = new Account.Info[](1); accounts[0] = getAccount(_user, 0); Actions.ActionArgs[] memory actions = new Actions.ActionArgs[](1); Types.AssetAmount memory amount = Types.AssetAmount({ sign: false, denomination: Types.AssetDenomination.Wei, ref: Types.AssetReference.Delta, value: _amount }); actions[0] = Actions.ActionArgs({ actionType: Actions.ActionType.Withdraw, accountId: 0, amount: amount, primaryMarketId: daiMarketId, otherAddress: _user, secondaryMarketId: 0, //not used otherAccountId: 0, //not used data: "" //not used }); soloMargin.operate(accounts, actions); } function getWeiBalance(address _user, uint _index) public view returns(Types.Wei memory) { Types.Wei[] memory weiBalances; (,,weiBalances) = soloMargin.getAccountBalances(getAccount(_user, _index)); return weiBalances[daiMarketId]; } function getParBalance(address _user, uint _index) public view returns(Types.Par memory) { Types.Par[] memory parBalances; (,parBalances,) = soloMargin.getAccountBalances(getAccount(_user, _index)); return parBalances[daiMarketId]; } function getAccount(address _user, uint _index) public pure returns(Account.Info memory) { Account.Info memory account = Account.Info({ owner: _user, number: _index }); return account; } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "./RAISaverTaker.sol"; import "../saver/RAISaverProxy.sol"; import "../../savings/dydx/ISoloMargin.sol"; import "../../exchangeV3/DFSExchangeCore.sol"; contract RAISaverFlashLoan is RAISaverProxy, AdminAuth { address public constant WETH_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; function callFunction( address, Account.Info memory, bytes memory _params ) public { ( bytes memory exDataBytes , RAISaverTaker.SaverData memory saverData ) = abi.decode(_params, (bytes, RAISaverTaker.SaverData)); ExchangeData memory exchangeData = unpackExchangeData(exDataBytes); address managerAddr = getManagerAddr(saverData.managerType); address userProxy = ISAFEManager(managerAddr).ownsSAFE(saverData.safeId); if (saverData.isRepay) { repayWithLoan(exchangeData, saverData); } else { boostWithLoan(exchangeData, saverData); } // payback FL, assumes we have weth TokenInterface(WETH_ADDR).deposit{value: (address(this).balance)}(); ERC20(WETH_ADDR).safeTransfer(userProxy, (saverData.flAmount + 2)); } function boostWithLoan( ExchangeData memory _exchangeData, RAISaverTaker.SaverData memory _saverData ) internal { address managerAddr = getManagerAddr(_saverData.managerType); address user = getOwner(ISAFEManager(managerAddr), _saverData.safeId); bytes32 collType = ISAFEManager(managerAddr).collateralTypes(_saverData.safeId); addCollateral(managerAddr, _saverData.safeId, _saverData.joinAddr, _saverData.flAmount, false); // Draw users Rai uint raiDrawn = drawRai(managerAddr, _saverData.safeId, collType, _exchangeData.srcAmount); // Swap _exchangeData.srcAmount = raiDrawn - takeFee(_saverData.gasCost, raiDrawn); _exchangeData.user = user; _exchangeData.dfsFeeDivider = isAutomation() ? AUTOMATIC_SERVICE_FEE : MANUAL_SERVICE_FEE; (, uint swapedAmount) = _sell(_exchangeData); // Return collateral addCollateral(managerAddr, _saverData.safeId, _saverData.joinAddr, swapedAmount, true); // Draw collateral to repay the flash loan drawCollateral(managerAddr, _saverData.safeId, _saverData.joinAddr, _saverData.flAmount, false); logger.Log(address(this), msg.sender, "RAIFlashBoost", abi.encode(_saverData.safeId, user, _exchangeData.srcAmount, swapedAmount)); } function repayWithLoan( ExchangeData memory _exchangeData, RAISaverTaker.SaverData memory _saverData ) internal { TokenInterface(WETH_ADDR).withdraw(_saverData.flAmount); address managerAddr = getManagerAddr(_saverData.managerType); address user = getOwner(ISAFEManager(managerAddr), _saverData.safeId); bytes32 collType = ISAFEManager(managerAddr).collateralTypes(_saverData.safeId); // Swap _exchangeData.srcAmount = _saverData.flAmount; _exchangeData.user = user; _exchangeData.dfsFeeDivider = isAutomation() ? AUTOMATIC_SERVICE_FEE : MANUAL_SERVICE_FEE; (, uint paybackAmount) = _sell(_exchangeData); paybackAmount -= takeFee(_saverData.gasCost, paybackAmount); paybackAmount = limitLoanAmount(managerAddr, _saverData.safeId, collType, paybackAmount, user); // Payback the debt paybackDebt(managerAddr, _saverData.safeId, collType, paybackAmount, user); // Draw collateral to repay the flash loan drawCollateral(managerAddr, _saverData.safeId, _saverData.joinAddr, _saverData.flAmount, false); logger.Log(address(this), msg.sender, "RAIFlashRepay", abi.encode(_saverData.safeId, user, _exchangeData.srcAmount, paybackAmount)); } /// @notice Handles that the amount is not bigger than cdp debt and not dust function limitLoanAmount(address _managerAddr, uint _safeId, bytes32 _collType, uint _paybackAmount, address _owner) internal returns (uint256) { uint debt = getAllDebt(address(safeEngine), ISAFEManager(_managerAddr).safes(_safeId), ISAFEManager(_managerAddr).safes(_safeId), _collType); if (_paybackAmount > debt) { ERC20(RAI_ADDRESS).transfer(_owner, (_paybackAmount - debt)); return debt; } uint debtLeft = debt - _paybackAmount; (,,,, uint dust,) = safeEngine.collateralTypes(_collType); dust = dust / 10**27; // Less than dust value if (debtLeft < dust) { uint amountOverDust = (dust - debtLeft); ERC20(RAI_ADDRESS).transfer(_owner, amountOverDust); return (_paybackAmount - amountOverDust); } return _paybackAmount; } receive() external override(DFSExchangeCore) payable {} }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../savings/dydx/ISoloMargin.sol"; import "../../utils/SafeERC20.sol"; import "../../interfaces/TokenInterface.sol"; import "../../DS/DSProxy.sol"; import "../AaveHelperV2.sol"; import "../../auth/AdminAuth.sol"; import "../../exchangeV3/DFSExchangeData.sol"; /// @title Import Aave position from account to wallet contract AaveSaverReceiverV2 is AaveHelperV2, AdminAuth, DFSExchangeData { using SafeERC20 for ERC20; address public constant AAVE_SAVER_PROXY = 0xBBCD23145Ab10C369c9e5D3b1D58506B0cD2ab44; address public constant AAVE_BASIC_PROXY = 0xc17c8eB12Ba24D62E69fd57cbd504EEf418867f9; address public constant AETH_ADDRESS = 0x030bA81f1c18d280636F32af80b9AAd02Cf0854e; function callFunction( address sender, Account.Info memory account, bytes memory data ) public { ( bytes memory exchangeDataBytes, address market, uint256 rateMode, uint256 gasCost, bool isRepay, uint256 ethAmount, uint256 txValue, address user, address proxy ) = abi.decode(data, (bytes,address,uint256,uint256,bool,uint256,uint256,address,address)); // withdraw eth TokenInterface(WETH_ADDRESS).withdraw(ethAmount); // deposit eth on behalf of proxy DSProxy(payable(proxy)).execute{value: ethAmount}(AAVE_BASIC_PROXY, abi.encodeWithSignature("deposit(address,address,uint256)", market, ETH_ADDR, ethAmount)); bytes memory functionData = packFunctionCall(market, exchangeDataBytes, rateMode, gasCost, isRepay); DSProxy(payable(proxy)).execute{value: txValue}(AAVE_SAVER_PROXY, functionData); // withdraw deposited eth DSProxy(payable(proxy)).execute(AAVE_BASIC_PROXY, abi.encodeWithSignature("withdraw(address,address,uint256)", market, ETH_ADDR, ethAmount)); // deposit eth, get weth and return to sender TokenInterface(WETH_ADDRESS).deposit.value(address(this).balance)(); ERC20(WETH_ADDRESS).safeTransfer(proxy, ethAmount+2); } function packFunctionCall(address _market, bytes memory _exchangeDataBytes, uint256 _rateMode, uint256 _gasCost, bool _isRepay) internal returns (bytes memory) { ExchangeData memory exData = unpackExchangeData(_exchangeDataBytes); bytes memory functionData; if (_isRepay) { functionData = abi.encodeWithSignature("repay(address,(address,address,uint256,uint256,uint256,uint256,address,address,bytes,(address,address,address,uint256,uint256,bytes)),uint256,uint256)", _market, exData, _rateMode, _gasCost); } else { functionData = abi.encodeWithSignature("boost(address,(address,address,uint256,uint256,uint256,uint256,address,address,bytes,(address,address,address,uint256,uint256,bytes)),uint256,uint256)", _market, exData, _rateMode, _gasCost); } return functionData; } /// @dev if contract receive eth, convert it to WETH receive() external payable { // deposit eth and get weth if (msg.sender == owner) { TokenInterface(WETH_ADDRESS).deposit.value(address(this).balance)(); } } }
pragma solidity ^0.6.0; import "../DS/DSMath.sol"; import "../DS/DSProxy.sol"; import "../utils/Discount.sol"; import "../interfaces/IFeeRecipient.sol"; import "../interfaces/IAToken.sol"; import "../interfaces/ILendingPoolV2.sol"; import "../interfaces/IPriceOracleGetterAave.sol"; import "../interfaces/IAaveProtocolDataProviderV2.sol"; import "../utils/SafeERC20.sol"; import "../utils/BotRegistry.sol"; contract AaveHelperV2 is DSMath { using SafeERC20 for ERC20; IFeeRecipient public constant feeRecipient = IFeeRecipient(0x39C4a92Dc506300c3Ea4c67ca4CA611102ee6F2A); address public constant DISCOUNT_ADDR = 0x1b14E8D511c9A4395425314f849bD737BAF8208F; address public constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // mainnet uint public constant MANUAL_SERVICE_FEE = 400; // 0.25% Fee uint public constant AUTOMATIC_SERVICE_FEE = 333; // 0.3% Fee address public constant BOT_REGISTRY_ADDRESS = 0x637726f8b08a7ABE3aE3aCaB01A80E2d8ddeF77B; address public constant ETH_ADDR = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; uint public constant NINETY_NINE_PERCENT_WEI = 990000000000000000; uint16 public constant AAVE_REFERRAL_CODE = 64; uint public constant STABLE_ID = 1; uint public constant VARIABLE_ID = 2; /// @notice Calculates the gas cost for transaction /// @param _oracleAddress address of oracle used /// @param _amount Amount that is converted /// @param _user Actuall user addr not DSProxy /// @param _gasCost Ether amount of gas we are spending for tx /// @param _tokenAddr token addr. of token we are getting for the fee /// @return gasCost The amount we took for the gas cost function getGasCost(address _oracleAddress, uint _amount, address _user, uint _gasCost, address _tokenAddr) internal returns (uint gasCost) { if (_gasCost == 0) return 0; uint256 price = IPriceOracleGetterAave(_oracleAddress).getAssetPrice(_tokenAddr); _gasCost = wdiv(_gasCost, price) / (10 ** (18 - _getDecimals(_tokenAddr))); gasCost = _gasCost; // gas cost can't go over 10% of the whole amount if (gasCost > (_amount / 10)) { gasCost = _amount / 10; } address walletAddr = feeRecipient.getFeeAddr(); if (_tokenAddr == ETH_ADDR) { payable(walletAddr).transfer(gasCost); } else { ERC20(_tokenAddr).safeTransfer(walletAddr, gasCost); } } /// @notice Returns the owner of the DSProxy that called the contract function getUserAddress() internal view returns (address) { DSProxy proxy = DSProxy(payable(address(this))); return proxy.owner(); } /// @notice Approves token contract to pull underlying tokens from the DSProxy /// @param _tokenAddr Token we are trying to approve /// @param _caller Address which will gain the approval function approveToken(address _tokenAddr, address _caller) internal { if (_tokenAddr != ETH_ADDR) { ERC20(_tokenAddr).safeApprove(_caller, uint256(-1)); } } /// @notice Send specific amount from contract to specific user /// @param _token Token we are trying to send /// @param _user User that should receive funds /// @param _amount Amount that should be sent function sendContractBalance(address _token, address _user, uint _amount) internal { if (_amount == 0) return; if (_token == ETH_ADDR) { payable(_user).transfer(_amount); } else { ERC20(_token).safeTransfer(_user, _amount); } } function sendFullContractBalance(address _token, address _user) internal { if (_token == ETH_ADDR) { sendContractBalance(_token, _user, address(this).balance); } else { sendContractBalance(_token, _user, ERC20(_token).balanceOf(address(this))); } } function _getDecimals(address _token) internal view returns (uint256) { if (_token == ETH_ADDR) return 18; return ERC20(_token).decimals(); } function getDataProvider(address _market) internal view returns(IAaveProtocolDataProviderV2) { return IAaveProtocolDataProviderV2(ILendingPoolAddressesProviderV2(_market).getAddress(0x0100000000000000000000000000000000000000000000000000000000000000)); } }
// SPDX-License-Identifier: agpl-3.0 pragma solidity 0.6.12; pragma experimental ABIEncoderV2; /** * @title LendingPoolAddressesProvider contract * @dev Main registry of addresses part of or connected to the protocol, including permissioned roles * - Acting also as factory of proxies and admin of those, so with right to change its implementations * - Owned by the Aave Governance * @author Aave **/ interface ILendingPoolAddressesProviderV2 { event LendingPoolUpdated(address indexed newAddress); event ConfigurationAdminUpdated(address indexed newAddress); event EmergencyAdminUpdated(address indexed newAddress); event LendingPoolConfiguratorUpdated(address indexed newAddress); event LendingPoolCollateralManagerUpdated(address indexed newAddress); event PriceOracleUpdated(address indexed newAddress); event LendingRateOracleUpdated(address indexed newAddress); event ProxyCreated(bytes32 id, address indexed newAddress); event AddressSet(bytes32 id, address indexed newAddress, bool hasProxy); function setAddress(bytes32 id, address newAddress) external; function setAddressAsProxy(bytes32 id, address impl) external; function getAddress(bytes32 id) external view returns (address); function getLendingPool() external view returns (address); function setLendingPoolImpl(address pool) external; function getLendingPoolConfigurator() external view returns (address); function setLendingPoolConfiguratorImpl(address configurator) external; function getLendingPoolCollateralManager() external view returns (address); function setLendingPoolCollateralManager(address manager) external; function getPoolAdmin() external view returns (address); function setPoolAdmin(address admin) external; function getEmergencyAdmin() external view returns (address); function setEmergencyAdmin(address admin) external; function getPriceOracle() external view returns (address); function setPriceOracle(address priceOracle) external; function getLendingRateOracle() external view returns (address); function setLendingRateOracle(address lendingRateOracle) external; } library DataTypes { // refer to the whitepaper, section 1.1 basic concepts for a formal description of these properties. struct ReserveData { //stores the reserve configuration ReserveConfigurationMap configuration; //the liquidity index. Expressed in ray uint128 liquidityIndex; //variable borrow index. Expressed in ray uint128 variableBorrowIndex; //the current supply rate. Expressed in ray uint128 currentLiquidityRate; //the current variable borrow rate. Expressed in ray uint128 currentVariableBorrowRate; //the current stable borrow rate. Expressed in ray uint128 currentStableBorrowRate; uint40 lastUpdateTimestamp; //tokens addresses address aTokenAddress; address stableDebtTokenAddress; address variableDebtTokenAddress; //address of the interest rate strategy address interestRateStrategyAddress; //the id of the reserve. Represents the position in the list of the active reserves uint8 id; } struct ReserveConfigurationMap { //bit 0-15: LTV //bit 16-31: Liq. threshold //bit 32-47: Liq. bonus //bit 48-55: Decimals //bit 56: Reserve is active //bit 57: reserve is frozen //bit 58: borrowing is enabled //bit 59: stable rate borrowing enabled //bit 60-63: reserved //bit 64-79: reserve factor uint256 data; } struct UserConfigurationMap { uint256 data; } enum InterestRateMode {NONE, STABLE, VARIABLE} } interface ILendingPoolV2 { /** * @dev Emitted on deposit() * @param reserve The address of the underlying asset of the reserve * @param user The address initiating the deposit * @param onBehalfOf The beneficiary of the deposit, receiving the aTokens * @param amount The amount deposited * @param referral The referral code used **/ event Deposit( address indexed reserve, address user, address indexed onBehalfOf, uint256 amount, uint16 indexed referral ); /** * @dev Emitted on withdraw() * @param reserve The address of the underlyng asset being withdrawn * @param user The address initiating the withdrawal, owner of aTokens * @param to Address that will receive the underlying * @param amount The amount to be withdrawn **/ event Withdraw(address indexed reserve, address indexed user, address indexed to, uint256 amount); /** * @dev Emitted on borrow() and flashLoan() when debt needs to be opened * @param reserve The address of the underlying asset being borrowed * @param user The address of the user initiating the borrow(), receiving the funds on borrow() or just * initiator of the transaction on flashLoan() * @param onBehalfOf The address that will be getting the debt * @param amount The amount borrowed out * @param borrowRateMode The rate mode: 1 for Stable, 2 for Variable * @param borrowRate The numeric rate at which the user has borrowed * @param referral The referral code used **/ event Borrow( address indexed reserve, address user, address indexed onBehalfOf, uint256 amount, uint256 borrowRateMode, uint256 borrowRate, uint16 indexed referral ); /** * @dev Emitted on repay() * @param reserve The address of the underlying asset of the reserve * @param user The beneficiary of the repayment, getting his debt reduced * @param repayer The address of the user initiating the repay(), providing the funds * @param amount The amount repaid **/ event Repay( address indexed reserve, address indexed user, address indexed repayer, uint256 amount ); /** * @dev Emitted on swapBorrowRateMode() * @param reserve The address of the underlying asset of the reserve * @param user The address of the user swapping his rate mode * @param rateMode The rate mode that the user wants to swap to **/ event Swap(address indexed reserve, address indexed user, uint256 rateMode); /** * @dev Emitted on setUserUseReserveAsCollateral() * @param reserve The address of the underlying asset of the reserve * @param user The address of the user enabling the usage as collateral **/ event ReserveUsedAsCollateralEnabled(address indexed reserve, address indexed user); /** * @dev Emitted on setUserUseReserveAsCollateral() * @param reserve The address of the underlying asset of the reserve * @param user The address of the user enabling the usage as collateral **/ event ReserveUsedAsCollateralDisabled(address indexed reserve, address indexed user); /** * @dev Emitted on rebalanceStableBorrowRate() * @param reserve The address of the underlying asset of the reserve * @param user The address of the user for which the rebalance has been executed **/ event RebalanceStableBorrowRate(address indexed reserve, address indexed user); /** * @dev Emitted on flashLoan() * @param target The address of the flash loan receiver contract * @param initiator The address initiating the flash loan * @param asset The address of the asset being flash borrowed * @param amount The amount flash borrowed * @param premium The fee flash borrowed * @param referralCode The referral code used **/ event FlashLoan( address indexed target, address indexed initiator, address indexed asset, uint256 amount, uint256 premium, uint16 referralCode ); /** * @dev Emitted when the pause is triggered. */ event Paused(); /** * @dev Emitted when the pause is lifted. */ event Unpaused(); /** * @dev Emitted when a borrower is liquidated. This event is emitted by the LendingPool via * LendingPoolCollateral manager using a DELEGATECALL * This allows to have the events in the generated ABI for LendingPool. * @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation * @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation * @param user The address of the borrower getting liquidated * @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover * @param liquidatedCollateralAmount The amount of collateral received by the liiquidator * @param liquidator The address of the liquidator * @param receiveAToken `true` if the liquidators wants to receive the collateral aTokens, `false` if he wants * to receive the underlying collateral asset directly **/ event LiquidationCall( address indexed collateralAsset, address indexed debtAsset, address indexed user, uint256 debtToCover, uint256 liquidatedCollateralAmount, address liquidator, bool receiveAToken ); /** * @dev Emitted when the state of a reserve is updated. NOTE: This event is actually declared * in the ReserveLogic library and emitted in the updateInterestRates() function. Since the function is internal, * the event will actually be fired by the LendingPool contract. The event is therefore replicated here so it * gets added to the LendingPool ABI * @param reserve The address of the underlying asset of the reserve * @param liquidityRate The new liquidity rate * @param stableBorrowRate The new stable borrow rate * @param variableBorrowRate The new variable borrow rate * @param liquidityIndex The new liquidity index * @param variableBorrowIndex The new variable borrow index **/ event ReserveDataUpdated( address indexed reserve, uint256 liquidityRate, uint256 stableBorrowRate, uint256 variableBorrowRate, uint256 liquidityIndex, uint256 variableBorrowIndex ); /** * @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens. * - E.g. User deposits 100 USDC and gets in return 100 aUSDC * @param asset The address of the underlying asset to deposit * @param amount The amount to be deposited * @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user * wants to receive them on his own wallet, or a different address if the beneficiary of aTokens * is a different wallet * @param referralCode Code used to register the integrator originating the operation, for potential rewards. * 0 if the action is executed directly by the user, without any middle-man **/ function deposit( address asset, uint256 amount, address onBehalfOf, uint16 referralCode ) external; /** * @dev Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned * E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC * @param asset The address of the underlying asset to withdraw * @param amount The underlying amount to be withdrawn * - Send the value type(uint256).max in order to withdraw the whole aToken balance * @param to Address that will receive the underlying, same as msg.sender if the user * wants to receive it on his own wallet, or a different address if the beneficiary is a * different wallet **/ function withdraw( address asset, uint256 amount, address to ) external; /** * @dev Allows users to borrow a specific `amount` of the reserve underlying asset, provided that the borrower * already deposited enough collateral, or he was given enough allowance by a credit delegator on the * corresponding debt token (StableDebtToken or VariableDebtToken) * - E.g. User borrows 100 USDC passing as `onBehalfOf` his own address, receiving the 100 USDC in his wallet * and 100 stable/variable debt tokens, depending on the `interestRateMode` * @param asset The address of the underlying asset to borrow * @param amount The amount to be borrowed * @param interestRateMode The interest rate mode at which the user wants to borrow: 1 for Stable, 2 for Variable * @param referralCode Code used to register the integrator originating the operation, for potential rewards. * 0 if the action is executed directly by the user, without any middle-man * @param onBehalfOf Address of the user who will receive the debt. Should be the address of the borrower itself * calling the function if he wants to borrow against his own collateral, or the address of the credit delegator * if he has been given credit delegation allowance **/ function borrow( address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf ) external; /** * @notice Repays a borrowed `amount` on a specific reserve, burning the equivalent debt tokens owned * - E.g. User repays 100 USDC, burning 100 variable/stable debt tokens of the `onBehalfOf` address * @param asset The address of the borrowed underlying asset previously borrowed * @param amount The amount to repay * - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode` * @param rateMode The interest rate mode at of the debt the user wants to repay: 1 for Stable, 2 for Variable * @param onBehalfOf Address of the user who will get his debt reduced/removed. Should be the address of the * user calling the function if he wants to reduce/remove his own debt, or the address of any other * other borrower whose debt should be removed **/ function repay( address asset, uint256 amount, uint256 rateMode, address onBehalfOf ) external; /** * @dev Allows a borrower to swap his debt between stable and variable mode, or viceversa * @param asset The address of the underlying asset borrowed * @param rateMode The rate mode that the user wants to swap to **/ function swapBorrowRateMode(address asset, uint256 rateMode) external; /** * @dev Rebalances the stable interest rate of a user to the current stable rate defined on the reserve. * - Users can be rebalanced if the following conditions are satisfied: * 1. Usage ratio is above 95% * 2. the current deposit APY is below REBALANCE_UP_THRESHOLD * maxVariableBorrowRate, which means that too much has been * borrowed at a stable rate and depositors are not earning enough * @param asset The address of the underlying asset borrowed * @param user The address of the user to be rebalanced **/ function rebalanceStableBorrowRate(address asset, address user) external; /** * @dev Allows depositors to enable/disable a specific deposited asset as collateral * @param asset The address of the underlying asset deposited * @param useAsCollateral `true` if the user wants to use the deposit as collateral, `false` otherwise **/ function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external; /** * @dev Function to liquidate a non-healthy position collateral-wise, with Health Factor below 1 * - The caller (liquidator) covers `debtToCover` amount of debt of the user getting liquidated, and receives * a proportionally amount of the `collateralAsset` plus a bonus to cover market risk * @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation * @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation * @param user The address of the borrower getting liquidated * @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover * @param receiveAToken `true` if the liquidators wants to receive the collateral aTokens, `false` if he wants * to receive the underlying collateral asset directly **/ function liquidationCall( address collateralAsset, address debtAsset, address user, uint256 debtToCover, bool receiveAToken ) external; /** * @dev Allows smartcontracts to access the liquidity of the pool within one transaction, * as long as the amount taken plus a fee is returned. * IMPORTANT There are security concerns for developers of flashloan receiver contracts that must be kept into consideration. * For further details please visit https://developers.aave.com * @param receiverAddress The address of the contract receiving the funds, implementing the IFlashLoanReceiver interface * @param assets The addresses of the assets being flash-borrowed * @param amounts The amounts amounts being flash-borrowed * @param modes Types of the debt to open if the flash loan is not returned: * 0 -> Don't open any debt, just revert if funds can't be transferred from the receiver * 1 -> Open debt at stable rate for the value of the amount flash-borrowed to the `onBehalfOf` address * 2 -> Open debt at variable rate for the value of the amount flash-borrowed to the `onBehalfOf` address * @param onBehalfOf The address that will receive the debt in the case of using on `modes` 1 or 2 * @param params Variadic packed params to pass to the receiver as extra information * @param referralCode Code used to register the integrator originating the operation, for potential rewards. * 0 if the action is executed directly by the user, without any middle-man **/ function flashLoan( address receiverAddress, address[] calldata assets, uint256[] calldata amounts, uint256[] calldata modes, address onBehalfOf, bytes calldata params, uint16 referralCode ) external; /** * @dev Returns the user account data across all the reserves * @param user The address of the user * @return totalCollateralETH the total collateral in ETH of the user * @return totalDebtETH the total debt in ETH of the user * @return availableBorrowsETH the borrowing power left of the user * @return currentLiquidationThreshold the liquidation threshold of the user * @return ltv the loan to value of the user * @return healthFactor the current health factor of the user **/ function getUserAccountData(address user) external view returns ( uint256 totalCollateralETH, uint256 totalDebtETH, uint256 availableBorrowsETH, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor ); function initReserve( address reserve, address aTokenAddress, address stableDebtAddress, address variableDebtAddress, address interestRateStrategyAddress ) external; function setReserveInterestRateStrategyAddress(address reserve, address rateStrategyAddress) external; function setConfiguration(address reserve, uint256 configuration) external; /** * @dev Returns the configuration of the reserve * @param asset The address of the underlying asset of the reserve * @return The configuration of the reserve **/ function getConfiguration(address asset) external view returns (DataTypes.ReserveConfigurationMap memory); /** * @dev Returns the configuration of the user across all the reserves * @param user The user address * @return The configuration of the user **/ function getUserConfiguration(address user) external view returns (DataTypes.UserConfigurationMap memory); /** * @dev Returns the normalized income normalized income of the reserve * @param asset The address of the underlying asset of the reserve * @return The reserve's normalized income */ function getReserveNormalizedIncome(address asset) external view returns (uint256); /** * @dev Returns the normalized variable debt per unit of asset * @param asset The address of the underlying asset of the reserve * @return The reserve normalized variable debt */ function getReserveNormalizedVariableDebt(address asset) external view returns (uint256); /** * @dev Returns the state and configuration of the reserve * @param asset The address of the underlying asset of the reserve * @return The state of the reserve **/ function getReserveData(address asset) external view returns (DataTypes.ReserveData memory); function finalizeTransfer( address asset, address from, address to, uint256 amount, uint256 balanceFromAfter, uint256 balanceToBefore ) external; function getReservesList() external view returns (address[] memory); function getAddressesProvider() external view returns (ILendingPoolAddressesProviderV2); function setPause(bool val) external; function paused() external view returns (bool); }
pragma solidity ^0.6.0; /************ @title IPriceOracleGetterAave interface @notice Interface for the Aave price oracle.*/ abstract contract IPriceOracleGetterAave { function getAssetPrice(address _asset) external virtual view returns (uint256); function getAssetsPrices(address[] calldata _assets) external virtual view returns(uint256[] memory); function getSourceOfAsset(address _asset) external virtual view returns(address); function getFallbackOracle() external virtual view returns(address); }
// SPDX-License-Identifier: agpl-3.0 pragma solidity ^0.6.8; pragma experimental ABIEncoderV2; abstract contract IAaveProtocolDataProviderV2 { struct TokenData { string symbol; address tokenAddress; } function getAllReservesTokens() external virtual view returns (TokenData[] memory); function getAllATokens() external virtual view returns (TokenData[] memory); function getReserveConfigurationData(address asset) external virtual view returns ( uint256 decimals, uint256 ltv, uint256 liquidationThreshold, uint256 liquidationBonus, uint256 reserveFactor, bool usageAsCollateralEnabled, bool borrowingEnabled, bool stableBorrowRateEnabled, bool isActive, bool isFrozen ); function getReserveData(address asset) external virtual view returns ( uint256 availableLiquidity, uint256 totalStableDebt, uint256 totalVariableDebt, uint256 liquidityRate, uint256 variableBorrowRate, uint256 stableBorrowRate, uint256 averageStableBorrowRate, uint256 liquidityIndex, uint256 variableBorrowIndex, uint40 lastUpdateTimestamp ); function getUserReserveData(address asset, address user) external virtual view returns ( uint256 currentATokenBalance, uint256 currentStableDebt, uint256 currentVariableDebt, uint256 principalStableDebt, uint256 scaledVariableDebt, uint256 stableBorrowRate, uint256 liquidityRate, uint40 stableRateLastUpdated, bool usageAsCollateralEnabled ); function getReserveTokensAddresses(address asset) external virtual view returns ( address aTokenAddress, address stableDebtTokenAddress, address variableDebtTokenAddress ); }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../AaveHelperV2.sol"; import "../../../utils/GasBurner.sol"; import "../../../auth/AdminAuth.sol"; import "../../../auth/ProxyPermission.sol"; import "../../../utils/DydxFlashLoanBase.sol"; import "../../../loggers/DefisaverLogger.sol"; import "../../../interfaces/ProxyRegistryInterface.sol"; import "../../../interfaces/TokenInterface.sol"; import "../../../interfaces/ERC20.sol"; import "../../../exchangeV3/DFSExchangeData.sol"; /// @title Import Aave position from account to wallet /// @dev Contract needs to have enough wei in WETH for all transactions (2 WETH wei per transaction) contract AaveSaverTakerOV2 is ProxyPermission, GasBurner, DFSExchangeData, AaveHelperV2 { address payable public constant AAVE_RECEIVER = 0xf852572bCdD36648999722751c29F40fE584da43; // leaving _flAmount to be the same as the older version function repay(address _market, ExchangeData memory _data, uint _rateMode, uint256 _gasCost, uint _flAmount) public payable burnGas(10) { address lendingPool = ILendingPoolAddressesProviderV2(_market).getLendingPool(); // send msg.value for exchange to the receiver AAVE_RECEIVER.transfer(msg.value); address[] memory assets = new address[](1); assets[0] = _data.srcAddr; uint256[] memory amounts = new uint256[](1); amounts[0] = _data.srcAmount; // for repay we are using regular flash loan with paying back the flash loan + premium uint256[] memory modes = new uint256[](1); modes[0] = 0; // create data bytes memory encodedData = packExchangeData(_data); bytes memory data = abi.encode(encodedData, _market, _gasCost, _rateMode, true, address(this)); // give permission to receiver and execute tx givePermission(AAVE_RECEIVER); ILendingPoolV2(lendingPool).flashLoan(AAVE_RECEIVER, assets, amounts, modes, address(this), data, AAVE_REFERRAL_CODE); removePermission(AAVE_RECEIVER); } // leaving _flAmount to be the same as the older version function boost(address _market, ExchangeData memory _data, uint _rateMode, uint256 _gasCost, uint _flAmount) public payable burnGas(10) { address lendingPool = ILendingPoolAddressesProviderV2(_market).getLendingPool(); // send msg.value for exchange to the receiver AAVE_RECEIVER.transfer(msg.value); address[] memory assets = new address[](1); assets[0] = _data.srcAddr; uint256[] memory amounts = new uint256[](1); amounts[0] = _data.srcAmount; uint256[] memory modes = new uint256[](1); modes[0] = _rateMode; // create data bytes memory encodedData = packExchangeData(_data); bytes memory data = abi.encode(encodedData, _market, _gasCost, _rateMode, false, address(this)); // give permission to receiver and execute tx givePermission(AAVE_RECEIVER); ILendingPoolV2(lendingPool).flashLoan(AAVE_RECEIVER, assets, amounts, modes, address(this), data, AAVE_REFERRAL_CODE); removePermission(AAVE_RECEIVER); } }
pragma solidity ^0.6.0; import "./DSProxyInterface.sol"; abstract contract ProxyRegistryInterface { function proxies(address _owner) public virtual view returns (address); function build(address) public virtual returns (address); }
pragma solidity ^0.6.0; import "../../interfaces/ExchangeInterfaceV3.sol"; import "../../interfaces/OasisInterface.sol"; import "../../interfaces/TokenInterface.sol"; import "../../DS/DSMath.sol"; import "../../utils/SafeERC20.sol"; import "../../auth/AdminAuth.sol"; contract OasisTradeWrapperV3 is DSMath, ExchangeInterfaceV3, AdminAuth { using SafeERC20 for ERC20; address public constant OTC_ADDRESS = 0x794e6e91555438aFc3ccF1c5076A74F42133d08D; address public constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address public constant KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; /// @notice Sells a _srcAmount of tokens at Oasis /// @param _srcAddr From token /// @param _destAddr To token /// @param _srcAmount From amount /// @return uint Destination amount function sell(address _srcAddr, address _destAddr, uint _srcAmount, bytes memory _additionalData) external override payable returns (uint) { address srcAddr = ethToWethAddr(_srcAddr); address destAddr = ethToWethAddr(_destAddr); ERC20(srcAddr).safeApprove(OTC_ADDRESS, _srcAmount); uint destAmount = OasisInterface(OTC_ADDRESS).sellAllAmount(srcAddr, _srcAmount, destAddr, 0); // convert weth -> eth and send back if (destAddr == WETH_ADDRESS) { TokenInterface(WETH_ADDRESS).withdraw(destAmount); msg.sender.transfer(destAmount); } else { ERC20(destAddr).safeTransfer(msg.sender, destAmount); } return destAmount; } /// @notice Buys a _destAmount of tokens at Oasis /// @param _srcAddr From token /// @param _destAddr To token /// @param _destAmount To amount /// @return uint srcAmount function buy(address _srcAddr, address _destAddr, uint _destAmount, bytes memory _additionalData) external override payable returns(uint) { address srcAddr = ethToWethAddr(_srcAddr); address destAddr = ethToWethAddr(_destAddr); ERC20(srcAddr).safeApprove(OTC_ADDRESS, uint(-1)); uint srcAmount = OasisInterface(OTC_ADDRESS).buyAllAmount(destAddr, _destAmount, srcAddr, uint(-1)); // convert weth -> eth and send back if (destAddr == WETH_ADDRESS) { TokenInterface(WETH_ADDRESS).withdraw(_destAmount); msg.sender.transfer(_destAmount); } else { ERC20(destAddr).safeTransfer(msg.sender, _destAmount); } // Send the leftover from the source token back sendLeftOver(srcAddr); return srcAmount; } /// @notice Return a rate for which we can sell an amount of tokens /// @param _srcAddr From token /// @param _destAddr To token /// @param _srcAmount From amount /// @return uint Rate function getSellRate(address _srcAddr, address _destAddr, uint _srcAmount, bytes memory _additionalData) public override view returns (uint) { address srcAddr = ethToWethAddr(_srcAddr); address destAddr = ethToWethAddr(_destAddr); return wdiv(OasisInterface(OTC_ADDRESS).getBuyAmount(destAddr, srcAddr, _srcAmount), _srcAmount); } /// @notice Return a rate for which we can buy an amount of tokens /// @param _srcAddr From token /// @param _destAddr To token /// @param _destAmount To amount /// @return uint Rate function getBuyRate(address _srcAddr, address _destAddr, uint _destAmount, bytes memory _additionalData) public override view returns (uint) { address srcAddr = ethToWethAddr(_srcAddr); address destAddr = ethToWethAddr(_destAddr); return wdiv(1 ether, wdiv(OasisInterface(OTC_ADDRESS).getPayAmount(srcAddr, destAddr, _destAmount), _destAmount)); } /// @notice Send any leftover tokens, we use to clear out srcTokens after buy /// @param _srcAddr Source token address function sendLeftOver(address _srcAddr) internal { msg.sender.transfer(address(this).balance); if (_srcAddr != KYBER_ETH_ADDRESS) { ERC20(_srcAddr).safeTransfer(msg.sender, ERC20(_srcAddr).balanceOf(address(this))); } } /// @notice Converts Kybers Eth address -> Weth /// @param _src Input address function ethToWethAddr(address _src) internal pure returns (address) { return _src == KYBER_ETH_ADDRESS ? WETH_ADDRESS : _src; } receive() payable external {} }
pragma solidity ^0.6.0; abstract contract OasisInterface { function getBuyAmount(address tokenToBuy, address tokenToPay, uint256 amountToPay) external virtual view returns (uint256 amountBought); function getPayAmount(address tokenToPay, address tokenToBuy, uint256 amountToBuy) public virtual view returns (uint256 amountPaid); function sellAllAmount(address pay_gem, uint256 pay_amt, address buy_gem, uint256 min_fill_amount) public virtual returns (uint256 fill_amt); function buyAllAmount(address buy_gem, uint256 buy_amt, address pay_gem, uint256 max_fill_amount) public virtual returns (uint256 fill_amt); }
pragma solidity ^0.6.0; import "../../interfaces/Join.sol"; import "../../interfaces/ERC20.sol"; import "../../interfaces/Vat.sol"; import "../../interfaces/Flipper.sol"; import "../../interfaces/Gem.sol"; contract BidProxy { address public constant DAI_JOIN = 0x9759A6Ac90977b93B58547b4A71c78317f391A28; address public constant VAT_ADDRESS = 0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B; address public constant DAI_ADDRESS = 0x6B175474E89094C44Da98b954EedeAC495271d0F; function daiBid(uint _bidId, uint _amount, address _flipper) public { uint tendAmount = _amount * (10 ** 27); joinDai(_amount); (, uint lot, , , , , , ) = Flipper(_flipper).bids(_bidId); Vat(VAT_ADDRESS).hope(_flipper); Flipper(_flipper).tend(_bidId, lot, tendAmount); } function collateralBid(uint _bidId, uint _amount, address _flipper) public { (uint bid, , , , , , , ) = Flipper(_flipper).bids(_bidId); joinDai(bid / (10**27)); Vat(VAT_ADDRESS).hope(_flipper); Flipper(_flipper).dent(_bidId, _amount, bid); } function closeBid(uint _bidId, address _flipper, address _joinAddr) public { bytes32 ilk = Join(_joinAddr).ilk(); Flipper(_flipper).deal(_bidId); uint amount = Vat(VAT_ADDRESS).gem(ilk, address(this)); Vat(VAT_ADDRESS).hope(_joinAddr); Gem(_joinAddr).exit(msg.sender, amount); } function exitCollateral(address _joinAddr) public { bytes32 ilk = Join(_joinAddr).ilk(); uint amount = Vat(VAT_ADDRESS).gem(ilk, address(this)); if(Join(_joinAddr).dec() != 18) { amount = amount / (10**(18 - Join(_joinAddr).dec())); } Vat(VAT_ADDRESS).hope(_joinAddr); Gem(_joinAddr).exit(msg.sender, amount); } function exitDai() public { uint amount = Vat(VAT_ADDRESS).dai(address(this)) / (10**27); Vat(VAT_ADDRESS).hope(DAI_JOIN); Gem(DAI_JOIN).exit(msg.sender, amount); } function withdrawToken(address _token) public { uint balance = ERC20(_token).balanceOf(address(this)); ERC20(_token).transfer(msg.sender, balance); } function withdrawEth() public { uint balance = address(this).balance; msg.sender.transfer(balance); } function joinDai(uint _amount) internal { uint amountInVat = Vat(VAT_ADDRESS).dai(address(this)) / (10**27); if (_amount > amountInVat) { uint amountDiff = (_amount - amountInVat) + 1; ERC20(DAI_ADDRESS).transferFrom(msg.sender, address(this), amountDiff); ERC20(DAI_ADDRESS).approve(DAI_JOIN, amountDiff); Join(DAI_JOIN).join(address(this), amountDiff); } } }
pragma solidity ^0.6.0; abstract contract Flipper { function bids(uint _bidId) public virtual returns (uint256, uint256, address, uint48, uint48, address, address, uint256); function tend(uint id, uint lot, uint bid) virtual external; function dent(uint id, uint lot, uint bid) virtual external; function deal(uint id) virtual external; }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "./saver/MCDSaverProxyHelper.sol"; import "../interfaces/Spotter.sol"; contract MCDLoanInfo is MCDSaverProxyHelper { Manager public constant manager = Manager(0x5ef30b9986345249bc32d8928B7ee64DE9435E39); Vat public constant vat = Vat(0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B); Spotter public constant spotter = Spotter(0x65C79fcB50Ca1594B025960e539eD7A9a6D434A3); struct VaultInfo { address owner; uint256 ratio; uint256 collateral; uint256 debt; bytes32 ilk; address urn; } /// @notice Gets a price of the asset /// @param _ilk Ilk of the CDP function getPrice(bytes32 _ilk) public view returns (uint) { (, uint mat) = spotter.ilks(_ilk); (,,uint spot,,) = vat.ilks(_ilk); return rmul(rmul(spot, spotter.par()), mat); } /// @notice Gets CDP ratio /// @param _cdpId Id of the CDP /// @param _ilk Ilk of the CDP function getRatio(uint _cdpId, bytes32 _ilk) public view returns (uint) { uint price = getPrice( _ilk); (uint collateral, uint debt) = getCdpInfo(manager, _cdpId, _ilk); if (debt == 0) return 0; return rdiv(wmul(collateral, price), debt); } /// @notice Gets CDP info (collateral, debt, price, ilk) /// @param _cdpId Id of the CDP function getVaultInfo(uint _cdpId) public view returns (VaultInfo memory vaultInfo) { address urn = manager.urns(_cdpId); bytes32 ilk = manager.ilks(_cdpId); (uint256 collateral, uint256 debt) = vat.urns(ilk, urn); (,uint rate,,,) = vat.ilks(ilk); debt = rmul(debt, rate); vaultInfo = VaultInfo({ owner: manager.owns(_cdpId), ratio: getRatio(_cdpId, ilk), collateral: collateral, debt: debt, ilk: ilk, urn: urn }); } function getVaultInfos(uint256[] memory _cdps) public view returns (VaultInfo[] memory vaultInfos) { vaultInfos = new VaultInfo[](_cdps.length); for (uint256 i = 0; i < _cdps.length; i++) { vaultInfos[i] = getVaultInfo(_cdps[i]); } } function getRatios(uint256[] memory _cdps) public view returns (uint[] memory ratios) { ratios = new uint256[](_cdps.length); for (uint256 i = 0; i<_cdps.length; i++) { bytes32 ilk = manager.ilks(_cdps[i]); ratios[i] = getRatio(_cdps[i], ilk); } } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../interfaces/Manager.sol"; import "./StaticV2.sol"; import "../saver/MCDSaverProxy.sol"; import "../../interfaces/Vat.sol"; import "../../interfaces/Spotter.sol"; import "../../auth/AdminAuth.sol"; /// @title Handles subscriptions for automatic monitoring contract SubscriptionsV2 is AdminAuth, StaticV2 { bytes32 internal constant ETH_ILK = 0x4554482d41000000000000000000000000000000000000000000000000000000; bytes32 internal constant BAT_ILK = 0x4241542d41000000000000000000000000000000000000000000000000000000; address public constant MANAGER_ADDRESS = 0x5ef30b9986345249bc32d8928B7ee64DE9435E39; address public constant VAT_ADDRESS = 0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B; address public constant SPOTTER_ADDRESS = 0x65C79fcB50Ca1594B025960e539eD7A9a6D434A3; CdpHolder[] public subscribers; mapping (uint => SubPosition) public subscribersPos; mapping (bytes32 => uint) public minLimits; uint public changeIndex; Manager public manager = Manager(MANAGER_ADDRESS); Vat public vat = Vat(VAT_ADDRESS); Spotter public spotter = Spotter(SPOTTER_ADDRESS); MCDSaverProxy public saverProxy; event Subscribed(address indexed owner, uint cdpId); event Unsubscribed(address indexed owner, uint cdpId); event Updated(address indexed owner, uint cdpId); event ParamUpdates(address indexed owner, uint cdpId, uint128, uint128, uint128, uint128, bool boostEnabled); /// @param _saverProxy Address of the MCDSaverProxy contract constructor(address _saverProxy) public { saverProxy = MCDSaverProxy(payable(_saverProxy)); minLimits[ETH_ILK] = 1700000000000000000; minLimits[BAT_ILK] = 1700000000000000000; } /// @dev Called by the DSProxy contract which owns the CDP /// @notice Adds the users CDP in the list of subscriptions so it can be monitored /// @param _cdpId Id of the CDP /// @param _minRatio Minimum ratio below which repay is triggered /// @param _maxRatio Maximum ratio after which boost is triggered /// @param _optimalBoost Ratio amount which boost should target /// @param _optimalRepay Ratio amount which repay should target /// @param _boostEnabled Boolean determing if boost is enabled /// @param _nextPriceEnabled Boolean determing if we can use nextPrice for this cdp function subscribe(uint _cdpId, uint128 _minRatio, uint128 _maxRatio, uint128 _optimalBoost, uint128 _optimalRepay, bool _boostEnabled, bool _nextPriceEnabled) external { require(isOwner(msg.sender, _cdpId), "Must be called by Cdp owner"); // if boost is not enabled, set max ratio to max uint uint128 localMaxRatio = _boostEnabled ? _maxRatio : uint128(-1); require(checkParams(manager.ilks(_cdpId), _minRatio, localMaxRatio), "Must be correct params"); SubPosition storage subInfo = subscribersPos[_cdpId]; CdpHolder memory subscription = CdpHolder({ minRatio: _minRatio, maxRatio: localMaxRatio, optimalRatioBoost: _optimalBoost, optimalRatioRepay: _optimalRepay, owner: msg.sender, cdpId: _cdpId, boostEnabled: _boostEnabled, nextPriceEnabled: _nextPriceEnabled }); changeIndex++; if (subInfo.subscribed) { subscribers[subInfo.arrPos] = subscription; emit Updated(msg.sender, _cdpId); emit ParamUpdates(msg.sender, _cdpId, _minRatio, localMaxRatio, _optimalBoost, _optimalRepay, _boostEnabled); } else { subscribers.push(subscription); subInfo.arrPos = subscribers.length - 1; subInfo.subscribed = true; emit Subscribed(msg.sender, _cdpId); } } /// @notice Called by the users DSProxy /// @dev Owner who subscribed cancels his subscription function unsubscribe(uint _cdpId) external { require(isOwner(msg.sender, _cdpId), "Must be called by Cdp owner"); _unsubscribe(_cdpId); } /// @dev Checks if the _owner is the owner of the CDP function isOwner(address _owner, uint _cdpId) internal view returns (bool) { return getOwner(_cdpId) == _owner; } /// @dev Checks limit for minimum ratio and if minRatio is bigger than max function checkParams(bytes32 _ilk, uint128 _minRatio, uint128 _maxRatio) internal view returns (bool) { if (_minRatio < minLimits[_ilk]) { return false; } if (_minRatio > _maxRatio) { return false; } return true; } /// @dev Internal method to remove a subscriber from the list function _unsubscribe(uint _cdpId) internal { require(subscribers.length > 0, "Must have subscribers in the list"); SubPosition storage subInfo = subscribersPos[_cdpId]; require(subInfo.subscribed, "Must first be subscribed"); uint lastCdpId = subscribers[subscribers.length - 1].cdpId; SubPosition storage subInfo2 = subscribersPos[lastCdpId]; subInfo2.arrPos = subInfo.arrPos; subscribers[subInfo.arrPos] = subscribers[subscribers.length - 1]; subscribers.pop(); changeIndex++; subInfo.subscribed = false; subInfo.arrPos = 0; emit Unsubscribed(msg.sender, _cdpId); } /// @notice Returns an address that owns the CDP /// @param _cdpId Id of the CDP function getOwner(uint _cdpId) public view returns(address) { return manager.owns(_cdpId); } /// @notice Helper method for the front to get all the info about the subscribed CDP function getSubscribedInfo(uint _cdpId) public view returns(bool, uint128, uint128, uint128, uint128, address, uint coll, uint debt) { SubPosition memory subInfo = subscribersPos[_cdpId]; if (!subInfo.subscribed) return (false, 0, 0, 0, 0, address(0), 0, 0); (coll, debt) = saverProxy.getCdpInfo(manager, _cdpId, manager.ilks(_cdpId)); CdpHolder memory subscriber = subscribers[subInfo.arrPos]; return ( true, subscriber.minRatio, subscriber.maxRatio, subscriber.optimalRatioRepay, subscriber.optimalRatioBoost, subscriber.owner, coll, debt ); } function getCdpHolder(uint _cdpId) public view returns (bool subscribed, CdpHolder memory) { SubPosition memory subInfo = subscribersPos[_cdpId]; if (!subInfo.subscribed) return (false, CdpHolder(0, 0, 0, 0, address(0), 0, false, false)); CdpHolder memory subscriber = subscribers[subInfo.arrPos]; return (true, subscriber); } /// @notice Helper method for the front to get the information about the ilk of a CDP function getIlkInfo(bytes32 _ilk, uint _cdpId) public view returns(bytes32 ilk, uint art, uint rate, uint spot, uint line, uint dust, uint mat, uint par) { // send either ilk or cdpId if (_ilk == bytes32(0)) { _ilk = manager.ilks(_cdpId); } ilk = _ilk; (,mat) = spotter.ilks(_ilk); par = spotter.par(); (art, rate, spot, line, dust) = vat.ilks(_ilk); } /// @notice Helper method to return all the subscribed CDPs function getSubscribers() public view returns (CdpHolder[] memory) { return subscribers; } /// @notice Helper method to return all the subscribed CDPs function getSubscribersByPage(uint _page, uint _perPage) public view returns (CdpHolder[] memory) { CdpHolder[] memory holders = new CdpHolder[](_perPage); uint start = _page * _perPage; uint end = start + _perPage; uint count = 0; for (uint i=start; i<end; i++) { holders[count] = subscribers[i]; count++; } return holders; } ////////////// ADMIN METHODS /////////////////// /// @notice Admin function to change a min. limit for an asset function changeMinRatios(bytes32 _ilk, uint _newRatio) public onlyOwner { minLimits[_ilk] = _newRatio; } /// @notice Admin function to unsubscribe a CDP function unsubscribeByAdmin(uint _cdpId) public onlyOwner { SubPosition storage subInfo = subscribersPos[_cdpId]; if (subInfo.subscribed) { _unsubscribe(_cdpId); } } }
pragma solidity ^0.6.0; /// @title Implements enum Method abstract contract StaticV2 { enum Method { Boost, Repay } struct CdpHolder { uint128 minRatio; uint128 maxRatio; uint128 optimalRatioBoost; uint128 optimalRatioRepay; address owner; uint cdpId; bool boostEnabled; bool nextPriceEnabled; } struct SubPosition { uint arrPos; bool subscribed; } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../interfaces/Manager.sol"; import "../../interfaces/Vat.sol"; import "../../interfaces/Spotter.sol"; import "../../DS/DSMath.sol"; import "../../auth/AdminAuth.sol"; import "../../loggers/DefisaverLogger.sol"; import "../../utils/GasBurner.sol"; import "../../utils/BotRegistry.sol"; import "../../exchangeV3/DFSExchangeData.sol"; import "./ISubscriptionsV2.sol"; import "./StaticV2.sol"; import "./MCDMonitorProxyV2.sol"; /// @title Implements logic that allows bots to call Boost and Repay contract MCDMonitorV2 is DSMath, AdminAuth, GasBurner, StaticV2 { uint public REPAY_GAS_TOKEN = 25; uint public BOOST_GAS_TOKEN = 25; uint public MAX_GAS_PRICE = 800000000000; // 800 gwei uint public REPAY_GAS_COST = 1000000; uint public BOOST_GAS_COST = 1000000; bytes4 public REPAY_SELECTOR = 0xf360ce20; bytes4 public BOOST_SELECTOR = 0x8ec2ae25; MCDMonitorProxyV2 public monitorProxyContract; ISubscriptionsV2 public subscriptionsContract; address public mcdSaverTakerAddress; address public constant BOT_REGISTRY_ADDRESS = 0x637726f8b08a7ABE3aE3aCaB01A80E2d8ddeF77B; address public constant PROXY_PERMISSION_ADDR = 0x5a4f877CA808Cca3cB7c2A194F80Ab8588FAE26B; Manager public manager = Manager(0x5ef30b9986345249bc32d8928B7ee64DE9435E39); Vat public vat = Vat(0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B); Spotter public spotter = Spotter(0x65C79fcB50Ca1594B025960e539eD7A9a6D434A3); DefisaverLogger public constant logger = DefisaverLogger(0x5c55B921f590a89C1Ebe84dF170E655a82b62126); modifier onlyApproved() { require(BotRegistry(BOT_REGISTRY_ADDRESS).botList(msg.sender), "Not auth bot"); _; } constructor(address _monitorProxy, address _subscriptions, address _mcdSaverTakerAddress) public { monitorProxyContract = MCDMonitorProxyV2(_monitorProxy); subscriptionsContract = ISubscriptionsV2(_subscriptions); mcdSaverTakerAddress = _mcdSaverTakerAddress; } /// @notice Bots call this method to repay for user when conditions are met /// @dev If the contract ownes gas token it will try and use it for gas price reduction function repayFor( DFSExchangeData.ExchangeData memory _exchangeData, uint _cdpId, uint _nextPrice, address _joinAddr ) public payable onlyApproved burnGas(REPAY_GAS_TOKEN) { (bool isAllowed, uint ratioBefore) = canCall(Method.Repay, _cdpId, _nextPrice); require(isAllowed); uint gasCost = calcGasCost(REPAY_GAS_COST); address owner = subscriptionsContract.getOwner(_cdpId); monitorProxyContract.callExecute{value: msg.value}( owner, mcdSaverTakerAddress, abi.encodeWithSelector(REPAY_SELECTOR, _exchangeData, _cdpId, gasCost, _joinAddr, 0)); (bool isGoodRatio, uint ratioAfter) = ratioGoodAfter(Method.Repay, _cdpId, _nextPrice); require(isGoodRatio); returnEth(); logger.Log(address(this), owner, "AutomaticMCDRepay", abi.encode(ratioBefore, ratioAfter)); } /// @notice Bots call this method to boost for user when conditions are met /// @dev If the contract ownes gas token it will try and use it for gas price reduction function boostFor( DFSExchangeData.ExchangeData memory _exchangeData, uint _cdpId, uint _nextPrice, address _joinAddr ) public payable onlyApproved burnGas(BOOST_GAS_TOKEN) { (bool isAllowed, uint ratioBefore) = canCall(Method.Boost, _cdpId, _nextPrice); require(isAllowed); uint gasCost = calcGasCost(BOOST_GAS_COST); address owner = subscriptionsContract.getOwner(_cdpId); monitorProxyContract.callExecute{value: msg.value}( owner, mcdSaverTakerAddress, abi.encodeWithSelector(BOOST_SELECTOR, _exchangeData, _cdpId, gasCost, _joinAddr, 0)); (bool isGoodRatio, uint ratioAfter) = ratioGoodAfter(Method.Boost, _cdpId, _nextPrice); require(isGoodRatio); returnEth(); logger.Log(address(this), owner, "AutomaticMCDBoost", abi.encode(ratioBefore, ratioAfter)); } /******************* INTERNAL METHODS ********************************/ function returnEth() internal { // return if some eth left if (address(this).balance > 0) { msg.sender.transfer(address(this).balance); } } /******************* STATIC METHODS ********************************/ /// @notice Returns an address that owns the CDP /// @param _cdpId Id of the CDP function getOwner(uint _cdpId) public view returns(address) { return manager.owns(_cdpId); } /// @notice Gets CDP info (collateral, debt) /// @param _cdpId Id of the CDP /// @param _ilk Ilk of the CDP function getCdpInfo(uint _cdpId, bytes32 _ilk) public view returns (uint, uint) { address urn = manager.urns(_cdpId); (uint collateral, uint debt) = vat.urns(_ilk, urn); (,uint rate,,,) = vat.ilks(_ilk); return (collateral, rmul(debt, rate)); } /// @notice Gets a price of the asset /// @param _ilk Ilk of the CDP function getPrice(bytes32 _ilk) public view returns (uint) { (, uint mat) = spotter.ilks(_ilk); (,,uint spot,,) = vat.ilks(_ilk); return rmul(rmul(spot, spotter.par()), mat); } /// @notice Gets CDP ratio /// @param _cdpId Id of the CDP /// @param _nextPrice Next price for user function getRatio(uint _cdpId, uint _nextPrice) public view returns (uint) { bytes32 ilk = manager.ilks(_cdpId); uint price = (_nextPrice == 0) ? getPrice(ilk) : _nextPrice; (uint collateral, uint debt) = getCdpInfo(_cdpId, ilk); if (debt == 0) return 0; return rdiv(wmul(collateral, price), debt) / (10 ** 18); } /// @notice Checks if Boost/Repay could be triggered for the CDP /// @dev Called by MCDMonitor to enforce the min/max check function canCall(Method _method, uint _cdpId, uint _nextPrice) public view returns(bool, uint) { bool subscribed; CdpHolder memory holder; (subscribed, holder) = subscriptionsContract.getCdpHolder(_cdpId); // check if cdp is subscribed if (!subscribed) return (false, 0); // check if using next price is allowed if (_nextPrice > 0 && !holder.nextPriceEnabled) return (false, 0); // check if boost and boost allowed if (_method == Method.Boost && !holder.boostEnabled) return (false, 0); // check if owner is still owner if (getOwner(_cdpId) != holder.owner) return (false, 0); uint currRatio = getRatio(_cdpId, _nextPrice); if (_method == Method.Repay) { return (currRatio < holder.minRatio, currRatio); } else if (_method == Method.Boost) { return (currRatio > holder.maxRatio, currRatio); } } /// @dev After the Boost/Repay check if the ratio doesn't trigger another call function ratioGoodAfter(Method _method, uint _cdpId, uint _nextPrice) public view returns(bool, uint) { CdpHolder memory holder; (, holder) = subscriptionsContract.getCdpHolder(_cdpId); uint currRatio = getRatio(_cdpId, _nextPrice); if (_method == Method.Repay) { return (currRatio < holder.maxRatio, currRatio); } else if (_method == Method.Boost) { return (currRatio > holder.minRatio, currRatio); } } /// @notice Calculates gas cost (in Eth) of tx /// @dev Gas price is limited to MAX_GAS_PRICE to prevent attack of draining user CDP /// @param _gasAmount Amount of gas used for the tx function calcGasCost(uint _gasAmount) public view returns (uint) { uint gasPrice = tx.gasprice <= MAX_GAS_PRICE ? tx.gasprice : MAX_GAS_PRICE; return mul(gasPrice, _gasAmount); } /******************* OWNER ONLY OPERATIONS ********************************/ /// @notice Allows owner to change gas cost for boost operation, but only up to 3 millions /// @param _gasCost New gas cost for boost method function changeBoostGasCost(uint _gasCost) public onlyOwner { require(_gasCost < 3000000); BOOST_GAS_COST = _gasCost; } /// @notice Allows owner to change gas cost for repay operation, but only up to 3 millions /// @param _gasCost New gas cost for repay method function changeRepayGasCost(uint _gasCost) public onlyOwner { require(_gasCost < 3000000); REPAY_GAS_COST = _gasCost; } /// @notice Allows owner to change max gas price /// @param _maxGasPrice New max gas price function changeMaxGasPrice(uint _maxGasPrice) public onlyOwner { require(_maxGasPrice < 1000000000000); MAX_GAS_PRICE = _maxGasPrice; } /// @notice Allows owner to change the amount of gas token burned per function call /// @param _gasAmount Amount of gas token /// @param _isRepay Flag to know for which function we are setting the gas token amount function changeGasTokenAmount(uint _gasAmount, bool _isRepay) public onlyOwner { if (_isRepay) { REPAY_GAS_TOKEN = _gasAmount; } else { BOOST_GAS_TOKEN = _gasAmount; } } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "./StaticV2.sol"; abstract contract ISubscriptionsV2 is StaticV2 { function getOwner(uint _cdpId) external view virtual returns(address); function getSubscribedInfo(uint _cdpId) public view virtual returns(bool, uint128, uint128, uint128, uint128, address, uint coll, uint debt); function getCdpHolder(uint _cdpId) public view virtual returns (bool subscribed, CdpHolder memory); }
pragma solidity ^0.6.0; import "../../interfaces/DSProxyInterface.sol"; import "../../interfaces/ERC20.sol"; import "../../auth/AdminAuth.sol"; /// @title Implements logic for calling MCDSaverProxy always from same contract contract MCDMonitorProxyV2 is AdminAuth { uint public CHANGE_PERIOD; uint public MIN_CHANGE_PERIOD = 6 * 1 hours; address public monitor; address public newMonitor; address public lastMonitor; uint public changeRequestedTimestamp; event MonitorChangeInitiated(address oldMonitor, address newMonitor); event MonitorChangeCanceled(); event MonitorChangeFinished(address monitor); event MonitorChangeReverted(address monitor); modifier onlyMonitor() { require (msg.sender == monitor); _; } constructor(uint _changePeriod) public { CHANGE_PERIOD = _changePeriod * 1 hours; } /// @notice Only monitor contract is able to call execute on users proxy /// @param _owner Address of cdp owner (users DSProxy address) /// @param _saverProxy Address of MCDSaverProxy /// @param _data Data to send to MCDSaverProxy function callExecute(address _owner, address _saverProxy, bytes memory _data) public payable onlyMonitor { // execute reverts if calling specific method fails DSProxyInterface(_owner).execute{value: msg.value}(_saverProxy, _data); // return if anything left if (address(this).balance > 0) { msg.sender.transfer(address(this).balance); } } /// @notice Allowed users are able to set Monitor contract without any waiting period first time /// @param _monitor Address of Monitor contract function setMonitor(address _monitor) public onlyOwner { require(monitor == address(0)); monitor = _monitor; } /// @notice Allowed users are able to start procedure for changing monitor /// @dev after CHANGE_PERIOD needs to call confirmNewMonitor to actually make a change /// @param _newMonitor address of new monitor function changeMonitor(address _newMonitor) public onlyOwner { require(changeRequestedTimestamp == 0); changeRequestedTimestamp = now; lastMonitor = monitor; newMonitor = _newMonitor; emit MonitorChangeInitiated(lastMonitor, newMonitor); } /// @notice At any point allowed users are able to cancel monitor change function cancelMonitorChange() public onlyOwner { require(changeRequestedTimestamp > 0); changeRequestedTimestamp = 0; newMonitor = address(0); emit MonitorChangeCanceled(); } /// @notice Anyone is able to confirm new monitor after CHANGE_PERIOD if process is started function confirmNewMonitor() public onlyOwner { require((changeRequestedTimestamp + CHANGE_PERIOD) < now); require(changeRequestedTimestamp != 0); require(newMonitor != address(0)); monitor = newMonitor; newMonitor = address(0); changeRequestedTimestamp = 0; emit MonitorChangeFinished(monitor); } /// @notice Its possible to revert monitor to last used monitor function revertMonitor() public onlyOwner { require(lastMonitor != address(0)); monitor = lastMonitor; emit MonitorChangeReverted(monitor); } function setChangePeriod(uint _periodInHours) public onlyOwner { require(_periodInHours * 1 hours > MIN_CHANGE_PERIOD); CHANGE_PERIOD = _periodInHours * 1 hours; } }
pragma solidity ^0.6.0; import "../../interfaces/CEtherInterface.sol"; import "../../interfaces/CompoundOracleInterface.sol"; import "../../interfaces/CTokenInterface.sol"; import "../../interfaces/ComptrollerInterface.sol"; import "../../interfaces/IFeeRecipient.sol"; import "../../utils/Discount.sol"; import "../../DS/DSMath.sol"; import "../../DS/DSProxy.sol"; import "./Exponential.sol"; import "../../utils/BotRegistry.sol"; import "../../utils/SafeERC20.sol"; /// @title Utlity functions for Compound contracts contract CompoundSaverHelper is DSMath, Exponential { using SafeERC20 for ERC20; IFeeRecipient public constant feeRecipient = IFeeRecipient(0x39C4a92Dc506300c3Ea4c67ca4CA611102ee6F2A); address public constant DISCOUNT_ADDR = 0x1b14E8D511c9A4395425314f849bD737BAF8208F; uint public constant MANUAL_SERVICE_FEE = 400; // 0.25% Fee uint public constant AUTOMATIC_SERVICE_FEE = 333; // 0.3% Fee address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant CETH_ADDRESS = 0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5; address public constant COMPTROLLER = 0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B; address public constant COMPOUND_LOGGER = 0x3DD0CDf5fFA28C6847B4B276e2fD256046a44bb7; address public constant BOT_REGISTRY_ADDRESS = 0x637726f8b08a7ABE3aE3aCaB01A80E2d8ddeF77B; /// @notice Helper method to payback the Compound debt /// @dev If amount is bigger it will repay the whole debt and send the extra to the _user /// @param _amount Amount of tokens we want to repay /// @param _cBorrowToken Ctoken address we are repaying /// @param _borrowToken Token address we are repaying /// @param _user Owner of the compound position we are paying back function paybackDebt(uint _amount, address _cBorrowToken, address _borrowToken, address payable _user) internal { uint wholeDebt = CTokenInterface(_cBorrowToken).borrowBalanceCurrent(address(this)); if (_amount > wholeDebt) { if (_borrowToken == ETH_ADDRESS) { _user.transfer((_amount - wholeDebt)); } else { ERC20(_borrowToken).safeTransfer(_user, (_amount - wholeDebt)); } _amount = wholeDebt; } approveCToken(_borrowToken, _cBorrowToken); if (_borrowToken == ETH_ADDRESS) { CEtherInterface(_cBorrowToken).repayBorrow{value: _amount}(); } else { require(CTokenInterface(_cBorrowToken).repayBorrow(_amount) == 0); } } /// @notice Calculates the fee amount /// @param _amount Amount that is converted /// @param _user Actuall user addr not DSProxy /// @param _gasCost Ether amount of gas we are spending for tx /// @param _cTokenAddr CToken addr. of token we are getting for the fee /// @return feeAmount The amount we took for the fee function getFee(uint _amount, address _user, uint _gasCost, address _cTokenAddr) internal returns (uint feeAmount) { uint fee = MANUAL_SERVICE_FEE; if (BotRegistry(BOT_REGISTRY_ADDRESS).botList(tx.origin)) { fee = AUTOMATIC_SERVICE_FEE; } address tokenAddr = getUnderlyingAddr(_cTokenAddr); if (Discount(DISCOUNT_ADDR).isCustomFeeSet(_user)) { fee = Discount(DISCOUNT_ADDR).getCustomServiceFee(_user); } feeAmount = (fee == 0) ? 0 : (_amount / fee); if (_gasCost != 0) { address oracle = ComptrollerInterface(COMPTROLLER).oracle(); uint usdTokenPrice = CompoundOracleInterface(oracle).getUnderlyingPrice(_cTokenAddr); uint ethPrice = CompoundOracleInterface(oracle).getUnderlyingPrice(CETH_ADDRESS); uint tokenPriceInEth = wdiv(usdTokenPrice, ethPrice); _gasCost = wdiv(_gasCost, tokenPriceInEth); feeAmount = add(feeAmount, _gasCost); } // fee can't go over 20% of the whole amount if (feeAmount > (_amount / 5)) { feeAmount = _amount / 5; } address walletAddr = feeRecipient.getFeeAddr(); if (tokenAddr == ETH_ADDRESS) { payable(walletAddr).transfer(feeAmount); } else { ERC20(tokenAddr).safeTransfer(walletAddr, feeAmount); } } /// @notice Calculates the gas cost of transaction and send it to wallet /// @param _amount Amount that is converted /// @param _gasCost Ether amount of gas we are spending for tx /// @param _cTokenAddr CToken addr. of token we are getting for the fee /// @return feeAmount The amount we took for the fee function getGasCost(uint _amount, uint _gasCost, address _cTokenAddr) internal returns (uint feeAmount) { address tokenAddr = getUnderlyingAddr(_cTokenAddr); if (_gasCost != 0) { address oracle = ComptrollerInterface(COMPTROLLER).oracle(); uint usdTokenPrice = CompoundOracleInterface(oracle).getUnderlyingPrice(_cTokenAddr); uint ethPrice = CompoundOracleInterface(oracle).getUnderlyingPrice(CETH_ADDRESS); uint tokenPriceInEth = wdiv(usdTokenPrice, ethPrice); feeAmount = wdiv(_gasCost, tokenPriceInEth); } // fee can't go over 20% of the whole amount if (feeAmount > (_amount / 5)) { feeAmount = _amount / 5; } address walletAddr = feeRecipient.getFeeAddr(); if (tokenAddr == ETH_ADDRESS) { payable(walletAddr).transfer(feeAmount); } else { ERC20(tokenAddr).safeTransfer(walletAddr, feeAmount); } } /// @notice Enters the market for the collatera and borrow tokens /// @param _cTokenAddrColl Collateral address we are entering the market in /// @param _cTokenAddrBorrow Borrow address we are entering the market in function enterMarket(address _cTokenAddrColl, address _cTokenAddrBorrow) internal { address[] memory markets = new address[](2); markets[0] = _cTokenAddrColl; markets[1] = _cTokenAddrBorrow; ComptrollerInterface(COMPTROLLER).enterMarkets(markets); } /// @notice Approves CToken contract to pull underlying tokens from the DSProxy /// @param _tokenAddr Token we are trying to approve /// @param _cTokenAddr Address which will gain the approval function approveCToken(address _tokenAddr, address _cTokenAddr) internal { if (_tokenAddr != ETH_ADDRESS) { ERC20(_tokenAddr).safeApprove(_cTokenAddr, uint(-1)); } } /// @notice Returns the underlying address of the cToken asset /// @param _cTokenAddress cToken address /// @return Token address of the cToken specified function getUnderlyingAddr(address _cTokenAddress) internal returns (address) { if (_cTokenAddress == CETH_ADDRESS) { return ETH_ADDRESS; } else { return CTokenInterface(_cTokenAddress).underlying(); } } /// @notice Returns the owner of the DSProxy that called the contract function getUserAddress() internal view returns (address) { DSProxy proxy = DSProxy(uint160(address(this))); return proxy.owner(); } /// @notice Returns the maximum amount of collateral available to withdraw /// @dev Due to rounding errors the result is - 1% wei from the exact amount /// @param _cCollAddress Collateral we are getting the max value of /// @param _account Users account /// @return Returns the max. collateral amount in that token function getMaxCollateral(address _cCollAddress, address _account) public returns (uint) { (, uint liquidityInUsd, ) = ComptrollerInterface(COMPTROLLER).getAccountLiquidity(_account); uint usersBalance = CTokenInterface(_cCollAddress).balanceOfUnderlying(_account); address oracle = ComptrollerInterface(COMPTROLLER).oracle(); if (liquidityInUsd == 0) return usersBalance; CTokenInterface(_cCollAddress).accrueInterest(); (, uint collFactorMantissa) = ComptrollerInterface(COMPTROLLER).markets(_cCollAddress); Exp memory collateralFactor = Exp({mantissa: collFactorMantissa}); (, uint tokensToUsd) = divScalarByExpTruncate(liquidityInUsd, collateralFactor); uint usdPrice = CompoundOracleInterface(oracle).getUnderlyingPrice(_cCollAddress); uint liqInToken = wdiv(tokensToUsd, usdPrice); if (liqInToken > usersBalance) return usersBalance; return sub(liqInToken, (liqInToken / 100)); // cut off 1% due to rounding issues } /// @notice Returns the maximum amount of borrow amount available /// @dev Due to rounding errors the result is - 1% wei from the exact amount /// @param _cBorrowAddress Borrow token we are getting the max value of /// @param _account Users account /// @return Returns the max. borrow amount in that token function getMaxBorrow(address _cBorrowAddress, address _account) public returns (uint) { (, uint liquidityInUsd, ) = ComptrollerInterface(COMPTROLLER).getAccountLiquidity(_account); address oracle = ComptrollerInterface(COMPTROLLER).oracle(); CTokenInterface(_cBorrowAddress).accrueInterest(); uint usdPrice = CompoundOracleInterface(oracle).getUnderlyingPrice(_cBorrowAddress); uint liquidityInToken = wdiv(liquidityInUsd, usdPrice); return sub(liquidityInToken, (liquidityInToken / 100)); // cut off 1% due to rounding issues } function isAutomation() internal view returns(bool) { return BotRegistry(BOT_REGISTRY_ADDRESS).botList(tx.origin); } }
pragma solidity ^0.6.0; import "../../compound/helpers/CompoundSaverHelper.sol"; contract CompShifter is CompoundSaverHelper { using SafeERC20 for ERC20; address public constant COMPTROLLER_ADDR = 0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B; function getLoanAmount(uint _cdpId, address _joinAddr) public returns(uint loanAmount) { return getWholeDebt(_cdpId, _joinAddr); } function getWholeDebt(uint _cdpId, address _joinAddr) public returns(uint loanAmount) { return CTokenInterface(_joinAddr).borrowBalanceCurrent(msg.sender); } function close( address _cCollAddr, address _cBorrowAddr, uint _collAmount, uint _debtAmount ) public { address collAddr = getUnderlyingAddr(_cCollAddr); // payback debt paybackDebt(_debtAmount, _cBorrowAddr, getUnderlyingAddr(_cBorrowAddr), tx.origin); require(CTokenInterface(_cCollAddr).redeemUnderlying(_collAmount) == 0); // Send back money to repay FL if (collAddr == ETH_ADDRESS) { msg.sender.transfer(address(this).balance); } else { ERC20(collAddr).safeTransfer(msg.sender, ERC20(collAddr).balanceOf(address(this))); } } function changeDebt( address _cBorrowAddrOld, address _cBorrowAddrNew, uint _debtAmountOld, uint _debtAmountNew ) public { address borrowAddrNew = getUnderlyingAddr(_cBorrowAddrNew); // payback debt in one token paybackDebt(_debtAmountOld, _cBorrowAddrOld, getUnderlyingAddr(_cBorrowAddrOld), tx.origin); // draw debt in another one borrowCompound(_cBorrowAddrNew, _debtAmountNew); // Send back money to repay FL if (borrowAddrNew == ETH_ADDRESS) { msg.sender.transfer(address(this).balance); } else { ERC20(borrowAddrNew).safeTransfer(msg.sender, ERC20(borrowAddrNew).balanceOf(address(this))); } } function open( address _cCollAddr, address _cBorrowAddr, uint _debtAmount ) public { address collAddr = getUnderlyingAddr(_cCollAddr); address borrowAddr = getUnderlyingAddr(_cBorrowAddr); uint collAmount = 0; if (collAddr == ETH_ADDRESS) { collAmount = address(this).balance; } else { collAmount = ERC20(collAddr).balanceOf(address(this)); } depositCompound(collAddr, _cCollAddr, collAmount); // draw debt borrowCompound(_cBorrowAddr, _debtAmount); // Send back money to repay FL if (borrowAddr == ETH_ADDRESS) { msg.sender.transfer(address(this).balance); } else { ERC20(borrowAddr).safeTransfer(msg.sender, ERC20(borrowAddr).balanceOf(address(this))); } } function repayAll(address _cTokenAddr) public { address tokenAddr = getUnderlyingAddr(_cTokenAddr); uint amount = ERC20(tokenAddr).balanceOf(address(this)); if (amount != 0) { paybackDebt(amount, _cTokenAddr, tokenAddr, tx.origin); } } function depositCompound(address _tokenAddr, address _cTokenAddr, uint _amount) internal { approveCToken(_tokenAddr, _cTokenAddr); enterMarket(_cTokenAddr); if (_tokenAddr != ETH_ADDRESS) { require(CTokenInterface(_cTokenAddr).mint(_amount) == 0, "mint error"); } else { CEtherInterface(_cTokenAddr).mint{value: _amount}(); } } function borrowCompound(address _cTokenAddr, uint _amount) internal { enterMarket(_cTokenAddr); require(CTokenInterface(_cTokenAddr).borrow(_amount) == 0); } function enterMarket(address _cTokenAddr) public { address[] memory markets = new address[](1); markets[0] = _cTokenAddr; ComptrollerInterface(COMPTROLLER_ADDR).enterMarkets(markets); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../utils/SafeERC20.sol"; import "../../exchangeV3/DFSExchangeCore.sol"; import "../../interfaces/CTokenInterface.sol"; import "../../utils/Discount.sol"; import "../helpers/CompoundSaverHelper.sol"; import "../../loggers/DefisaverLogger.sol"; /// @title Implements the actual logic of Repay/Boost with FL contract CompoundSaverFlashProxy is DFSExchangeCore, CompoundSaverHelper { address public constant DEFISAVER_LOGGER = 0x5c55B921f590a89C1Ebe84dF170E655a82b62126; using SafeERC20 for ERC20; /// @notice Repays the position and sends tokens back for FL /// @param _exData Exchange data /// @param _cAddresses cTokens addreses and exchange [cCollAddress, cBorrowAddress] /// @param _gasCost Gas cost for transaction /// @param _flashLoanData Data about FL [amount, fee] function flashRepay( ExchangeData memory _exData, address[2] memory _cAddresses, // cCollAddress, cBorrowAddress uint256 _gasCost, uint[2] memory _flashLoanData // amount, fee ) public payable { enterMarket(_cAddresses[0], _cAddresses[1]); address payable user = payable(getUserAddress()); uint flashBorrowed = _flashLoanData[0] + _flashLoanData[1]; uint maxColl = getMaxCollateral(_cAddresses[0], address(this)); // draw max coll require(CTokenInterface(_cAddresses[0]).redeemUnderlying(maxColl) == 0); address collToken = getUnderlyingAddr(_cAddresses[0]); address borrowToken = getUnderlyingAddr(_cAddresses[1]); uint swapAmount = 0; if (collToken != borrowToken) { // swap max coll + loanAmount _exData.srcAmount = maxColl + _flashLoanData[0]; _exData.dfsFeeDivider = isAutomation() ? AUTOMATIC_SERVICE_FEE : MANUAL_SERVICE_FEE; _exData.user = user; (,swapAmount) = _sell(_exData); // get fee swapAmount -= getGasCost(swapAmount, _gasCost, _cAddresses[1]); } else { swapAmount = (maxColl + _flashLoanData[0]); swapAmount -= getGasCost(swapAmount, _gasCost, _cAddresses[1]); } // payback debt paybackDebt(swapAmount, _cAddresses[1], borrowToken, user); // draw collateral for loanAmount + loanFee require(CTokenInterface(_cAddresses[0]).redeemUnderlying(flashBorrowed) == 0); // repay flash loan returnFlashLoan(collToken, flashBorrowed); DefisaverLogger(DEFISAVER_LOGGER).Log(address(this), msg.sender, "CompoundRepay", abi.encode(_exData.srcAmount, swapAmount, collToken, borrowToken)); } /// @notice Boosts the position and sends tokens back for FL /// @param _exData Exchange data /// @param _cAddresses cTokens addreses and exchange [cCollAddress, cBorrowAddress] /// @param _gasCost Gas cost for specific transaction /// @param _flashLoanData Data about FL [amount, fee] function flashBoost( ExchangeData memory _exData, address[2] memory _cAddresses, // cCollAddress, cBorrowAddress uint256 _gasCost, uint[2] memory _flashLoanData // amount, fee ) public payable { enterMarket(_cAddresses[0], _cAddresses[1]); address payable user = payable(getUserAddress()); uint flashBorrowed = _flashLoanData[0] + _flashLoanData[1]; // borrow max amount uint borrowAmount = getMaxBorrow(_cAddresses[1], address(this)); require(CTokenInterface(_cAddresses[1]).borrow(borrowAmount) == 0); address collToken = getUnderlyingAddr(_cAddresses[0]); address borrowToken = getUnderlyingAddr(_cAddresses[1]); uint swapAmount = 0; if (collToken != borrowToken) { // get dfs fee _exData.srcAmount = (borrowAmount + _flashLoanData[0]); _exData.dfsFeeDivider = isAutomation() ? AUTOMATIC_SERVICE_FEE : MANUAL_SERVICE_FEE; _exData.user = user; (, swapAmount) = _sell(_exData); swapAmount -= getGasCost(swapAmount, _gasCost, _cAddresses[1]); } else { swapAmount = (borrowAmount + _flashLoanData[0]); swapAmount -= getGasCost(swapAmount, _gasCost, _cAddresses[1]); } // deposit swaped collateral depositCollateral(collToken, _cAddresses[0], swapAmount); // borrow token to repay flash loan require(CTokenInterface(_cAddresses[1]).borrow(flashBorrowed) == 0); // repay flash loan returnFlashLoan(borrowToken, flashBorrowed); DefisaverLogger(DEFISAVER_LOGGER).Log(address(this), msg.sender, "CompoundBoost", abi.encode(_exData.srcAmount, swapAmount, collToken, borrowToken)); } /// @notice Helper method to deposit tokens in Compound /// @param _collToken Token address of the collateral /// @param _cCollToken CToken address of the collateral /// @param _depositAmount Amount to deposit function depositCollateral(address _collToken, address _cCollToken, uint _depositAmount) internal { approveCToken(_collToken, _cCollToken); if (_collToken != ETH_ADDRESS) { require(CTokenInterface(_cCollToken).mint(_depositAmount) == 0); } else { CEtherInterface(_cCollToken).mint{value: _depositAmount}(); // reverts on fail } } /// @notice Returns the tokens/ether to the msg.sender which is the FL contract /// @param _tokenAddr Address of token which we return /// @param _amount Amount to return function returnFlashLoan(address _tokenAddr, uint _amount) internal { if (_tokenAddr != ETH_ADDRESS) { ERC20(_tokenAddr).safeTransfer(msg.sender, _amount); } msg.sender.transfer(address(this).balance); } }
pragma solidity ^0.6.0; import "../../utils/GasBurner.sol"; import "../../auth/ProxyPermission.sol"; import "../../loggers/DefisaverLogger.sol"; import "../../interfaces/ILendingPool.sol"; import "../../interfaces/CTokenInterface.sol"; import "../../interfaces/ProxyRegistryInterface.sol"; import "../helpers/CreamSaverHelper.sol"; /// @title Imports cream position from the account to DSProxy contract CreamImportTaker is CreamSaverHelper, ProxyPermission, GasBurner { ILendingPool public constant lendingPool = ILendingPool(0x398eC7346DcD622eDc5ae82352F02bE94C62d119); address payable public constant CREAM_IMPORT_FLASH_LOAN = 0x24F4aC0Fe758c45cf8425D8Fbdd608cca9A7dBf8; address public constant PROXY_REGISTRY_ADDRESS = 0x4678f0a6958e4D2Bc4F1BAF7Bc52E8F3564f3fE4; DefisaverLogger public constant logger = DefisaverLogger(0x5c55B921f590a89C1Ebe84dF170E655a82b62126); /// @notice Starts the process to move users position 1 collateral and 1 borrow /// @dev User must approve cream_IMPORT_FLASH_LOAN to pull _cCollateralToken /// @param _cCollateralToken Collateral we are moving to DSProxy /// @param _cBorrowToken Borrow token we are moving to DSProxy function importLoan(address _cCollateralToken, address _cBorrowToken) external burnGas(20) { address proxy = getProxy(); uint loanAmount = CTokenInterface(_cBorrowToken).borrowBalanceCurrent(msg.sender); bytes memory paramsData = abi.encode(_cCollateralToken, _cBorrowToken, msg.sender, proxy); givePermission(CREAM_IMPORT_FLASH_LOAN); lendingPool.flashLoan(CREAM_IMPORT_FLASH_LOAN, getUnderlyingAddr(_cBorrowToken), loanAmount, paramsData); removePermission(CREAM_IMPORT_FLASH_LOAN); logger.Log(address(this), msg.sender, "CreamImport", abi.encode(loanAmount, 0, _cCollateralToken)); } /// @notice Gets proxy address, if user doesn't has DSProxy build it /// @return proxy DsProxy address function getProxy() internal returns (address proxy) { proxy = ProxyRegistryInterface(PROXY_REGISTRY_ADDRESS).proxies(msg.sender); if (proxy == address(0)) { proxy = ProxyRegistryInterface(PROXY_REGISTRY_ADDRESS).build(msg.sender); } } }
pragma solidity ^0.6.0; import "../../utils/FlashLoanReceiverBase.sol"; import "../../interfaces/ProxyRegistryInterface.sol"; import "../../interfaces/CTokenInterface.sol"; import "../../utils/SafeERC20.sol"; /// @title Receives FL from Aave and imports the position to DSProxy contract CreamImportFlashLoan is FlashLoanReceiverBase { using SafeERC20 for ERC20; ILendingPoolAddressesProvider public LENDING_POOL_ADDRESS_PROVIDER = ILendingPoolAddressesProvider(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8); address public constant CREAM_BORROW_PROXY = 0x87F198Ef6116CdBC5f36B581d212ad950b7e2Ddd; address public owner; constructor() FlashLoanReceiverBase(LENDING_POOL_ADDRESS_PROVIDER) public { owner = msg.sender; } /// @notice Called by Aave when sending back the FL amount /// @param _reserve The address of the borrowed token /// @param _amount Amount of FL tokens received /// @param _fee FL Aave fee /// @param _params The params that are sent from the original FL caller contract function executeOperation( address _reserve, uint256 _amount, uint256 _fee, bytes calldata _params) external override { ( address cCollateralToken, address cBorrowToken, address user, address proxy ) = abi.decode(_params, (address,address,address,address)); // approve FL tokens so we can repay them ERC20(_reserve).safeApprove(cBorrowToken, uint(-1)); // repay cream debt require(CTokenInterface(cBorrowToken).repayBorrowBehalf(user, uint(-1)) == 0, "Repay borrow behalf fail"); // transfer cTokens to proxy uint cTokenBalance = CTokenInterface(cCollateralToken).balanceOf(user); require(CTokenInterface(cCollateralToken).transferFrom(user, proxy, cTokenBalance)); // borrow bytes memory proxyData = getProxyData(cCollateralToken, cBorrowToken, _reserve, (_amount + _fee)); DSProxyInterface(proxy).execute(CREAM_BORROW_PROXY, proxyData); // Repay the loan with the money DSProxy sent back transferFundsBackToPoolInternal(_reserve, _amount.add(_fee)); } /// @notice Formats function data call so we can call it through DSProxy /// @param _cCollToken CToken address of collateral /// @param _cBorrowToken CToken address we will borrow /// @param _borrowToken Token address we will borrow /// @param _amount Amount that will be borrowed /// @return proxyData Formated function call data function getProxyData(address _cCollToken, address _cBorrowToken, address _borrowToken, uint _amount) internal pure returns (bytes memory proxyData) { proxyData = abi.encodeWithSignature( "borrow(address,address,address,uint256)", _cCollToken, _cBorrowToken, _borrowToken, _amount); } function withdrawStuckFunds(address _tokenAddr, uint _amount) public { require(owner == msg.sender, "Must be owner"); if (_tokenAddr == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) { msg.sender.transfer(_amount); } else { ERC20(_tokenAddr).safeTransfer(owner, _amount); } } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../mcd/saver/MCDSaverProxy.sol"; import "../../utils/FlashLoanReceiverBase.sol"; import "../../exchangeV3/DFSExchangeCore.sol"; /// @title Receiver of Dydx flash loan and performs the fl repay/boost logic /// @notice Must have a dust amount of WETH on the contract for 2 wei dydx fee contract MCDSaverFlashLoan is MCDSaverProxy, AdminAuth, FlashLoanReceiverBase { ILendingPoolAddressesProvider public LENDING_POOL_ADDRESS_PROVIDER = ILendingPoolAddressesProvider(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8); constructor() FlashLoanReceiverBase(LENDING_POOL_ADDRESS_PROVIDER) public {} struct SaverData { uint cdpId; uint gasCost; uint loanAmount; uint fee; address joinAddr; ManagerType managerType; } function executeOperation( address _reserve, uint256 _amount, uint256 _fee, bytes calldata _params) external override { //check the contract has the specified balance require(_amount <= getBalanceInternal(address(this), _reserve), "Invalid balance for the contract"); ( bytes memory exDataBytes, uint cdpId, uint gasCost, address joinAddr, bool isRepay, uint8 managerType ) = abi.decode(_params, (bytes,uint256,uint256,address,bool,uint8)); ExchangeData memory exchangeData = unpackExchangeData(exDataBytes); SaverData memory saverData = SaverData({ cdpId: cdpId, gasCost: gasCost, loanAmount: _amount, fee: _fee, joinAddr: joinAddr, managerType: ManagerType(managerType) }); if (isRepay) { repayWithLoan(exchangeData, saverData); } else { boostWithLoan(exchangeData, saverData); } transferFundsBackToPoolInternal(_reserve, _amount.add(_fee)); // if there is some eth left (0x fee), return it to user if (address(this).balance > 0) { tx.origin.transfer(address(this).balance); } } function boostWithLoan( ExchangeData memory _exchangeData, SaverData memory _saverData ) internal { address managerAddr = getManagerAddr(_saverData.managerType); address user = getOwner(Manager(managerAddr), _saverData.cdpId); // Draw users Dai uint maxDebt = getMaxDebt(managerAddr, _saverData.cdpId, Manager(managerAddr).ilks(_saverData.cdpId)); uint daiDrawn = drawDai(managerAddr, _saverData.cdpId, Manager(managerAddr).ilks(_saverData.cdpId), maxDebt); // Swap _exchangeData.srcAmount = daiDrawn + _saverData.loanAmount - takeFee(_saverData.gasCost, daiDrawn + _saverData.loanAmount); _exchangeData.user = user; _exchangeData.dfsFeeDivider = isAutomation() ? AUTOMATIC_SERVICE_FEE : MANUAL_SERVICE_FEE; (, uint swapedAmount) = _sell(_exchangeData); // Return collateral addCollateral(managerAddr, _saverData.cdpId, _saverData.joinAddr, swapedAmount); // Draw Dai to repay the flash loan drawDai(managerAddr, _saverData.cdpId, Manager(managerAddr).ilks(_saverData.cdpId), (_saverData.loanAmount + _saverData.fee)); logger.Log(address(this), msg.sender, "MCDFlashBoost", abi.encode(_saverData.cdpId, user, _exchangeData.srcAmount, swapedAmount)); } function repayWithLoan( ExchangeData memory _exchangeData, SaverData memory _saverData ) internal { address managerAddr = getManagerAddr(_saverData.managerType); address user = getOwner(Manager(managerAddr), _saverData.cdpId); bytes32 ilk = Manager(managerAddr).ilks(_saverData.cdpId); // Draw collateral uint maxColl = getMaxCollateral(managerAddr, _saverData.cdpId, ilk, _saverData.joinAddr); uint collDrawn = drawCollateral(managerAddr, _saverData.cdpId, _saverData.joinAddr, maxColl); // Swap _exchangeData.srcAmount = (_saverData.loanAmount + collDrawn); _exchangeData.user = user; _exchangeData.dfsFeeDivider = isAutomation() ? AUTOMATIC_SERVICE_FEE : MANUAL_SERVICE_FEE; (, uint paybackAmount) = _sell(_exchangeData); paybackAmount -= takeFee(_saverData.gasCost, paybackAmount); paybackAmount = limitLoanAmount(managerAddr, _saverData.cdpId, ilk, paybackAmount, user); // Payback the debt paybackDebt(managerAddr, _saverData.cdpId, ilk, paybackAmount, user); // Draw collateral to repay the flash loan drawCollateral(managerAddr, _saverData.cdpId, _saverData.joinAddr, (_saverData.loanAmount + _saverData.fee)); logger.Log(address(this), msg.sender, "MCDFlashRepay", abi.encode(_saverData.cdpId, user, _exchangeData.srcAmount, paybackAmount)); } /// @notice Handles that the amount is not bigger than cdp debt and not dust function limitLoanAmount(address _managerAddr, uint _cdpId, bytes32 _ilk, uint _paybackAmount, address _owner) internal returns (uint256) { uint debt = getAllDebt(address(vat), Manager(_managerAddr).urns(_cdpId), Manager(_managerAddr).urns(_cdpId), _ilk); if (_paybackAmount > debt) { ERC20(DAI_ADDRESS).transfer(_owner, (_paybackAmount - debt)); return debt; } uint debtLeft = debt - _paybackAmount; (,,,, uint dust) = vat.ilks(_ilk); dust = dust / 10**27; // Less than dust value if (debtLeft < dust) { uint amountOverDust = (dust - debtLeft); ERC20(DAI_ADDRESS).transfer(_owner, amountOverDust); return (_paybackAmount - amountOverDust); } return _paybackAmount; } receive() external override(FlashLoanReceiverBase, DFSExchangeCore) payable {} }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../mcd/saver/MCDSaverProxy.sol"; import "../../utils/FlashLoanReceiverBase.sol"; import "../../auth/AdminAuth.sol"; import "../../exchangeV3/DFSExchangeCore.sol"; import "../../mcd/saver/MCDSaverProxyHelper.sol"; import "./MCDCloseTaker.sol"; contract MCDCloseFlashLoan is DFSExchangeCore, MCDSaverProxyHelper, FlashLoanReceiverBase, AdminAuth { ILendingPoolAddressesProvider public LENDING_POOL_ADDRESS_PROVIDER = ILendingPoolAddressesProvider(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8); uint public constant SERVICE_FEE = 400; // 0.25% Fee bytes32 internal constant ETH_ILK = 0x4554482d41000000000000000000000000000000000000000000000000000000; address public constant DAI_ADDRESS = 0x6B175474E89094C44Da98b954EedeAC495271d0F; address public constant DAI_JOIN_ADDRESS = 0x9759A6Ac90977b93B58547b4A71c78317f391A28; address public constant SPOTTER_ADDRESS = 0x65C79fcB50Ca1594B025960e539eD7A9a6D434A3; address public constant VAT_ADDRESS = 0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B; DaiJoin public constant daiJoin = DaiJoin(DAI_JOIN_ADDRESS); Spotter public constant spotter = Spotter(SPOTTER_ADDRESS); Vat public constant vat = Vat(VAT_ADDRESS); struct CloseData { uint cdpId; uint collAmount; uint daiAmount; uint minAccepted; address joinAddr; address proxy; uint flFee; bool toDai; address reserve; uint amount; } constructor() FlashLoanReceiverBase(LENDING_POOL_ADDRESS_PROVIDER) public {} function executeOperation( address _reserve, uint256 _amount, uint256 _fee, bytes calldata _params) external override { (address proxy, bytes memory packedData) = abi.decode(_params, (address,bytes)); (MCDCloseTaker.CloseData memory closeDataSent, ExchangeData memory exchangeData) = abi.decode(packedData, (MCDCloseTaker.CloseData,ExchangeData)); CloseData memory closeData = CloseData({ cdpId: closeDataSent.cdpId, collAmount: closeDataSent.collAmount, daiAmount: closeDataSent.daiAmount, minAccepted: closeDataSent.minAccepted, joinAddr: closeDataSent.joinAddr, proxy: proxy, flFee: _fee, toDai: closeDataSent.toDai, reserve: _reserve, amount: _amount }); address user = DSProxy(payable(closeData.proxy)).owner(); exchangeData.dfsFeeDivider = SERVICE_FEE; exchangeData.user = user; address managerAddr = getManagerAddr(closeDataSent.managerType); closeCDP(closeData, exchangeData, user, managerAddr); } function closeCDP( CloseData memory _closeData, ExchangeData memory _exchangeData, address _user, address _managerAddr ) internal { paybackDebt(_managerAddr, _closeData.cdpId, Manager(_managerAddr).ilks(_closeData.cdpId), _closeData.daiAmount); // payback whole debt uint drawnAmount = drawMaxCollateral(_managerAddr, _closeData.cdpId, _closeData.joinAddr, _closeData.collAmount); // draw whole collateral uint daiSwaped = 0; if (_closeData.toDai) { _exchangeData.srcAmount = drawnAmount; (, daiSwaped) = _sell(_exchangeData); } else { _exchangeData.destAmount = (_closeData.daiAmount + _closeData.flFee); (, daiSwaped) = _buy(_exchangeData); } address tokenAddr = getVaultCollAddr(_closeData.joinAddr); if (_closeData.toDai) { tokenAddr = DAI_ADDRESS; } require(getBalance(tokenAddr) >= _closeData.minAccepted, "Below min. number of eth specified"); transferFundsBackToPoolInternal(_closeData.reserve, _closeData.amount.add(_closeData.flFee)); sendLeftover(tokenAddr, DAI_ADDRESS, payable(_user)); } function drawMaxCollateral(address _managerAddr, uint _cdpId, address _joinAddr, uint _amount) internal returns (uint) { Manager(_managerAddr).frob(_cdpId, -toPositiveInt(_amount), 0); Manager(_managerAddr).flux(_cdpId, address(this), _amount); uint joinAmount = _amount; if (Join(_joinAddr).dec() != 18) { joinAmount = _amount / (10 ** (18 - Join(_joinAddr).dec())); } Join(_joinAddr).exit(address(this), joinAmount); if (isEthJoinAddr(_joinAddr)) { Join(_joinAddr).gem().withdraw(joinAmount); // Weth -> Eth } return joinAmount; } function paybackDebt(address _managerAddr, uint _cdpId, bytes32 _ilk, uint _daiAmount) internal { address urn = Manager(_managerAddr).urns(_cdpId); daiJoin.dai().approve(DAI_JOIN_ADDRESS, _daiAmount); daiJoin.join(urn, _daiAmount); Manager(_managerAddr).frob(_cdpId, 0, normalizePaybackAmount(VAT_ADDRESS, urn, _ilk)); } function getVaultCollAddr(address _joinAddr) internal view returns (address) { address tokenAddr = address(Join(_joinAddr).gem()); if (tokenAddr == EXCHANGE_WETH_ADDRESS) { return KYBER_ETH_ADDRESS; } return tokenAddr; } function getPrice(bytes32 _ilk) public view returns (uint256) { (, uint256 mat) = spotter.ilks(_ilk); (, , uint256 spot, , ) = vat.ilks(_ilk); return rmul(rmul(spot, spotter.par()), mat); } receive() external override(FlashLoanReceiverBase, DFSExchangeCore) payable {} }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../mcd/saver/MCDSaverProxy.sol"; import "../../loggers/DefisaverLogger.sol"; import "../../interfaces/ILendingPool.sol"; import "../../exchangeV3/DFSExchangeData.sol"; import "../../utils/GasBurner.sol"; abstract contract IMCDSubscriptions { function unsubscribe(uint256 _cdpId) external virtual ; function subscribersPos(uint256 _cdpId) external virtual returns (uint256, bool); } contract MCDCloseTaker is MCDSaverProxyHelper, GasBurner { address public constant SUBSCRIPTION_ADDRESS_NEW = 0xC45d4f6B6bf41b6EdAA58B01c4298B8d9078269a; address public constant DEFISAVER_LOGGER = 0x5c55B921f590a89C1Ebe84dF170E655a82b62126; ILendingPool public constant lendingPool = ILendingPool(0x398eC7346DcD622eDc5ae82352F02bE94C62d119); address public constant SPOTTER_ADDRESS = 0x65C79fcB50Ca1594B025960e539eD7A9a6D434A3; address public constant VAT_ADDRESS = 0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B; address public constant DAI_ADDRESS = 0x6B175474E89094C44Da98b954EedeAC495271d0F; // solhint-disable-next-line const-name-snakecase DefisaverLogger public constant logger = DefisaverLogger(DEFISAVER_LOGGER); struct CloseData { uint cdpId; address joinAddr; uint collAmount; uint daiAmount; uint minAccepted; bool wholeDebt; bool toDai; ManagerType managerType; } Vat public constant vat = Vat(VAT_ADDRESS); Spotter public constant spotter = Spotter(SPOTTER_ADDRESS); function closeWithLoan( DFSExchangeData.ExchangeData memory _exchangeData, CloseData memory _closeData, address payable mcdCloseFlashLoan ) public payable burnGas(20) { mcdCloseFlashLoan.transfer(msg.value); // 0x fee address managerAddr = getManagerAddr(_closeData.managerType); if (_closeData.wholeDebt) { _closeData.daiAmount = getAllDebt( VAT_ADDRESS, Manager(managerAddr).urns(_closeData.cdpId), Manager(managerAddr).urns(_closeData.cdpId), Manager(managerAddr).ilks(_closeData.cdpId) ); (_closeData.collAmount, ) = getCdpInfo(Manager(managerAddr), _closeData.cdpId, Manager(managerAddr).ilks(_closeData.cdpId)); } Manager(managerAddr).cdpAllow(_closeData.cdpId, mcdCloseFlashLoan, 1); bytes memory packedData = _packData(_closeData, _exchangeData); bytes memory paramsData = abi.encode(address(this), packedData); lendingPool.flashLoan(mcdCloseFlashLoan, DAI_ADDRESS, _closeData.daiAmount, paramsData); Manager(managerAddr).cdpAllow(_closeData.cdpId, mcdCloseFlashLoan, 0); // If sub. to automatic protection unsubscribe unsubscribe(SUBSCRIPTION_ADDRESS_NEW, _closeData.cdpId); logger.Log(address(this), msg.sender, "MCDClose", abi.encode(_closeData.cdpId, _closeData.collAmount, _closeData.daiAmount, _closeData.toDai)); } /// @notice Gets the maximum amount of debt available to generate /// @param _managerAddr Address of the CDP Manager /// @param _cdpId Id of the CDP /// @param _ilk Ilk of the CDP function getMaxDebt(address _managerAddr, uint256 _cdpId, bytes32 _ilk) public view returns (uint256) { uint256 price = getPrice(_ilk); (, uint256 mat) = spotter.ilks(_ilk); (uint256 collateral, uint256 debt) = getCdpInfo(Manager(_managerAddr), _cdpId, _ilk); return sub(wdiv(wmul(collateral, price), mat), debt); } /// @notice Gets a price of the asset /// @param _ilk Ilk of the CDP function getPrice(bytes32 _ilk) public view returns (uint256) { (, uint256 mat) = spotter.ilks(_ilk); (, , uint256 spot, , ) = vat.ilks(_ilk); return rmul(rmul(spot, spotter.par()), mat); } function unsubscribe(address _subContract, uint _cdpId) internal { (, bool isSubscribed) = IMCDSubscriptions(_subContract).subscribersPos(_cdpId); if (isSubscribed) { IMCDSubscriptions(_subContract).unsubscribe(_cdpId); } } function _packData( CloseData memory _closeData, DFSExchangeData.ExchangeData memory _exchangeData ) internal pure returns (bytes memory) { return abi.encode(_closeData, _exchangeData); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../utils/FlashLoanReceiverBase.sol"; import "../../interfaces/DSProxyInterface.sol"; import "../../exchange/SaverExchangeCore.sol"; /// @title Contract that receives the FL from Aave for Repays/Boost contract CreamSaverFlashLoan is FlashLoanReceiverBase, SaverExchangeCore { ILendingPoolAddressesProvider public LENDING_POOL_ADDRESS_PROVIDER = ILendingPoolAddressesProvider(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8); address payable public COMPOUND_SAVER_FLASH_PROXY = 0x1e012554891d271eDc80ba8eB146EA5FF596fA51; address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public owner; using SafeERC20 for ERC20; constructor() FlashLoanReceiverBase(LENDING_POOL_ADDRESS_PROVIDER) public { owner = msg.sender; } /// @notice Called by Aave when sending back the FL amount /// @param _reserve The address of the borrowed token /// @param _amount Amount of FL tokens received /// @param _fee FL Aave fee /// @param _params The params that are sent from the original FL caller contract function executeOperation( address _reserve, uint256 _amount, uint256 _fee, bytes calldata _params) external override { // Format the call data for DSProxy (bytes memory proxyData, address payable proxyAddr) = packFunctionCall(_amount, _fee, _params); // Send Flash loan amount to DSProxy sendLoanToProxy(proxyAddr, _reserve, _amount); // Execute the DSProxy call DSProxyInterface(proxyAddr).execute(COMPOUND_SAVER_FLASH_PROXY, proxyData); // Repay the loan with the money DSProxy sent back transferFundsBackToPoolInternal(_reserve, _amount.add(_fee)); // if there is some eth left (0x fee), return it to user if (address(this).balance > 0) { tx.origin.transfer(address(this).balance); } } /// @notice Formats function data call so we can call it through DSProxy /// @param _amount Amount of FL /// @param _fee Fee of the FL /// @param _params Saver proxy params /// @return proxyData Formated function call data function packFunctionCall(uint _amount, uint _fee, bytes memory _params) internal pure returns (bytes memory proxyData, address payable) { ( bytes memory exDataBytes, address[2] memory cAddresses, // cCollAddress, cBorrowAddress uint256 gasCost, bool isRepay, address payable proxyAddr ) = abi.decode(_params, (bytes,address[2],uint256,bool,address)); ExchangeData memory _exData = unpackExchangeData(exDataBytes); uint[2] memory flashLoanData = [_amount, _fee]; if (isRepay) { proxyData = abi.encodeWithSignature("flashRepay((address,address,uint256,uint256,uint256,address,address,bytes,uint256),address[2],uint256,uint256[2])", _exData, cAddresses, gasCost, flashLoanData); } else { proxyData = abi.encodeWithSignature("flashBoost((address,address,uint256,uint256,uint256,address,address,bytes,uint256),address[2],uint256,uint256[2])", _exData, cAddresses, gasCost, flashLoanData); } return (proxyData, proxyAddr); } /// @notice Send the FL funds received to DSProxy /// @param _proxy DSProxy address /// @param _reserve Token address /// @param _amount Amount of tokens function sendLoanToProxy(address payable _proxy, address _reserve, uint _amount) internal { if (_reserve != ETH_ADDRESS) { ERC20(_reserve).safeTransfer(_proxy, _amount); } _proxy.transfer(address(this).balance); } receive() external override(SaverExchangeCore, FlashLoanReceiverBase) payable {} }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../exchange/SaverExchangeCore.sol"; contract ExchangeDataParser { function decodeExchangeData( SaverExchangeCore.ExchangeData memory exchangeData ) internal pure returns (address[4] memory, uint[4] memory, bytes memory) { return ( [exchangeData.srcAddr, exchangeData.destAddr, exchangeData.exchangeAddr, exchangeData.wrapper], [exchangeData.srcAmount, exchangeData.destAmount, exchangeData.minPrice, exchangeData.price0x], exchangeData.callData ); } function encodeExchangeData( address[4] memory exAddr, uint[4] memory exNum, bytes memory callData ) internal pure returns (SaverExchangeCore.ExchangeData memory) { return SaverExchangeCore.ExchangeData({ srcAddr: exAddr[0], destAddr: exAddr[1], srcAmount: exNum[0], destAmount: exNum[1], minPrice: exNum[2], wrapper: exAddr[3], exchangeAddr: exAddr[2], callData: callData, price0x: exNum[3] }); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../interfaces/GasTokenInterface.sol"; import "../interfaces/IFeeRecipient.sol"; import "./SaverExchangeCore.sol"; import "../DS/DSMath.sol"; import "../loggers/DefisaverLogger.sol"; import "../auth/AdminAuth.sol"; import "../utils/GasBurner.sol"; import "../utils/SafeERC20.sol"; contract SaverExchange is SaverExchangeCore, AdminAuth, GasBurner { using SafeERC20 for ERC20; uint256 public constant SERVICE_FEE = 800; // 0.125% Fee IFeeRecipient public constant _feeRecipient = IFeeRecipient(0x39C4a92Dc506300c3Ea4c67ca4CA611102ee6F2A); // solhint-disable-next-line const-name-snakecase DefisaverLogger public constant logger = DefisaverLogger(0x5c55B921f590a89C1Ebe84dF170E655a82b62126); uint public burnAmount = 10; /// @notice Takes a src amount of tokens and converts it into the dest token /// @dev Takes fee from the _srcAmount before the exchange /// @param exData [srcAddr, destAddr, srcAmount, destAmount, minPrice, exchangeType, exchangeAddr, callData, price0x] /// @param _user User address who called the exchange function sell(ExchangeData memory exData, address payable _user) public payable burnGas(burnAmount) { // take fee uint dfsFee = getFee(exData.srcAmount, exData.srcAddr); exData.srcAmount = sub(exData.srcAmount, dfsFee); // Perform the exchange (address wrapper, uint destAmount) = _sell(exData); // send back any leftover ether or tokens sendLeftover(exData.srcAddr, exData.destAddr, _user); // log the event logger.Log(address(this), msg.sender, "ExchangeSell", abi.encode(wrapper, exData.srcAddr, exData.destAddr, exData.srcAmount, destAmount)); } /// @notice Takes a dest amount of tokens and converts it from the src token /// @dev Send always more than needed for the swap, extra will be returned /// @param exData [srcAddr, destAddr, srcAmount, destAmount, minPrice, exchangeType, exchangeAddr, callData, price0x] /// @param _user User address who called the exchange function buy(ExchangeData memory exData, address payable _user) public payable burnGas(burnAmount){ uint dfsFee = getFee(exData.srcAmount, exData.srcAddr); exData.srcAmount = sub(exData.srcAmount, dfsFee); // Perform the exchange (address wrapper, uint srcAmount) = _buy(exData); // send back any leftover ether or tokens sendLeftover(exData.srcAddr, exData.destAddr, _user); // log the event logger.Log(address(this), msg.sender, "ExchangeBuy", abi.encode(wrapper, exData.srcAddr, exData.destAddr, srcAmount, exData.destAmount)); } /// @notice Takes a feePercentage and sends it to wallet /// @param _amount Dai amount of the whole trade /// @param _token Address of the token /// @return feeAmount Amount in Dai owner earned on the fee function getFee(uint256 _amount, address _token) internal returns (uint256 feeAmount) { uint256 fee = SERVICE_FEE; if (Discount(DISCOUNT_ADDRESS).isCustomFeeSet(msg.sender)) { fee = Discount(DISCOUNT_ADDRESS).getCustomServiceFee(msg.sender); } if (fee == 0) { feeAmount = 0; } else { address walletAddr = _feeRecipient.getFeeAddr(); feeAmount = _amount / fee; if (_token == KYBER_ETH_ADDRESS) { payable(walletAddr).transfer(feeAmount); } else { ERC20(_token).safeTransfer(walletAddr, feeAmount); } } } /// @notice Changes the amount of gas token we burn for each call /// @dev Only callable by the owner /// @param _newBurnAmount New amount of gas tokens to be burned function changeBurnAmount(uint _newBurnAmount) public { require(owner == msg.sender); burnAmount = _newBurnAmount; } }
pragma solidity ^0.6.0; import "../../utils/SafeERC20.sol"; import "../../interfaces/KyberNetworkProxyInterface.sol"; import "../../interfaces/IFeeRecipient.sol"; import "../../interfaces/ExchangeInterfaceV3.sol"; import "../../DS/DSMath.sol"; import "../../auth/AdminAuth.sol"; contract KyberWrapperV3 is DSMath, ExchangeInterfaceV3, AdminAuth { address public constant KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant KYBER_INTERFACE = 0x9AAb3f75489902f3a48495025729a0AF77d4b11e; IFeeRecipient public constant feeRecipient = IFeeRecipient(0x39C4a92Dc506300c3Ea4c67ca4CA611102ee6F2A); using SafeERC20 for ERC20; /// @notice Sells a _srcAmount of tokens at Kyber /// @param _srcAddr From token /// @param _destAddr To token /// @param _srcAmount From amount /// @return uint Destination amount function sell(address _srcAddr, address _destAddr, uint _srcAmount, bytes memory _additionalData) external override payable returns (uint) { ERC20 srcToken = ERC20(_srcAddr); ERC20 destToken = ERC20(_destAddr); address walletAddr = feeRecipient.getFeeAddr(); KyberNetworkProxyInterface kyberNetworkProxy = KyberNetworkProxyInterface(KYBER_INTERFACE); if (_srcAddr != KYBER_ETH_ADDRESS) { srcToken.safeApprove(address(kyberNetworkProxy), _srcAmount); } uint destAmount = kyberNetworkProxy.trade{value: msg.value}( srcToken, _srcAmount, destToken, msg.sender, uint(-1), 0, walletAddr ); return destAmount; } /// @notice Buys a _destAmount of tokens at Kyber /// @param _srcAddr From token /// @param _destAddr To token /// @param _destAmount To amount /// @return uint srcAmount function buy(address _srcAddr, address _destAddr, uint _destAmount, bytes memory _additionalData) external override payable returns(uint) { ERC20 srcToken = ERC20(_srcAddr); ERC20 destToken = ERC20(_destAddr); address walletAddr = feeRecipient.getFeeAddr(); uint srcAmount = 0; if (_srcAddr != KYBER_ETH_ADDRESS) { srcAmount = srcToken.balanceOf(address(this)); } else { srcAmount = msg.value; } KyberNetworkProxyInterface kyberNetworkProxy = KyberNetworkProxyInterface(KYBER_INTERFACE); if (_srcAddr != KYBER_ETH_ADDRESS) { srcToken.safeApprove(address(kyberNetworkProxy), srcAmount); } uint destAmount = kyberNetworkProxy.trade{value: msg.value}( srcToken, srcAmount, destToken, msg.sender, _destAmount, 0, walletAddr ); require(destAmount == _destAmount, "Wrong dest amount"); uint srcAmountAfter = 0; if (_srcAddr != KYBER_ETH_ADDRESS) { srcAmountAfter = srcToken.balanceOf(address(this)); } else { srcAmountAfter = address(this).balance; } // Send the leftover from the source token back sendLeftOver(_srcAddr); return (srcAmount - srcAmountAfter); } /// @notice Return a rate for which we can sell an amount of tokens /// @param _srcAddr From token /// @param _destAddr To token /// @param _srcAmount From amount /// @return rate Rate function getSellRate(address _srcAddr, address _destAddr, uint _srcAmount, bytes memory _additionalData) public override view returns (uint rate) { (rate, ) = KyberNetworkProxyInterface(KYBER_INTERFACE) .getExpectedRate(ERC20(_srcAddr), ERC20(_destAddr), _srcAmount); // multiply with decimal difference in src token rate = rate * (10**(18 - getDecimals(_srcAddr))); // divide with decimal difference in dest token rate = rate / (10**(18 - getDecimals(_destAddr))); } /// @notice Return a rate for which we can buy an amount of tokens /// @param _srcAddr From token /// @param _destAddr To token /// @param _destAmount To amount /// @return rate Rate function getBuyRate(address _srcAddr, address _destAddr, uint _destAmount, bytes memory _additionalData) public override view returns (uint rate) { uint256 srcRate = getSellRate(_destAddr, _srcAddr, _destAmount, _additionalData); uint256 srcAmount = wmul(srcRate, _destAmount); rate = getSellRate(_srcAddr, _destAddr, srcAmount, _additionalData); // increase rate by 3% too account for inaccuracy between sell/buy conversion rate = rate + (rate / 30); } /// @notice Send any leftover tokens, we use to clear out srcTokens after buy /// @param _srcAddr Source token address function sendLeftOver(address _srcAddr) internal { msg.sender.transfer(address(this).balance); if (_srcAddr != KYBER_ETH_ADDRESS) { ERC20(_srcAddr).safeTransfer(msg.sender, ERC20(_srcAddr).balanceOf(address(this))); } } receive() payable external {} function getDecimals(address _token) internal view returns (uint256) { if (_token == KYBER_ETH_ADDRESS) return 18; return ERC20(_token).decimals(); } }
pragma solidity ^0.6.0; import "./ERC20.sol"; abstract contract KyberNetworkProxyInterface { function maxGasPrice() external virtual view returns (uint256); function getUserCapInWei(address user) external virtual view returns (uint256); function getUserCapInTokenWei(address user, ERC20 token) external virtual view returns (uint256); function enabled() external virtual view returns (bool); function info(bytes32 id) external virtual view returns (uint256); function getExpectedRate(ERC20 src, ERC20 dest, uint256 srcQty) public virtual view returns (uint256 expectedRate, uint256 slippageRate); function tradeWithHint( ERC20 src, uint256 srcAmount, ERC20 dest, address destAddress, uint256 maxDestAmount, uint256 minConversionRate, address walletId, bytes memory hint ) public virtual payable returns (uint256); function trade( ERC20 src, uint256 srcAmount, ERC20 dest, address destAddress, uint256 maxDestAmount, uint256 minConversionRate, address walletId ) public virtual payable returns (uint256); function swapEtherToToken(ERC20 token, uint256 minConversionRate) external virtual payable returns (uint256); function swapTokenToEther(ERC20 token, uint256 tokenQty, uint256 minRate) external virtual payable returns (uint256); function swapTokenToToken(ERC20 src, uint256 srcAmount, ERC20 dest, uint256 minConversionRate) public virtual returns (uint256); }
pragma solidity ^0.6.0; import "../../utils/SafeERC20.sol"; import "../../interfaces/ExchangeInterfaceV3.sol"; import "../../interfaces/UniswapRouterInterface.sol"; import "../../DS/DSMath.sol"; import "../../auth/AdminAuth.sol"; /// @title DFS exchange wrapper for UniswapV2 contract UniswapWrapperV3 is DSMath, ExchangeInterfaceV3, AdminAuth { address public constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address public constant KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; UniswapRouterInterface public constant router = UniswapRouterInterface(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); using SafeERC20 for ERC20; /// @notice Sells a _srcAmount of tokens at UniswapV2 /// @param _srcAddr From token /// @param _destAddr To token /// @param _srcAmount From amount /// @return uint Destination amount function sell(address _srcAddr, address _destAddr, uint _srcAmount, bytes memory _additionalData) external payable override returns (uint) { _srcAddr = ethToWethAddr(_srcAddr); _destAddr = ethToWethAddr(_destAddr); uint[] memory amounts; address[] memory path = abi.decode(_additionalData, (address[])); ERC20(_srcAddr).safeApprove(address(router), _srcAmount); // if we are buying ether if (_destAddr == WETH_ADDRESS) { amounts = router.swapExactTokensForETH(_srcAmount, 1, path, msg.sender, block.timestamp + 1); } // if we are selling token to token else { amounts = router.swapExactTokensForTokens(_srcAmount, 1, path, msg.sender, block.timestamp + 1); } return amounts[amounts.length - 1]; } /// @notice Buys a _destAmount of tokens at UniswapV2 /// @param _srcAddr From token /// @param _destAddr To token /// @param _destAmount To amount /// @return uint srcAmount function buy(address _srcAddr, address _destAddr, uint _destAmount, bytes memory _additionalData) external override payable returns(uint) { _srcAddr = ethToWethAddr(_srcAddr); _destAddr = ethToWethAddr(_destAddr); uint[] memory amounts; address[] memory path = abi.decode(_additionalData, (address[])); ERC20(_srcAddr).safeApprove(address(router), uint(-1)); // if we are buying ether if (_destAddr == WETH_ADDRESS) { amounts = router.swapTokensForExactETH(_destAmount, uint(-1), path, msg.sender, block.timestamp + 1); } // if we are buying token to token else { amounts = router.swapTokensForExactTokens(_destAmount, uint(-1), path, msg.sender, block.timestamp + 1); } // Send the leftover from the source token back sendLeftOver(_srcAddr); return amounts[0]; } /// @notice Return a rate for which we can sell an amount of tokens /// @param _srcAddr From token /// @param _destAddr To token /// @param _srcAmount From amount /// @return uint Rate function getSellRate(address _srcAddr, address _destAddr, uint _srcAmount, bytes memory _additionalData) public override view returns (uint) { _srcAddr = ethToWethAddr(_srcAddr); _destAddr = ethToWethAddr(_destAddr); address[] memory path = abi.decode(_additionalData, (address[])); uint[] memory amounts = router.getAmountsOut(_srcAmount, path); return wdiv(amounts[amounts.length - 1], _srcAmount); } /// @notice Return a rate for which we can buy an amount of tokens /// @param _srcAddr From token /// @param _destAddr To token /// @param _destAmount To amount /// @return uint Rate function getBuyRate(address _srcAddr, address _destAddr, uint _destAmount, bytes memory _additionalData) public override view returns (uint) { _srcAddr = ethToWethAddr(_srcAddr); _destAddr = ethToWethAddr(_destAddr); address[] memory path = abi.decode(_additionalData, (address[])); uint[] memory amounts = router.getAmountsIn(_destAmount, path); return wdiv(_destAmount, amounts[0]); } /// @notice Send any leftover tokens, we use to clear out srcTokens after buy /// @param _srcAddr Source token address function sendLeftOver(address _srcAddr) internal { msg.sender.transfer(address(this).balance); if (_srcAddr != KYBER_ETH_ADDRESS) { ERC20(_srcAddr).safeTransfer(msg.sender, ERC20(_srcAddr).balanceOf(address(this))); } } /// @notice Converts Kybers Eth address -> Weth /// @param _src Input address function ethToWethAddr(address _src) internal pure returns (address) { return _src == KYBER_ETH_ADDRESS ? WETH_ADDRESS : _src; } function getDecimals(address _token) internal view returns (uint256) { if (_token == KYBER_ETH_ADDRESS) return 18; return ERC20(_token).decimals(); } receive() payable external {} }
pragma solidity ^0.6.0; abstract contract UniswapRouterInterface { function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external virtual returns (uint[] memory amounts); function swapExactTokensForTokens( uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline ) external virtual returns (uint[] memory amounts); function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) external virtual returns (uint[] memory amounts); function swapTokensForExactTokens( uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline ) external virtual returns (uint[] memory amounts); function getAmountsOut(uint amountIn, address[] memory path) public virtual view returns (uint[] memory amounts); function getAmountsIn(uint amountOut, address[] memory path) public virtual view returns (uint[] memory amounts); }
pragma solidity ^0.6.0; import "../../utils/SafeERC20.sol"; import "../../interfaces/ExchangeInterfaceV2.sol"; import "../../interfaces/UniswapRouterInterface.sol"; import "../../DS/DSMath.sol"; import "../../auth/AdminAuth.sol"; /// @title DFS exchange wrapper for UniswapV2 contract UniswapV2Wrapper is DSMath, ExchangeInterfaceV2, AdminAuth { address public constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address public constant KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; UniswapRouterInterface public constant router = UniswapRouterInterface(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); using SafeERC20 for ERC20; /// @notice Sells a _srcAmount of tokens at UniswapV2 /// @param _srcAddr From token /// @param _destAddr To token /// @param _srcAmount From amount /// @return uint Destination amount function sell(address _srcAddr, address _destAddr, uint _srcAmount) external payable override returns (uint) { _srcAddr = ethToWethAddr(_srcAddr); _destAddr = ethToWethAddr(_destAddr); uint[] memory amounts; address[] memory path = new address[](2); path[0] = _srcAddr; path[1] = _destAddr; ERC20(_srcAddr).safeApprove(address(router), _srcAmount); // if we are buying ether if (_destAddr == WETH_ADDRESS) { amounts = router.swapExactTokensForETH(_srcAmount, 1, path, msg.sender, block.timestamp + 1); } // if we are selling token to token else { amounts = router.swapExactTokensForTokens(_srcAmount, 1, path, msg.sender, block.timestamp + 1); } return amounts[amounts.length - 1]; } /// @notice Buys a _destAmount of tokens at UniswapV2 /// @param _srcAddr From token /// @param _destAddr To token /// @param _destAmount To amount /// @return uint srcAmount function buy(address _srcAddr, address _destAddr, uint _destAmount) external override payable returns(uint) { _srcAddr = ethToWethAddr(_srcAddr); _destAddr = ethToWethAddr(_destAddr); uint[] memory amounts; address[] memory path = new address[](2); path[0] = _srcAddr; path[1] = _destAddr; ERC20(_srcAddr).safeApprove(address(router), uint(-1)); // if we are buying ether if (_destAddr == WETH_ADDRESS) { amounts = router.swapTokensForExactETH(_destAmount, uint(-1), path, msg.sender, block.timestamp + 1); } // if we are buying token to token else { amounts = router.swapTokensForExactTokens(_destAmount, uint(-1), path, msg.sender, block.timestamp + 1); } // Send the leftover from the source token back sendLeftOver(_srcAddr); return amounts[0]; } /// @notice Return a rate for which we can sell an amount of tokens /// @param _srcAddr From token /// @param _destAddr To token /// @param _srcAmount From amount /// @return uint Rate function getSellRate(address _srcAddr, address _destAddr, uint _srcAmount) public override view returns (uint) { _srcAddr = ethToWethAddr(_srcAddr); _destAddr = ethToWethAddr(_destAddr); address[] memory path = new address[](2); path[0] = _srcAddr; path[1] = _destAddr; uint[] memory amounts = router.getAmountsOut(_srcAmount, path); return wdiv(amounts[amounts.length - 1], _srcAmount); } /// @notice Return a rate for which we can buy an amount of tokens /// @param _srcAddr From token /// @param _destAddr To token /// @param _destAmount To amount /// @return uint Rate function getBuyRate(address _srcAddr, address _destAddr, uint _destAmount) public override view returns (uint) { _srcAddr = ethToWethAddr(_srcAddr); _destAddr = ethToWethAddr(_destAddr); address[] memory path = new address[](2); path[0] = _srcAddr; path[1] = _destAddr; uint[] memory amounts = router.getAmountsIn(_destAmount, path); return wdiv(_destAmount, amounts[0]); } /// @notice Send any leftover tokens, we use to clear out srcTokens after buy /// @param _srcAddr Source token address function sendLeftOver(address _srcAddr) internal { msg.sender.transfer(address(this).balance); if (_srcAddr != KYBER_ETH_ADDRESS) { ERC20(_srcAddr).safeTransfer(msg.sender, ERC20(_srcAddr).balanceOf(address(this))); } } /// @notice Converts Kybers Eth address -> Weth /// @param _src Input address function ethToWethAddr(address _src) internal pure returns (address) { return _src == KYBER_ETH_ADDRESS ? WETH_ADDRESS : _src; } function getDecimals(address _token) internal view returns (uint256) { if (_token == KYBER_ETH_ADDRESS) return 18; return ERC20(_token).decimals(); } receive() payable external {} }
pragma solidity ^0.6.0; import "../../utils/SafeERC20.sol"; import "../../interfaces/KyberNetworkProxyInterface.sol"; import "../../interfaces/ExchangeInterfaceV2.sol"; import "../../interfaces/UniswapExchangeInterface.sol"; import "../../interfaces/UniswapFactoryInterface.sol"; import "../../DS/DSMath.sol"; import "../../auth/AdminAuth.sol"; contract UniswapWrapper is DSMath, ExchangeInterfaceV2, AdminAuth { address public constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address public constant KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant UNISWAP_FACTORY = 0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95; using SafeERC20 for ERC20; /// @notice Sells a _srcAmount of tokens at Uniswap /// @param _srcAddr From token /// @param _destAddr To token /// @param _srcAmount From amount /// @return uint Destination amount function sell(address _srcAddr, address _destAddr, uint _srcAmount) external payable override returns (uint) { address uniswapExchangeAddr; uint destAmount; _srcAddr = ethToWethAddr(_srcAddr); _destAddr = ethToWethAddr(_destAddr); // if we are buying ether if (_destAddr == WETH_ADDRESS) { uniswapExchangeAddr = UniswapFactoryInterface(UNISWAP_FACTORY).getExchange(_srcAddr); ERC20(_srcAddr).safeApprove(uniswapExchangeAddr, _srcAmount); destAmount = UniswapExchangeInterface(uniswapExchangeAddr). tokenToEthTransferInput(_srcAmount, 1, block.timestamp + 1, msg.sender); } // if we are selling token to token else { uniswapExchangeAddr = UniswapFactoryInterface(UNISWAP_FACTORY).getExchange(_srcAddr); ERC20(_srcAddr).safeApprove(uniswapExchangeAddr, _srcAmount); destAmount = UniswapExchangeInterface(uniswapExchangeAddr). tokenToTokenTransferInput(_srcAmount, 1, 1, block.timestamp + 1, msg.sender, _destAddr); } return destAmount; } /// @notice Buys a _destAmount of tokens at Uniswap /// @param _srcAddr From token /// @param _destAddr To token /// @param _destAmount To amount /// @return uint srcAmount function buy(address _srcAddr, address _destAddr, uint _destAmount) external override payable returns(uint) { address uniswapExchangeAddr; uint srcAmount; _srcAddr = ethToWethAddr(_srcAddr); _destAddr = ethToWethAddr(_destAddr); // if we are buying ether if (_destAddr == WETH_ADDRESS) { uniswapExchangeAddr = UniswapFactoryInterface(UNISWAP_FACTORY).getExchange(_srcAddr); ERC20(_srcAddr).safeApprove(uniswapExchangeAddr, uint(-1)); srcAmount = UniswapExchangeInterface(uniswapExchangeAddr). tokenToEthTransferOutput(_destAmount, uint(-1), block.timestamp + 1, msg.sender); } // if we are buying token to token else { uniswapExchangeAddr = UniswapFactoryInterface(UNISWAP_FACTORY).getExchange(_srcAddr); ERC20(_srcAddr).safeApprove(uniswapExchangeAddr, uint(-1)); srcAmount = UniswapExchangeInterface(uniswapExchangeAddr). tokenToTokenTransferOutput(_destAmount, uint(-1), uint(-1), block.timestamp + 1, msg.sender, _destAddr); } // Send the leftover from the source token back sendLeftOver(_srcAddr); return srcAmount; } /// @notice Return a rate for which we can sell an amount of tokens /// @param _srcAddr From token /// @param _destAddr To token /// @param _srcAmount From amount /// @return uint Rate function getSellRate(address _srcAddr, address _destAddr, uint _srcAmount) public override view returns (uint) { _srcAddr = ethToWethAddr(_srcAddr); _destAddr = ethToWethAddr(_destAddr); if(_srcAddr == WETH_ADDRESS) { address uniswapTokenAddress = UniswapFactoryInterface(UNISWAP_FACTORY).getExchange(_destAddr); return wdiv(UniswapExchangeInterface(uniswapTokenAddress).getEthToTokenInputPrice(_srcAmount), _srcAmount); } else if (_destAddr == WETH_ADDRESS) { address uniswapTokenAddress = UniswapFactoryInterface(UNISWAP_FACTORY).getExchange(_srcAddr); return wdiv(UniswapExchangeInterface(uniswapTokenAddress).getTokenToEthInputPrice(_srcAmount), _srcAmount); } else { uint ethBought = UniswapExchangeInterface(UniswapFactoryInterface(UNISWAP_FACTORY).getExchange(_srcAddr)).getTokenToEthInputPrice(_srcAmount); return wdiv(UniswapExchangeInterface(UniswapFactoryInterface(UNISWAP_FACTORY).getExchange(_destAddr)).getEthToTokenInputPrice(ethBought), _srcAmount); } } /// @notice Return a rate for which we can buy an amount of tokens /// @param _srcAddr From token /// @param _destAddr To token /// @param _destAmount To amount /// @return uint Rate function getBuyRate(address _srcAddr, address _destAddr, uint _destAmount) public override view returns (uint) { _srcAddr = ethToWethAddr(_srcAddr); _destAddr = ethToWethAddr(_destAddr); if(_srcAddr == WETH_ADDRESS) { address uniswapTokenAddress = UniswapFactoryInterface(UNISWAP_FACTORY).getExchange(_destAddr); return wdiv(1 ether, wdiv(UniswapExchangeInterface(uniswapTokenAddress).getEthToTokenOutputPrice(_destAmount), _destAmount)); } else if (_destAddr == WETH_ADDRESS) { address uniswapTokenAddress = UniswapFactoryInterface(UNISWAP_FACTORY).getExchange(_srcAddr); return wdiv(1 ether, wdiv(UniswapExchangeInterface(uniswapTokenAddress).getTokenToEthOutputPrice(_destAmount), _destAmount)); } else { uint ethNeeded = UniswapExchangeInterface(UniswapFactoryInterface(UNISWAP_FACTORY).getExchange(_destAddr)).getTokenToEthOutputPrice(_destAmount); return wdiv(1 ether, wdiv(UniswapExchangeInterface(UniswapFactoryInterface(UNISWAP_FACTORY).getExchange(_srcAddr)).getEthToTokenOutputPrice(ethNeeded), _destAmount)); } } /// @notice Send any leftover tokens, we use to clear out srcTokens after buy /// @param _srcAddr Source token address function sendLeftOver(address _srcAddr) internal { msg.sender.transfer(address(this).balance); if (_srcAddr != KYBER_ETH_ADDRESS) { ERC20(_srcAddr).safeTransfer(msg.sender, ERC20(_srcAddr).balanceOf(address(this))); } } /// @notice Converts Kybers Eth address -> Weth /// @param _src Input address function ethToWethAddr(address _src) internal pure returns (address) { return _src == KYBER_ETH_ADDRESS ? WETH_ADDRESS : _src; } receive() payable external {} }
pragma solidity ^0.6.0; abstract contract UniswapExchangeInterface { function getEthToTokenInputPrice(uint256 eth_sold) external virtual view returns (uint256 tokens_bought); function getEthToTokenOutputPrice(uint256 tokens_bought) external virtual view returns (uint256 eth_sold); function getTokenToEthInputPrice(uint256 tokens_sold) external virtual view returns (uint256 eth_bought); function getTokenToEthOutputPrice(uint256 eth_bought) external virtual view returns (uint256 tokens_sold); function tokenToEthTransferInput( uint256 tokens_sold, uint256 min_eth, uint256 deadline, address recipient ) external virtual returns (uint256 eth_bought); function ethToTokenTransferInput(uint256 min_tokens, uint256 deadline, address recipient) external virtual payable returns (uint256 tokens_bought); function tokenToTokenTransferInput( uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address recipient, address token_addr ) external virtual returns (uint256 tokens_bought); function ethToTokenTransferOutput( uint256 tokens_bought, uint256 deadline, address recipient ) external virtual payable returns (uint256 eth_sold); function tokenToEthTransferOutput( uint256 eth_bought, uint256 max_tokens, uint256 deadline, address recipient ) external virtual returns (uint256 tokens_sold); function tokenToTokenTransferOutput( uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address recipient, address token_addr ) external virtual returns (uint256 tokens_sold); }
pragma solidity ^0.6.0; abstract contract UniswapFactoryInterface { function getExchange(address token) external view virtual returns (address exchange); }
pragma solidity ^0.6.0; import "../../utils/SafeERC20.sol"; import "../../interfaces/KyberNetworkProxyInterface.sol"; import "../../interfaces/ExchangeInterfaceV2.sol"; import "../../interfaces/IFeeRecipient.sol"; import "../../DS/DSMath.sol"; import "../../auth/AdminAuth.sol"; contract KyberWrapper is DSMath, ExchangeInterfaceV2, AdminAuth { address public constant KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant KYBER_INTERFACE = 0x9AAb3f75489902f3a48495025729a0AF77d4b11e; IFeeRecipient public constant feeRecipient = IFeeRecipient(0x39C4a92Dc506300c3Ea4c67ca4CA611102ee6F2A); using SafeERC20 for ERC20; /// @notice Sells a _srcAmount of tokens at Kyber /// @param _srcAddr From token /// @param _destAddr To token /// @param _srcAmount From amount /// @return uint Destination amount function sell(address _srcAddr, address _destAddr, uint _srcAmount) external override payable returns (uint) { ERC20 srcToken = ERC20(_srcAddr); ERC20 destToken = ERC20(_destAddr); address walletAddr = feeRecipient.getFeeAddr(); KyberNetworkProxyInterface kyberNetworkProxy = KyberNetworkProxyInterface(KYBER_INTERFACE); if (_srcAddr != KYBER_ETH_ADDRESS) { srcToken.safeApprove(address(kyberNetworkProxy), _srcAmount); } uint destAmount = kyberNetworkProxy.trade{value: msg.value}( srcToken, _srcAmount, destToken, msg.sender, uint(-1), 0, walletAddr ); return destAmount; } /// @notice Buys a _destAmount of tokens at Kyber /// @param _srcAddr From token /// @param _destAddr To token /// @param _destAmount To amount /// @return uint srcAmount function buy(address _srcAddr, address _destAddr, uint _destAmount) external override payable returns(uint) { ERC20 srcToken = ERC20(_srcAddr); ERC20 destToken = ERC20(_destAddr); address walletAddr = feeRecipient.getFeeAddr(); uint srcAmount = 0; if (_srcAddr != KYBER_ETH_ADDRESS) { srcAmount = srcToken.balanceOf(address(this)); } else { srcAmount = msg.value; } KyberNetworkProxyInterface kyberNetworkProxy = KyberNetworkProxyInterface(KYBER_INTERFACE); if (_srcAddr != KYBER_ETH_ADDRESS) { srcToken.safeApprove(address(kyberNetworkProxy), srcAmount); } uint destAmount = kyberNetworkProxy.trade{value: msg.value}( srcToken, srcAmount, destToken, msg.sender, _destAmount, 0, walletAddr ); require(destAmount == _destAmount, "Wrong dest amount"); uint srcAmountAfter = 0; if (_srcAddr != KYBER_ETH_ADDRESS) { srcAmountAfter = srcToken.balanceOf(address(this)); } else { srcAmountAfter = address(this).balance; } // Send the leftover from the source token back sendLeftOver(_srcAddr); return (srcAmount - srcAmountAfter); } /// @notice Return a rate for which we can sell an amount of tokens /// @param _srcAddr From token /// @param _destAddr To token /// @param _srcAmount From amount /// @return rate Rate function getSellRate(address _srcAddr, address _destAddr, uint _srcAmount) public override view returns (uint rate) { (rate, ) = KyberNetworkProxyInterface(KYBER_INTERFACE) .getExpectedRate(ERC20(_srcAddr), ERC20(_destAddr), _srcAmount); // multiply with decimal difference in src token rate = rate * (10**(18 - getDecimals(_srcAddr))); // divide with decimal difference in dest token rate = rate / (10**(18 - getDecimals(_destAddr))); } /// @notice Return a rate for which we can buy an amount of tokens /// @param _srcAddr From token /// @param _destAddr To token /// @param _destAmount To amount /// @return rate Rate function getBuyRate(address _srcAddr, address _destAddr, uint _destAmount) public override view returns (uint rate) { uint256 srcRate = getSellRate(_destAddr, _srcAddr, _destAmount); uint256 srcAmount = wmul(srcRate, _destAmount); rate = getSellRate(_srcAddr, _destAddr, srcAmount); // increase rate by 3% too account for inaccuracy between sell/buy conversion rate = rate + (rate / 30); } /// @notice Send any leftover tokens, we use to clear out srcTokens after buy /// @param _srcAddr Source token address function sendLeftOver(address _srcAddr) internal { msg.sender.transfer(address(this).balance); if (_srcAddr != KYBER_ETH_ADDRESS) { ERC20(_srcAddr).safeTransfer(msg.sender, ERC20(_srcAddr).balanceOf(address(this))); } } receive() payable external {} function getDecimals(address _token) internal view returns (uint256) { if (_token == KYBER_ETH_ADDRESS) return 18; return ERC20(_token).decimals(); } }
pragma solidity ^0.6.0; import "../../interfaces/ExchangeInterfaceV2.sol"; import "../../interfaces/OasisInterface.sol"; import "../../interfaces/TokenInterface.sol"; import "../../DS/DSMath.sol"; import "../../utils/SafeERC20.sol"; import "../../auth/AdminAuth.sol"; contract OasisTradeWrapper is DSMath, ExchangeInterfaceV2, AdminAuth { using SafeERC20 for ERC20; address public constant OTC_ADDRESS = 0x794e6e91555438aFc3ccF1c5076A74F42133d08D; address public constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address public constant KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; /// @notice Sells a _srcAmount of tokens at Oasis /// @param _srcAddr From token /// @param _destAddr To token /// @param _srcAmount From amount /// @return uint Destination amount function sell(address _srcAddr, address _destAddr, uint _srcAmount) external override payable returns (uint) { address srcAddr = ethToWethAddr(_srcAddr); address destAddr = ethToWethAddr(_destAddr); ERC20(srcAddr).safeApprove(OTC_ADDRESS, _srcAmount); uint destAmount = OasisInterface(OTC_ADDRESS).sellAllAmount(srcAddr, _srcAmount, destAddr, 0); // convert weth -> eth and send back if (destAddr == WETH_ADDRESS) { TokenInterface(WETH_ADDRESS).withdraw(destAmount); msg.sender.transfer(destAmount); } else { ERC20(destAddr).safeTransfer(msg.sender, destAmount); } return destAmount; } /// @notice Buys a _destAmount of tokens at Oasis /// @param _srcAddr From token /// @param _destAddr To token /// @param _destAmount To amount /// @return uint srcAmount function buy(address _srcAddr, address _destAddr, uint _destAmount) external override payable returns(uint) { address srcAddr = ethToWethAddr(_srcAddr); address destAddr = ethToWethAddr(_destAddr); ERC20(srcAddr).safeApprove(OTC_ADDRESS, uint(-1)); uint srcAmount = OasisInterface(OTC_ADDRESS).buyAllAmount(destAddr, _destAmount, srcAddr, uint(-1)); // convert weth -> eth and send back if (destAddr == WETH_ADDRESS) { TokenInterface(WETH_ADDRESS).withdraw(_destAmount); msg.sender.transfer(_destAmount); } else { ERC20(destAddr).safeTransfer(msg.sender, _destAmount); } // Send the leftover from the source token back sendLeftOver(srcAddr); return srcAmount; } /// @notice Return a rate for which we can sell an amount of tokens /// @param _srcAddr From token /// @param _destAddr To token /// @param _srcAmount From amount /// @return uint Rate function getSellRate(address _srcAddr, address _destAddr, uint _srcAmount) public override view returns (uint) { address srcAddr = ethToWethAddr(_srcAddr); address destAddr = ethToWethAddr(_destAddr); return wdiv(OasisInterface(OTC_ADDRESS).getBuyAmount(destAddr, srcAddr, _srcAmount), _srcAmount); } /// @notice Return a rate for which we can buy an amount of tokens /// @param _srcAddr From token /// @param _destAddr To token /// @param _destAmount To amount /// @return uint Rate function getBuyRate(address _srcAddr, address _destAddr, uint _destAmount) public override view returns (uint) { address srcAddr = ethToWethAddr(_srcAddr); address destAddr = ethToWethAddr(_destAddr); return wdiv(1 ether, wdiv(OasisInterface(OTC_ADDRESS).getPayAmount(srcAddr, destAddr, _destAmount), _destAmount)); } /// @notice Send any leftover tokens, we use to clear out srcTokens after buy /// @param _srcAddr Source token address function sendLeftOver(address _srcAddr) internal { msg.sender.transfer(address(this).balance); if (_srcAddr != KYBER_ETH_ADDRESS) { ERC20(_srcAddr).safeTransfer(msg.sender, ERC20(_srcAddr).balanceOf(address(this))); } } /// @notice Converts Kybers Eth address -> Weth /// @param _src Input address function ethToWethAddr(address _src) internal pure returns (address) { return _src == KYBER_ETH_ADDRESS ? WETH_ADDRESS : _src; } receive() payable external {} }
pragma solidity ^0.6.0; import "../DS/DSMath.sol"; import "../interfaces/TokenInterface.sol"; import "../interfaces/ExchangeInterfaceV2.sol"; import "./SaverExchangeHelper.sol"; contract Prices is DSMath { address public constant KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; enum ActionType { SELL, BUY } /// @notice Returns the best estimated price from 2 exchanges /// @param _amount Amount of source tokens you want to exchange /// @param _srcToken Address of the source token /// @param _destToken Address of the destination token /// @param _type Type of action SELL|BUY /// @param _wrappers Array of wrapper addresses to compare /// @return (address, uint) The address of the best exchange and the exchange price function getBestPrice( uint256 _amount, address _srcToken, address _destToken, ActionType _type, address[] memory _wrappers ) public returns (address, uint256) { uint256[] memory rates = new uint256[](_wrappers.length); for (uint i=0; i<_wrappers.length; i++) { rates[i] = getExpectedRate(_wrappers[i], _srcToken, _destToken, _amount, _type); } return getBiggestRate(_wrappers, rates); } /// @notice Return the expected rate from the exchange wrapper /// @dev In case of Oasis/Uniswap handles the different precision tokens /// @param _wrapper Address of exchange wrapper /// @param _srcToken From token /// @param _destToken To token /// @param _amount Amount to be exchanged /// @param _type Type of action SELL|BUY function getExpectedRate( address _wrapper, address _srcToken, address _destToken, uint256 _amount, ActionType _type ) public returns (uint256) { bool success; bytes memory result; if (_type == ActionType.SELL) { (success, result) = _wrapper.call(abi.encodeWithSignature( "getSellRate(address,address,uint256)", _srcToken, _destToken, _amount )); } else { (success, result) = _wrapper.call(abi.encodeWithSignature( "getBuyRate(address,address,uint256)", _srcToken, _destToken, _amount )); } if (success) { return sliceUint(result, 0); } return 0; } /// @notice Finds the biggest rate between exchanges, needed for sell rate /// @param _wrappers Array of wrappers to compare /// @param _rates Array of rates to compare function getBiggestRate( address[] memory _wrappers, uint256[] memory _rates ) internal pure returns (address, uint) { uint256 maxIndex = 0; // starting from 0 in case there is only one rate in array for (uint256 i=0; i<_rates.length; i++) { if (_rates[i] > _rates[maxIndex]) { maxIndex = i; } } return (_wrappers[maxIndex], _rates[maxIndex]); } function getDecimals(address _token) internal view returns (uint256) { if (_token == KYBER_ETH_ADDRESS) return 18; return ERC20(_token).decimals(); } function sliceUint(bytes memory bs, uint256 start) internal pure returns (uint256) { require(bs.length >= start + 32, "slicing out of range"); uint256 x; assembly { x := mload(add(bs, add(0x20, start))) } return x; } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../DS/DSMath.sol"; import "../interfaces/TokenInterface.sol"; import "../interfaces/ExchangeInterfaceV3.sol"; import "../utils/SafeERC20.sol"; contract DFSPrices is DSMath { address public constant KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; enum ActionType { SELL, BUY } /// @notice Returns the best estimated price from 2 exchanges /// @param _amount Amount of source tokens you want to exchange /// @param _srcToken Address of the source token /// @param _destToken Address of the destination token /// @param _type Type of action SELL|BUY /// @param _wrappers Array of wrapper addresses to compare /// @return (address, uint) The address of the best exchange and the exchange price function getBestPrice( uint256 _amount, address _srcToken, address _destToken, ActionType _type, address[] memory _wrappers, bytes[] memory _additionalData ) public returns (address, uint256) { uint256[] memory rates = new uint256[](_wrappers.length); for (uint i=0; i<_wrappers.length; i++) { rates[i] = getExpectedRate(_wrappers[i], _srcToken, _destToken, _amount, _type, _additionalData[i]); } return getBiggestRate(_wrappers, rates); } /// @notice Return the expected rate from the exchange wrapper /// @dev In case of Oasis/Uniswap handles the different precision tokens /// @param _wrapper Address of exchange wrapper /// @param _srcToken From token /// @param _destToken To token /// @param _amount Amount to be exchanged /// @param _type Type of action SELL|BUY function getExpectedRate( address _wrapper, address _srcToken, address _destToken, uint256 _amount, ActionType _type, bytes memory _additionalData ) public returns (uint256) { bool success; bytes memory result; if (_type == ActionType.SELL) { (success, result) = _wrapper.call(abi.encodeWithSignature( "getSellRate(address,address,uint256,bytes)", _srcToken, _destToken, _amount, _additionalData )); } else { (success, result) = _wrapper.call(abi.encodeWithSignature( "getBuyRate(address,address,uint256,bytes)", _srcToken, _destToken, _amount, _additionalData )); } if (success) { return sliceUint(result, 0); } return 0; } /// @notice Finds the biggest rate between exchanges, needed for sell rate /// @param _wrappers Array of wrappers to compare /// @param _rates Array of rates to compare function getBiggestRate( address[] memory _wrappers, uint256[] memory _rates ) internal pure returns (address, uint) { uint256 maxIndex = 0; // starting from 0 in case there is only one rate in array for (uint256 i=0; i<_rates.length; i++) { if (_rates[i] > _rates[maxIndex]) { maxIndex = i; } } return (_wrappers[maxIndex], _rates[maxIndex]); } function getDecimals(address _token) internal view returns (uint256) { if (_token == KYBER_ETH_ADDRESS) return 18; return ERC20(_token).decimals(); } function sliceUint(bytes memory bs, uint256 start) internal pure returns (uint256) { require(bs.length >= start + 32, "slicing out of range"); uint256 x; assembly { x := mload(add(bs, add(0x20, start))) } return x; } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../utils/SafeERC20.sol"; import "../../DS/DSMath.sol"; import "../../auth/AdminAuth.sol"; import "../DFSExchangeHelper.sol"; import "../../interfaces/OffchainWrapperInterface.sol"; import "../../interfaces/TokenInterface.sol"; contract ZeroxWrapper is OffchainWrapperInterface, DFSExchangeHelper, AdminAuth, DSMath { string public constant ERR_SRC_AMOUNT = "Not enough funds"; string public constant ERR_PROTOCOL_FEE = "Not enough eth for protcol fee"; string public constant ERR_TOKENS_SWAPED_ZERO = "Order success but amount 0"; using SafeERC20 for ERC20; /// @notice Takes order from 0x and returns bool indicating if it is successful /// @param _exData Exchange data /// @param _type Action type (buy or sell) function takeOrder( ExchangeData memory _exData, ActionType _type ) override public payable returns (bool success, uint256) { // check that contract have enough balance for exchange and protocol fee require(getBalance(_exData.srcAddr) >= _exData.srcAmount, ERR_SRC_AMOUNT); require(getBalance(KYBER_ETH_ADDRESS) >= _exData.offchainData.protocolFee, ERR_PROTOCOL_FEE); /// @dev 0x always uses max approve in v1, so we approve the exact amount we want to sell /// @dev safeApprove is modified to always first set approval to 0, then to exact amount if (_type == ActionType.SELL) { ERC20(_exData.srcAddr).safeApprove(_exData.offchainData.allowanceTarget, _exData.srcAmount); } else { uint srcAmount = wdiv(_exData.destAmount, _exData.offchainData.price) + 1; // + 1 so we round up ERC20(_exData.srcAddr).safeApprove(_exData.offchainData.allowanceTarget, srcAmount); } // we know that it will be eth if dest addr is either weth or eth address destAddr = _exData.destAddr == KYBER_ETH_ADDRESS ? EXCHANGE_WETH_ADDRESS : _exData.destAddr; uint256 tokensBefore = getBalance(destAddr); (success, ) = _exData.offchainData.exchangeAddr.call{value: _exData.offchainData.protocolFee}(_exData.offchainData.callData); uint256 tokensSwaped = 0; if (success) { // get the current balance of the swaped tokens tokensSwaped = getBalance(destAddr) - tokensBefore; require(tokensSwaped > 0, ERR_TOKENS_SWAPED_ZERO); } // returns all funds from src addr, dest addr and eth funds (protocol fee leftovers) sendLeftover(_exData.srcAddr, destAddr, msg.sender); return (success, tokensSwaped); } // solhint-disable-next-line no-empty-blocks receive() external virtual payable {} }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../utils/SafeERC20.sol"; import "../../DS/DSMath.sol"; import "../../auth/AdminAuth.sol"; import "../DFSExchangeHelper.sol"; import "../../interfaces/OffchainWrapperInterface.sol"; import "../../interfaces/TokenInterface.sol"; contract ScpWrapper is OffchainWrapperInterface, DFSExchangeHelper, AdminAuth, DSMath { string public constant ERR_SRC_AMOUNT = "Not enough funds"; string public constant ERR_PROTOCOL_FEE = "Not enough eth for protcol fee"; string public constant ERR_TOKENS_SWAPED_ZERO = "Order success but amount 0"; using SafeERC20 for ERC20; /// @notice Takes order from Scp and returns bool indicating if it is successful /// @param _exData Exchange data /// @param _type Action type (buy or sell) function takeOrder( ExchangeData memory _exData, ActionType _type ) override public payable returns (bool success, uint256) { // check that contract have enough balance for exchange and protocol fee require(getBalance(_exData.srcAddr) >= _exData.srcAmount, ERR_SRC_AMOUNT); require(getBalance(KYBER_ETH_ADDRESS) >= _exData.offchainData.protocolFee, ERR_PROTOCOL_FEE); ERC20(_exData.srcAddr).safeApprove(_exData.offchainData.allowanceTarget, _exData.srcAmount); // write in the exact amount we are selling/buing in an order if (_type == ActionType.SELL) { writeUint256(_exData.offchainData.callData, 36, _exData.srcAmount); } else { uint srcAmount = wdiv(_exData.destAmount, _exData.offchainData.price) + 1; // + 1 so we round up writeUint256(_exData.offchainData.callData, 36, srcAmount); } // we know that it will be eth if dest addr is either weth or eth address destAddr = _exData.destAddr == KYBER_ETH_ADDRESS ? EXCHANGE_WETH_ADDRESS : _exData.destAddr; uint256 tokensBefore = getBalance(destAddr); (success, ) = _exData.offchainData.exchangeAddr.call{value: _exData.offchainData.protocolFee}(_exData.offchainData.callData); uint256 tokensSwaped = 0; if (success) { // get the current balance of the swaped tokens tokensSwaped = getBalance(destAddr) - tokensBefore; require(tokensSwaped > 0, ERR_TOKENS_SWAPED_ZERO); } // returns all funds from src addr, dest addr and eth funds (protocol fee leftovers) sendLeftover(_exData.srcAddr, destAddr, msg.sender); return (success, tokensSwaped); } // solhint-disable-next-line no-empty-blocks receive() external virtual payable {} }
pragma solidity ^0.6.0; import "../DS/DSMath.sol"; import "../DS/DSProxy.sol"; import "../utils/Discount.sol"; import "../interfaces/IFeeRecipient.sol"; import "../interfaces/IAToken.sol"; import "../interfaces/ILendingPool.sol"; import "../interfaces/ILendingPoolAddressesProvider.sol"; import "../interfaces/IPriceOracleGetterAave.sol"; import "../utils/SafeERC20.sol"; import "../utils/BotRegistry.sol"; contract AaveHelper is DSMath { using SafeERC20 for ERC20; IFeeRecipient public constant feeRecipient = IFeeRecipient(0x39C4a92Dc506300c3Ea4c67ca4CA611102ee6F2A); address public constant DISCOUNT_ADDR = 0x1b14E8D511c9A4395425314f849bD737BAF8208F; uint public constant MANUAL_SERVICE_FEE = 400; // 0.25% Fee uint public constant AUTOMATIC_SERVICE_FEE = 333; // 0.3% Fee address public constant BOT_REGISTRY_ADDRESS = 0x637726f8b08a7ABE3aE3aCaB01A80E2d8ddeF77B; address public constant ETH_ADDR = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant AAVE_LENDING_POOL_ADDRESSES = 0x24a42fD28C976A61Df5D00D0599C34c4f90748c8; uint public constant NINETY_NINE_PERCENT_WEI = 990000000000000000; uint16 public constant AAVE_REFERRAL_CODE = 64; /// @param _collateralAddress underlying token address /// @param _user users address function getMaxCollateral(address _collateralAddress, address _user) public view returns (uint256) { address lendingPoolAddressDataProvider = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPoolDataProvider(); address lendingPoolCoreAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPoolCore(); address priceOracleAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getPriceOracle(); uint256 pow10 = 10 ** (18 - _getDecimals(_collateralAddress)); // fetch all needed data (,uint256 totalCollateralETH, uint256 totalBorrowsETH,,uint256 currentLTV,,,) = ILendingPool(lendingPoolAddressDataProvider).calculateUserGlobalData(_user); (,uint256 tokenLTV,,) = ILendingPool(lendingPoolCoreAddress).getReserveConfiguration(_collateralAddress); uint256 collateralPrice = IPriceOracleGetterAave(priceOracleAddress).getAssetPrice(_collateralAddress); uint256 userTokenBalance = ILendingPool(lendingPoolCoreAddress).getUserUnderlyingAssetBalance(_collateralAddress, _user); uint256 userTokenBalanceEth = wmul(userTokenBalance * pow10, collateralPrice); // if borrow is 0, return whole user balance if (totalBorrowsETH == 0) { return userTokenBalance; } uint256 maxCollateralEth = div(sub(mul(currentLTV, totalCollateralETH), mul(totalBorrowsETH, 100)), currentLTV); /// @dev final amount can't be higher than users token balance maxCollateralEth = maxCollateralEth > userTokenBalanceEth ? userTokenBalanceEth : maxCollateralEth; // might happen due to wmul precision if (maxCollateralEth >= totalCollateralETH) { return wdiv(totalCollateralETH, collateralPrice) / pow10; } // get sum of all other reserves multiplied with their liquidation thresholds by reversing formula uint256 a = sub(wmul(currentLTV, totalCollateralETH), wmul(tokenLTV, userTokenBalanceEth)); // add new collateral amount multiplied by its threshold, and then divide with new total collateral uint256 newLiquidationThreshold = wdiv(add(a, wmul(sub(userTokenBalanceEth, maxCollateralEth), tokenLTV)), sub(totalCollateralETH, maxCollateralEth)); // if new threshold is lower than first one, calculate new max collateral with newLiquidationThreshold if (newLiquidationThreshold < currentLTV) { maxCollateralEth = div(sub(mul(newLiquidationThreshold, totalCollateralETH), mul(totalBorrowsETH, 100)), newLiquidationThreshold); maxCollateralEth = maxCollateralEth > userTokenBalanceEth ? userTokenBalanceEth : maxCollateralEth; } return wmul(wdiv(maxCollateralEth, collateralPrice) / pow10, NINETY_NINE_PERCENT_WEI); } /// @param _borrowAddress underlying token address /// @param _user users address function getMaxBorrow(address _borrowAddress, address _user) public view returns (uint256) { address lendingPoolAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPool(); address priceOracleAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getPriceOracle(); (,,,,uint256 availableBorrowsETH,,,) = ILendingPool(lendingPoolAddress).getUserAccountData(_user); uint256 borrowPrice = IPriceOracleGetterAave(priceOracleAddress).getAssetPrice(_borrowAddress); return wmul(wdiv(availableBorrowsETH, borrowPrice) / (10 ** (18 - _getDecimals(_borrowAddress))), NINETY_NINE_PERCENT_WEI); } function getMaxBoost(address _borrowAddress, address _collateralAddress, address _user) public view returns (uint256) { address lendingPoolAddressDataProvider = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPoolDataProvider(); address lendingPoolCoreAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPoolCore(); address priceOracleAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getPriceOracle(); (,uint256 totalCollateralETH, uint256 totalBorrowsETH,,uint256 currentLTV,,,) = ILendingPool(lendingPoolAddressDataProvider).calculateUserGlobalData(_user); (,uint256 tokenLTV,,) = ILendingPool(lendingPoolCoreAddress).getReserveConfiguration(_collateralAddress); totalCollateralETH = div(mul(totalCollateralETH, currentLTV), 100); uint256 availableBorrowsETH = wmul(mul(div(sub(totalCollateralETH, totalBorrowsETH), sub(100, tokenLTV)), 100), NINETY_NINE_PERCENT_WEI); uint256 borrowPrice = IPriceOracleGetterAave(priceOracleAddress).getAssetPrice(_borrowAddress); return wdiv(availableBorrowsETH, borrowPrice) / (10 ** (18 - _getDecimals(_borrowAddress))); } /// @notice Calculates the fee amount /// @param _amount Amount that is converted /// @param _user Actuall user addr not DSProxy /// @param _gasCost Ether amount of gas we are spending for tx /// @param _tokenAddr token addr. of token we are getting for the fee /// @return feeAmount The amount we took for the fee function getFee(uint _amount, address _user, uint _gasCost, address _tokenAddr) internal returns (uint feeAmount) { address priceOracleAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getPriceOracle(); uint fee = MANUAL_SERVICE_FEE; if (BotRegistry(BOT_REGISTRY_ADDRESS).botList(tx.origin)) { fee = AUTOMATIC_SERVICE_FEE; } if (Discount(DISCOUNT_ADDR).isCustomFeeSet(_user)) { fee = Discount(DISCOUNT_ADDR).getCustomServiceFee(_user); } feeAmount = (fee == 0) ? 0 : (_amount / fee); if (_gasCost != 0) { uint256 price = IPriceOracleGetterAave(priceOracleAddress).getAssetPrice(_tokenAddr); _gasCost = wdiv(_gasCost, price) / (10 ** (18 - _getDecimals(_tokenAddr))); feeAmount = add(feeAmount, _gasCost); } // fee can't go over 20% of the whole amount if (feeAmount > (_amount / 5)) { feeAmount = _amount / 5; } address walletAddr = feeRecipient.getFeeAddr(); if (_tokenAddr == ETH_ADDR) { payable(walletAddr).transfer(feeAmount); } else { ERC20(_tokenAddr).safeTransfer(walletAddr, feeAmount); } } /// @notice Calculates the gas cost for transaction /// @param _amount Amount that is converted /// @param _user Actuall user addr not DSProxy /// @param _gasCost Ether amount of gas we are spending for tx /// @param _tokenAddr token addr. of token we are getting for the fee /// @return gasCost The amount we took for the gas cost function getGasCost(uint _amount, address _user, uint _gasCost, address _tokenAddr) internal returns (uint gasCost) { if (_gasCost == 0) return 0; address priceOracleAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getPriceOracle(); uint256 price = IPriceOracleGetterAave(priceOracleAddress).getAssetPrice(_tokenAddr); _gasCost = wmul(_gasCost, price); gasCost = _gasCost; // fee can't go over 20% of the whole amount if (gasCost > (_amount / 5)) { gasCost = _amount / 5; } address walletAddr = feeRecipient.getFeeAddr(); if (_tokenAddr == ETH_ADDR) { payable(walletAddr).transfer(gasCost); } else { ERC20(_tokenAddr).safeTransfer(walletAddr, gasCost); } } /// @notice Returns the owner of the DSProxy that called the contract function getUserAddress() internal view returns (address) { DSProxy proxy = DSProxy(payable(address(this))); return proxy.owner(); } /// @notice Approves token contract to pull underlying tokens from the DSProxy /// @param _tokenAddr Token we are trying to approve /// @param _caller Address which will gain the approval function approveToken(address _tokenAddr, address _caller) internal { if (_tokenAddr != ETH_ADDR) { ERC20(_tokenAddr).safeApprove(_caller, uint256(-1)); } } /// @notice Send specific amount from contract to specific user /// @param _token Token we are trying to send /// @param _user User that should receive funds /// @param _amount Amount that should be sent function sendContractBalance(address _token, address _user, uint _amount) public { if (_amount == 0) return; if (_token == ETH_ADDR) { payable(_user).transfer(_amount); } else { ERC20(_token).safeTransfer(_user, _amount); } } function sendFullContractBalance(address _token, address _user) public { if (_token == ETH_ADDR) { sendContractBalance(_token, _user, address(this).balance); } else { sendContractBalance(_token, _user, ERC20(_token).balanceOf(address(this))); } } function _getDecimals(address _token) internal view returns (uint256) { if (_token == ETH_ADDR) return 18; return ERC20(_token).decimals(); } function isAutomation() internal view returns(bool) { return BotRegistry(BOT_REGISTRY_ADDRESS).botList(tx.origin); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../savings/dydx/ISoloMargin.sol"; import "../../utils/SafeERC20.sol"; import "../../interfaces/TokenInterface.sol"; import "../../DS/DSProxy.sol"; import "../AaveHelper.sol"; import "../../auth/AdminAuth.sol"; import "../../exchange/SaverExchangeCore.sol"; /// @title Import Aave position from account to wallet contract AaveSaverReceiver is AaveHelper, AdminAuth, SaverExchangeCore { using SafeERC20 for ERC20; address public constant AAVE_SAVER_PROXY = 0xCab7ce9148499E0dD8228c3c8cDb9B56Ac2bb57a; address public constant AAVE_BASIC_PROXY = 0xd042D4E9B4186c545648c7FfFe87125c976D110B; address public constant AETH_ADDRESS = 0x3a3A65aAb0dd2A17E3F1947bA16138cd37d08c04; function callFunction( address sender, Account.Info memory account, bytes memory data ) public { ( bytes memory exchangeDataBytes, uint256 gasCost, bool isRepay, uint256 ethAmount, uint256 txValue, address user, address proxy ) = abi.decode(data, (bytes,uint256,bool,uint256,uint256,address,address)); // withdraw eth TokenInterface(WETH_ADDRESS).withdraw(ethAmount); address lendingPoolCoreAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPoolCore(); address lendingPool = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPool(); // deposit eth on behalf of proxy DSProxy(payable(proxy)).execute{value: ethAmount}(AAVE_BASIC_PROXY, abi.encodeWithSignature("deposit(address,uint256)", ETH_ADDR, ethAmount)); bytes memory functionData = packFunctionCall(exchangeDataBytes, gasCost, isRepay); DSProxy(payable(proxy)).execute{value: txValue}(AAVE_SAVER_PROXY, functionData); // withdraw deposited eth DSProxy(payable(proxy)).execute(AAVE_BASIC_PROXY, abi.encodeWithSignature("withdraw(address,address,uint256,bool)", ETH_ADDR, AETH_ADDRESS, ethAmount, false)); // deposit eth, get weth and return to sender TokenInterface(WETH_ADDRESS).deposit.value(address(this).balance)(); ERC20(WETH_ADDRESS).safeTransfer(proxy, ethAmount+2); } function packFunctionCall(bytes memory _exchangeDataBytes, uint256 _gasCost, bool _isRepay) internal returns (bytes memory) { ExchangeData memory exData = unpackExchangeData(_exchangeDataBytes); bytes memory functionData; if (_isRepay) { functionData = abi.encodeWithSignature("repay((address,address,uint256,uint256,uint256,address,address,bytes,uint256),uint256)", exData, _gasCost); } else { functionData = abi.encodeWithSignature("boost((address,address,uint256,uint256,uint256,address,address,bytes,uint256),uint256)", exData, _gasCost); } return functionData; } /// @dev if contract receive eth, convert it to WETH receive() external override payable { // deposit eth and get weth if (msg.sender == owner) { TokenInterface(WETH_ADDRESS).deposit.value(address(this).balance)(); } } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../DS/DSMath.sol"; import "../interfaces/reflexer/IGetSafes.sol"; import "../interfaces/reflexer/ISAFEEngine.sol"; import "../interfaces/reflexer/ISAFEManager.sol"; import "../interfaces/reflexer/IOracleRelayer.sol"; import "../interfaces/reflexer/IMedianOracle.sol"; import "../interfaces/reflexer/ITaxCollector.sol"; contract RaiLoanInfo is DSMath { // mainnet address public constant GET_SAFES_ADDR = 0xdf4BC9aA98cC8eCd90Ba2BEe73aD4a1a9C8d202B; address public constant MANAGER_ADDR = 0xEfe0B4cA532769a3AE758fD82E1426a03A94F185; address public constant SAFE_ENGINE_ADDRESS = 0xCC88a9d330da1133Df3A7bD823B95e52511A6962; address public constant ORACLE_RELAYER_ADDRESS = 0x4ed9C0dCa0479bC64d8f4EB3007126D5791f7851; address public constant MEDIAN_ORACLE_ADDRESS = 0x12A5E1c81B10B264A575930aEae80681DDF595fe; address public constant TAX_COLLECTOR_ADDRESS = 0xcDB05aEda142a1B0D6044C09C64e4226c1a281EB; // kovan // address public constant GET_SAFES_ADDR = 0x702dcf4a8C3bBBd243477D5704fc45F2762D3826; // address public constant MANAGER_ADDR = 0x807C8eCb73d9c8203d2b1369E678098B9370F2EA; // address public constant SAFE_ENGINE_ADDRESS = 0x7f63fE955fFF8EA474d990f1Fc8979f2C650edbE; // address public constant ORACLE_RELAYER_ADDRESS = 0xE5Ae4E49bEA485B5E5172EE6b1F99243cB15225c; // address public constant MEDIAN_ORACLE_ADDRESS = 0x82bEAd00751EFA3286c9Dd17e4Ea2570916B3944; // address public constant TAX_COLLECTOR_ADDRESS = 0xc1a94C5ad9FCD79b03F79B34d8C0B0C8192fdc16; struct SafeInfo { uint256 safeId; uint256 coll; uint256 debt; address safeAddr; bytes32 collType; } struct CollInfo { uint256 debtCeiling; uint256 currDebtAmount; uint256 currRate; uint256 dust; uint256 safetyPrice; uint256 liqPrice; uint256 assetPrice; uint256 liqRatio; uint256 stabilityFee; } struct RaiInfo { uint256 redemptionPrice; uint256 currRaiPrice; uint256 redemptionRate; } function getCollateralTypeInfo(bytes32 _collType) public returns (CollInfo memory collInfo) { ( uint256 debtAmount, uint256 accumulatedRates, uint256 safetyPrice, uint256 debtCeiling, uint256 debtFloor, uint256 liquidationPrice ) = ISAFEEngine(SAFE_ENGINE_ADDRESS).collateralTypes(_collType); (, uint liqRatio) = IOracleRelayer(ORACLE_RELAYER_ADDRESS).collateralTypes(_collType); (uint stabilityFee,) = ITaxCollector(TAX_COLLECTOR_ADDRESS).collateralTypes(_collType); collInfo = CollInfo({ debtCeiling: debtCeiling, currDebtAmount: debtAmount, currRate: accumulatedRates, dust: debtFloor, safetyPrice: safetyPrice, liqPrice: liquidationPrice, assetPrice: getPrice(_collType), liqRatio: liqRatio, stabilityFee: stabilityFee }); } function getCollAndRaiInfo(bytes32 _collType) public returns (CollInfo memory collInfo, RaiInfo memory raiInfo) { collInfo = getCollateralTypeInfo(_collType); raiInfo = getRaiInfo(); } function getPrice(bytes32 _collType) public returns (uint256) { (, uint256 safetyCRatio) = IOracleRelayer(ORACLE_RELAYER_ADDRESS).collateralTypes(_collType); (, , uint256 safetyPrice, , , ) = ISAFEEngine(SAFE_ENGINE_ADDRESS).collateralTypes(_collType); uint256 redemptionPrice = IOracleRelayer(ORACLE_RELAYER_ADDRESS).redemptionPrice(); return rmul(rmul(safetyPrice, redemptionPrice), safetyCRatio); } function getRaiInfo() public returns (RaiInfo memory raiInfo) { raiInfo = RaiInfo({ redemptionPrice: IOracleRelayer(ORACLE_RELAYER_ADDRESS).redemptionPrice(), currRaiPrice: IMedianOracle(MEDIAN_ORACLE_ADDRESS).read(), redemptionRate: IOracleRelayer(ORACLE_RELAYER_ADDRESS).redemptionRate() }); } function getSafeInfo(uint256 _safeId) public view returns (SafeInfo memory safeInfo) { address safeAddr = ISAFEManager(MANAGER_ADDR).safes(_safeId); bytes32 collType = ISAFEManager(MANAGER_ADDR).collateralTypes(_safeId); (uint256 coll, uint256 debt) = ISAFEEngine(SAFE_ENGINE_ADDRESS).safes(collType, safeAddr); safeInfo = SafeInfo({ safeId: _safeId, coll: coll, debt: debt, safeAddr: safeAddr, collType: collType }); } function getUserSafes(address _user) public view returns ( uint256[] memory ids, address[] memory safes, bytes32[] memory collateralTypes ) { return IGetSafes(GET_SAFES_ADDR).getSafesAsc(MANAGER_ADDR, _user); } function getUserSafesFullInfo(address _user) public view returns (SafeInfo[] memory safeInfos) { (uint256[] memory ids, , ) = getUserSafes(_user); safeInfos = new SafeInfo[](ids.length); for (uint256 i = 0; i < ids.length; ++i) { safeInfos[i] = getSafeInfo(ids[i]); } } function getFullInfo(address _user, bytes32 _collType) public returns ( CollInfo memory collInfo, RaiInfo memory raiInfo, SafeInfo[] memory safeInfos ) { collInfo = getCollateralTypeInfo(_collType); raiInfo = getRaiInfo(); safeInfos = getUserSafesFullInfo(_user); } }
pragma solidity ^0.6.0; abstract contract IGetSafes { function getSafesAsc(address manager, address guy) external virtual view returns (uint[] memory ids, address[] memory safes, bytes32[] memory collateralTypes); function getSafesDesc(address manager, address guy) external virtual view returns (uint[] memory ids, address[] memory safes, bytes32[] memory collateralTypes); }
pragma solidity ^0.6.0; abstract contract IMedianOracle { function read() external virtual view returns (uint256); }
pragma solidity ^0.6.0; import "./DSProxy.sol"; abstract contract DSProxyFactoryInterface { function build(address owner) public virtual returns (DSProxy proxy); }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../../utils/SafeERC20.sol"; import "../../../interfaces/TokenInterface.sol"; import "../../../DS/DSProxy.sol"; import "../../AaveHelperV2.sol"; import "../../../auth/AdminAuth.sol"; import "../../../exchangeV3/DFSExchangeCore.sol"; /// @title Import Aave position from account to wallet contract AaveSaverReceiverOV2 is AaveHelperV2, AdminAuth, DFSExchangeCore { using SafeERC20 for ERC20; address public constant AAVE_BASIC_PROXY = 0xc17c8eB12Ba24D62E69fd57cbd504EEf418867f9; function boost(ExchangeData memory _exchangeData, address _market, uint256 _gasCost, address _proxy) internal { (, uint swappedAmount) = _sell(_exchangeData); address user = DSAuth(_proxy).owner(); swappedAmount -= getGasCost(ILendingPoolAddressesProviderV2(_market).getPriceOracle(), swappedAmount, user, _gasCost, _exchangeData.destAddr); // if its eth we need to send it to the basic proxy, if not, we need to approve users proxy to pull tokens uint256 msgValue = 0; address token = _exchangeData.destAddr; // sell always return eth, but deposit differentiate eth vs weth, so we change weth address to eth when we are depoisting if (_exchangeData.destAddr == ETH_ADDR || _exchangeData.destAddr == WETH_ADDRESS) { msgValue = swappedAmount; token = ETH_ADDR; } else { ERC20(_exchangeData.destAddr).safeApprove(_proxy, swappedAmount); } // deposit collateral on behalf of user DSProxy(payable(_proxy)).execute{value: msgValue}( AAVE_BASIC_PROXY, abi.encodeWithSignature( "deposit(address,address,uint256)", _market, token, swappedAmount ) ); } function repay(ExchangeData memory _exchangeData, address _market, uint256 _gasCost, address _proxy, uint256 _rateMode, uint _aaveFlashlLoanFee) internal { // we will withdraw exactly the srcAmount, as fee we keep before selling uint valueToWithdraw = _exchangeData.srcAmount; // take out the fee wee need to pay and sell the rest _exchangeData.srcAmount = _exchangeData.srcAmount - _aaveFlashlLoanFee; (, uint swappedAmount) = _sell(_exchangeData); // set protocol fee left to eth balance of this address // but if destAddr is eth or weth, this also includes that value so we need to substract it uint protocolFeeLeft = address(this).balance; address user = DSAuth(_proxy).owner(); swappedAmount -= getGasCost(ILendingPoolAddressesProviderV2(_market).getPriceOracle(), swappedAmount, user, _gasCost, _exchangeData.destAddr); // if its eth we need to send it to the basic proxy, if not, we need to approve basic proxy to pull tokens uint256 msgValue = 0; if (_exchangeData.destAddr == ETH_ADDR || _exchangeData.destAddr == WETH_ADDRESS) { protocolFeeLeft -= swappedAmount; msgValue = swappedAmount; } else { ERC20(_exchangeData.destAddr).safeApprove(_proxy, swappedAmount); } // first payback the loan with swapped amount DSProxy(payable(_proxy)).execute{value: msgValue}( AAVE_BASIC_PROXY, abi.encodeWithSignature( "payback(address,address,uint256,uint256)", _market, _exchangeData.destAddr, swappedAmount, _rateMode ) ); // if some tokens left after payback (full repay) we need to return it back to the proxy owner require(user != address(0)); // be sure that we fetched the user correctly if (_exchangeData.destAddr == ETH_ADDR || _exchangeData.destAddr == WETH_ADDRESS) { // keep protocol fee for tx.origin, but the rest of the balance return to the user payable(user).transfer(address(this).balance - protocolFeeLeft); } else { // in case its a token, just return whole value back to the user, as protocol fee is always in eth uint amount = ERC20(_exchangeData.destAddr).balanceOf(address(this)); ERC20(_exchangeData.destAddr).safeTransfer(user, amount); } // pull the amount we flash loaned in collateral to be able to payback the debt DSProxy(payable(_proxy)).execute(AAVE_BASIC_PROXY, abi.encodeWithSignature("withdraw(address,address,uint256)", _market, _exchangeData.srcAddr, valueToWithdraw)); } function executeOperation( address[] calldata, uint256[] calldata amounts, uint256[] calldata premiums, address initiator, bytes calldata params ) public returns (bool) { ( bytes memory exchangeDataBytes, address market, uint256 gasCost, uint256 rateMode, bool isRepay, address proxy ) = abi.decode(params, (bytes,address,uint256,uint256,bool,address)); address lendingPool = ILendingPoolAddressesProviderV2(market).getLendingPool(); require(msg.sender == lendingPool, "Callbacks only allowed from Aave"); require(initiator == proxy, "initiator isn't proxy"); ExchangeData memory exData = unpackExchangeData(exchangeDataBytes); exData.user = DSAuth(proxy).owner(); exData.dfsFeeDivider = MANUAL_SERVICE_FEE; if (BotRegistry(BOT_REGISTRY_ADDRESS).botList(tx.origin)) { exData.dfsFeeDivider = AUTOMATIC_SERVICE_FEE; } // this is to avoid stack too deep uint fee = premiums[0]; uint totalValueToReturn = exData.srcAmount + fee; // if its repay, we are using regular flash loan and payback the premiums if (isRepay) { repay(exData, market, gasCost, proxy, rateMode, fee); address token = exData.srcAddr; if (token == ETH_ADDR || token == WETH_ADDRESS) { // deposit eth, get weth and return to sender TokenInterface(WETH_ADDRESS).deposit.value(totalValueToReturn)(); token = WETH_ADDRESS; } ERC20(token).safeApprove(lendingPool, totalValueToReturn); } else { boost(exData, market, gasCost, proxy); } tx.origin.transfer(address(this).balance); return true; } /// @dev allow contract to receive eth from sell receive() external override payable {} }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../savings/dydx/ISoloMargin.sol"; import "../../utils/SafeERC20.sol"; import "../../interfaces/TokenInterface.sol"; import "../../DS/DSProxy.sol"; import "../AaveHelperV2.sol"; import "../../auth/AdminAuth.sol"; // weth->eth // deposit eth for users proxy // borrow users token from proxy // repay on behalf of user // pull user supply // take eth amount from supply (if needed more, borrow it?) // return eth to sender /// @title Import Aave position from account to wallet contract AaveImportV2 is AaveHelperV2, AdminAuth { using SafeERC20 for ERC20; address public constant BASIC_PROXY = 0xc17c8eB12Ba24D62E69fd57cbd504EEf418867f9; address public constant PULL_TOKENS_PROXY = 0x45431b79F783e0BF0fe7eF32D06A3e061780bfc4; function callFunction( address, Account.Info memory, bytes memory data ) public { ( address market, address collateralToken, address borrowToken, uint256 ethAmount, address proxy ) = abi.decode(data, (address,address,address,uint256,address)); address user = DSProxy(payable(proxy)).owner(); // withdraw eth TokenInterface(WETH_ADDRESS).withdraw(ethAmount); IAaveProtocolDataProviderV2 dataProvider = getDataProvider(market); uint256 globalBorrowAmountStable = 0; uint256 globalBorrowAmountVariable = 0; { // avoid stack too deep // deposit eth on behalf of proxy DSProxy(payable(proxy)).execute{value: ethAmount}(BASIC_PROXY, abi.encodeWithSignature("deposit(address,address,uint256)", market, ETH_ADDR, ethAmount)); // borrow needed amount to repay users borrow (, uint256 borrowsStable, uint256 borrowsVariable,,,,,,) = dataProvider.getUserReserveData(borrowToken, user); if (borrowsStable > 0) { DSProxy(payable(proxy)).execute(BASIC_PROXY, abi.encodeWithSignature("borrow(address,address,uint256,uint256)", market, borrowToken, borrowsStable, STABLE_ID)); globalBorrowAmountStable = borrowsStable; } if (borrowsVariable > 0) { DSProxy(payable(proxy)).execute(BASIC_PROXY, abi.encodeWithSignature("borrow(address,address,uint256,uint256)", market, borrowToken, borrowsVariable, VARIABLE_ID)); globalBorrowAmountVariable = borrowsVariable; } } if (globalBorrowAmountVariable > 0) { paybackOnBehalf(market, proxy, globalBorrowAmountVariable, borrowToken, user, VARIABLE_ID); } if (globalBorrowAmountStable > 0) { paybackOnBehalf(market, proxy, globalBorrowAmountStable, borrowToken, user, STABLE_ID); } (address aToken,,) = dataProvider.getReserveTokensAddresses(collateralToken); // pull coll tokens DSProxy(payable(proxy)).execute(PULL_TOKENS_PROXY, abi.encodeWithSignature("pullTokens(address,uint256)", aToken, ERC20(aToken).balanceOf(user))); // enable as collateral DSProxy(payable(proxy)).execute(BASIC_PROXY, abi.encodeWithSignature("setUserUseReserveAsCollateralIfNeeded(address,address)", market, collateralToken)); // withdraw deposited eth DSProxy(payable(proxy)).execute(BASIC_PROXY, abi.encodeWithSignature("withdraw(address,address,uint256)", market, ETH_ADDR, ethAmount)); // deposit eth, get weth and return to sender TokenInterface(WETH_ADDRESS).deposit{value: (address(this).balance)}(); ERC20(WETH_ADDRESS).safeTransfer(proxy, ethAmount+2); } function paybackOnBehalf(address _market, address _proxy, uint _amount, address _token, address _onBehalf, uint _rateMode) internal { // payback on behalf of user if (_token != ETH_ADDR) { ERC20(_token).safeApprove(_proxy, _amount); DSProxy(payable(_proxy)).execute(BASIC_PROXY, abi.encodeWithSignature("paybackOnBehalf(address,address,uint256,uint256,address)", _market, _token, _amount, _rateMode, _onBehalf)); } else { DSProxy(payable(_proxy)).execute{value: _amount}(BASIC_PROXY, abi.encodeWithSignature("paybackOnBehalf(address,address,uint256,uint256,address)", _market, _token, _amount, _rateMode, _onBehalf)); } } /// @dev if contract receive eth, convert it to WETH receive() external payable { // deposit eth and get weth if (msg.sender == owner) { TokenInterface(WETH_ADDRESS).deposit{value: (address(this).balance)}(); } } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../savings/dydx/ISoloMargin.sol"; import "../../utils/SafeERC20.sol"; import "../../interfaces/TokenInterface.sol"; import "../../DS/DSProxy.sol"; import "../AaveHelper.sol"; import "../../auth/AdminAuth.sol"; // weth->eth // deposit eth for users proxy // borrow users token from proxy // repay on behalf of user // pull user supply // take eth amount from supply (if needed more, borrow it?) // return eth to sender /// @title Import Aave position from account to wallet contract AaveImport is AaveHelper, AdminAuth { using SafeERC20 for ERC20; address public constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address public constant BASIC_PROXY = 0xF499FB2feb3351aEA373723a6A0e8F6BE6fBF616; address public constant AETH_ADDRESS = 0x3a3A65aAb0dd2A17E3F1947bA16138cd37d08c04; address public constant PULL_TOKENS_PROXY = 0x45431b79F783e0BF0fe7eF32D06A3e061780bfc4; function callFunction( address, Account.Info memory, bytes memory data ) public { ( address collateralToken, address borrowToken, uint256 ethAmount, address proxy ) = abi.decode(data, (address,address,uint256,address)); address user = DSProxy(payable(proxy)).owner(); // withdraw eth TokenInterface(WETH_ADDRESS).withdraw(ethAmount); address lendingPoolCoreAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPoolCore(); address lendingPool = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPool(); address aCollateralToken = ILendingPool(lendingPoolCoreAddress).getReserveATokenAddress(collateralToken); address aBorrowToken = ILendingPool(lendingPoolCoreAddress).getReserveATokenAddress(borrowToken); uint256 globalBorrowAmount = 0; { // avoid stack too deep // deposit eth on behalf of proxy DSProxy(payable(proxy)).execute{value: ethAmount}(BASIC_PROXY, abi.encodeWithSignature("deposit(address,uint256)", ETH_ADDR, ethAmount)); // borrow needed amount to repay users borrow (,uint256 borrowAmount,,uint256 borrowRateMode,,,uint256 originationFee,,,) = ILendingPool(lendingPool).getUserReserveData(borrowToken, user); borrowAmount += originationFee; DSProxy(payable(proxy)).execute(BASIC_PROXY, abi.encodeWithSignature("borrow(address,uint256,uint256)", borrowToken, borrowAmount, borrowRateMode)); globalBorrowAmount = borrowAmount; } // payback on behalf of user if (borrowToken != ETH_ADDR) { ERC20(borrowToken).safeApprove(proxy, globalBorrowAmount); DSProxy(payable(proxy)).execute(BASIC_PROXY, abi.encodeWithSignature("paybackOnBehalf(address,address,uint256,bool,address)", borrowToken, aBorrowToken, 0, true, user)); } else { DSProxy(payable(proxy)).execute{value: globalBorrowAmount}(BASIC_PROXY, abi.encodeWithSignature("paybackOnBehalf(address,address,uint256,bool,address)", borrowToken, aBorrowToken, 0, true, user)); } // pull coll tokens DSProxy(payable(proxy)).execute(PULL_TOKENS_PROXY, abi.encodeWithSignature("pullTokens(address,uint256)", aCollateralToken, ERC20(aCollateralToken).balanceOf(user))); // enable as collateral DSProxy(payable(proxy)).execute(BASIC_PROXY, abi.encodeWithSignature("setUserUseReserveAsCollateralIfNeeded(address)", collateralToken)); // withdraw deposited eth DSProxy(payable(proxy)).execute(BASIC_PROXY, abi.encodeWithSignature("withdraw(address,address,uint256,bool)", ETH_ADDR, AETH_ADDRESS, ethAmount, false)); // deposit eth, get weth and return to sender TokenInterface(WETH_ADDRESS).deposit{value: (address(this).balance)}(); ERC20(WETH_ADDRESS).safeTransfer(proxy, ethAmount+2); } /// @dev if contract receive eth, convert it to WETH receive() external payable { // deposit eth and get weth if (msg.sender == owner) { TokenInterface(WETH_ADDRESS).deposit{value: (address(this).balance)}(); } } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../AaveHelper.sol"; import "../../exchangeV3/DFSExchangeCore.sol"; import "../../interfaces/IAToken.sol"; import "../../interfaces/ILendingPool.sol"; import "../../loggers/DefisaverLogger.sol"; import "../../utils/GasBurner.sol"; contract AaveSaverProxy is GasBurner, DFSExchangeCore, AaveHelper { address public constant DEFISAVER_LOGGER = 0x5c55B921f590a89C1Ebe84dF170E655a82b62126; uint public constant VARIABLE_RATE = 2; function repay(ExchangeData memory _data, uint _gasCost) public payable burnGas(20) { address lendingPoolCore = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPoolCore(); address lendingPool = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPool(); address payable user = payable(getUserAddress()); // redeem collateral address aTokenCollateral = ILendingPool(lendingPoolCore).getReserveATokenAddress(_data.srcAddr); // uint256 maxCollateral = IAToken(aTokenCollateral).balanceOf(address(this)); // don't swap more than maxCollateral // _data.srcAmount = _data.srcAmount > maxCollateral ? maxCollateral : _data.srcAmount; IAToken(aTokenCollateral).redeem(_data.srcAmount); uint256 destAmount = _data.srcAmount; if (_data.srcAddr != _data.destAddr) { _data.dfsFeeDivider = isAutomation() ? AUTOMATIC_SERVICE_FEE : MANUAL_SERVICE_FEE; _data.user = user; // swap (, destAmount) = _sell(_data); destAmount -= getGasCost(destAmount, user, _gasCost, _data.destAddr); } else { destAmount -= getGasCost(destAmount, user, _gasCost, _data.destAddr); } // payback if (_data.destAddr == ETH_ADDR) { ILendingPool(lendingPool).repay{value: destAmount}(_data.destAddr, destAmount, payable(address(this))); } else { approveToken(_data.destAddr, lendingPoolCore); ILendingPool(lendingPool).repay(_data.destAddr, destAmount, payable(address(this))); } // first return 0x fee to msg.sender as it is the address that actually sent 0x fee sendContractBalance(ETH_ADDR, tx.origin, min(address(this).balance, msg.value)); // send all leftovers from dest addr to proxy owner sendFullContractBalance(_data.destAddr, user); DefisaverLogger(DEFISAVER_LOGGER).Log(address(this), msg.sender, "AaveRepay", abi.encode(_data.srcAddr, _data.destAddr, _data.srcAmount, destAmount)); } function boost(ExchangeData memory _data, uint _gasCost) public payable burnGas(20) { address lendingPoolCore = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPoolCore(); address lendingPool = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPool(); (,,,uint256 borrowRateMode,,,,,,bool collateralEnabled) = ILendingPool(lendingPool).getUserReserveData(_data.destAddr, address(this)); address payable user = payable(getUserAddress()); // skipping this check as its too expensive // uint256 maxBorrow = getMaxBoost(_data.srcAddr, _data.destAddr, address(this)); // _data.srcAmount = _data.srcAmount > maxBorrow ? maxBorrow : _data.srcAmount; // borrow amount ILendingPool(lendingPool).borrow(_data.srcAddr, _data.srcAmount, borrowRateMode == 0 ? VARIABLE_RATE : borrowRateMode, AAVE_REFERRAL_CODE); uint256 destAmount; if (_data.destAddr != _data.srcAddr) { _data.dfsFeeDivider = isAutomation() ? AUTOMATIC_SERVICE_FEE : MANUAL_SERVICE_FEE; _data.user = user; // swap (, destAmount) = _sell(_data); destAmount -= getGasCost(_data.destAmount, user, _gasCost, _data.destAddr); } else { destAmount = _data.srcAmount; destAmount -= getGasCost(_data.destAmount, user, _gasCost, _data.destAddr); } if (_data.destAddr == ETH_ADDR) { ILendingPool(lendingPool).deposit{value: destAmount}(_data.destAddr, destAmount, AAVE_REFERRAL_CODE); } else { approveToken(_data.destAddr, lendingPoolCore); ILendingPool(lendingPool).deposit(_data.destAddr, destAmount, AAVE_REFERRAL_CODE); } if (!collateralEnabled) { ILendingPool(lendingPool).setUserUseReserveAsCollateral(_data.destAddr, true); } // returning to msg.sender as it is the address that actually sent 0x fee sendContractBalance(ETH_ADDR, tx.origin, min(address(this).balance, msg.value)); // send all leftovers from dest addr to proxy owner sendFullContractBalance(_data.destAddr, user); DefisaverLogger(DEFISAVER_LOGGER).Log(address(this), msg.sender, "AaveBoost", abi.encode(_data.srcAddr, _data.destAddr, _data.srcAmount, destAmount)); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../AaveHelperV2.sol"; import "../../exchangeV3/DFSExchangeCore.sol"; import "../../interfaces/IAToken.sol"; import "../../interfaces/TokenInterface.sol"; import "../../loggers/DefisaverLogger.sol"; import "../../utils/GasBurner.sol"; contract AaveSaverProxyV2 is DFSExchangeCore, AaveHelperV2, GasBurner { address public constant DEFISAVER_LOGGER = 0x5c55B921f590a89C1Ebe84dF170E655a82b62126; function repay(address _market, ExchangeData memory _data, uint _rateMode, uint _gasCost) public payable burnGas(20) { address lendingPool = ILendingPoolAddressesProviderV2(_market).getLendingPool(); IAaveProtocolDataProviderV2 dataProvider = getDataProvider(_market); address payable user = payable(getUserAddress()); ILendingPoolV2(lendingPool).withdraw(_data.srcAddr, _data.srcAmount, address(this)); uint256 destAmount = _data.srcAmount; if (_data.srcAddr != _data.destAddr) { _data.user = user; _data.dfsFeeDivider = MANUAL_SERVICE_FEE; if (BotRegistry(BOT_REGISTRY_ADDRESS).botList(tx.origin)) { _data.dfsFeeDivider = AUTOMATIC_SERVICE_FEE; } // swap (, destAmount) = _sell(_data); } // take gas cost at the end destAmount -= getGasCost(ILendingPoolAddressesProviderV2(_market).getPriceOracle(), destAmount, user, _gasCost, _data.destAddr); // payback if (_data.destAddr == WETH_ADDRESS) { TokenInterface(WETH_ADDRESS).deposit.value(destAmount)(); } approveToken(_data.destAddr, lendingPool); // if destAmount higher than borrow repay whole debt uint borrow; if (_rateMode == STABLE_ID) { (,borrow,,,,,,,) = dataProvider.getUserReserveData(_data.destAddr, address(this)); } else { (,,borrow,,,,,,) = dataProvider.getUserReserveData(_data.destAddr, address(this)); } ILendingPoolV2(lendingPool).repay(_data.destAddr, destAmount > borrow ? borrow : destAmount, _rateMode, payable(address(this))); // first return 0x fee to tx.origin as it is the address that actually sent 0x fee sendContractBalance(ETH_ADDR, tx.origin, min(address(this).balance, msg.value)); // send all leftovers from dest addr to proxy owner sendFullContractBalance(_data.destAddr, user); DefisaverLogger(DEFISAVER_LOGGER).Log(address(this), msg.sender, "AaveV2Repay", abi.encode(_data.srcAddr, _data.destAddr, _data.srcAmount, destAmount)); } function boost(address _market, ExchangeData memory _data, uint _rateMode, uint _gasCost) public payable burnGas(20) { address lendingPool = ILendingPoolAddressesProviderV2(_market).getLendingPool(); IAaveProtocolDataProviderV2 dataProvider = getDataProvider(_market); address payable user = payable(getUserAddress()); // borrow amount ILendingPoolV2(lendingPool).borrow(_data.srcAddr, _data.srcAmount, _rateMode, AAVE_REFERRAL_CODE, address(this)); // take gas cost at the beginning _data.srcAmount -= getGasCost(ILendingPoolAddressesProviderV2(_market).getPriceOracle(), _data.srcAmount, user, _gasCost, _data.srcAddr); uint256 destAmount; if (_data.destAddr != _data.srcAddr) { _data.user = user; _data.dfsFeeDivider = MANUAL_SERVICE_FEE; if (BotRegistry(BOT_REGISTRY_ADDRESS).botList(tx.origin)) { _data.dfsFeeDivider = AUTOMATIC_SERVICE_FEE; } (, destAmount) = _sell(_data); } else { destAmount = _data.srcAmount; } if (_data.destAddr == WETH_ADDRESS) { TokenInterface(WETH_ADDRESS).deposit.value(destAmount)(); } approveToken(_data.destAddr, lendingPool); ILendingPoolV2(lendingPool).deposit(_data.destAddr, destAmount, address(this), AAVE_REFERRAL_CODE); (,,,,,,,,bool collateralEnabled) = dataProvider.getUserReserveData(_data.destAddr, address(this)); if (!collateralEnabled) { ILendingPoolV2(lendingPool).setUserUseReserveAsCollateral(_data.destAddr, true); } // returning to msg.sender as it is the address that actually sent 0x fee sendContractBalance(ETH_ADDR, tx.origin, min(address(this).balance, msg.value)); // send all leftovers from dest addr to proxy owner sendFullContractBalance(_data.destAddr, user); DefisaverLogger(DEFISAVER_LOGGER).Log(address(this), msg.sender, "AaveV2Boost", abi.encode(_data.srcAddr, _data.destAddr, _data.srcAmount, destAmount)); } }
pragma solidity ^0.6.0; import "../utils/GasBurner.sol"; import "../interfaces/TokenInterface.sol"; import "../interfaces/IAToken.sol"; import "../interfaces/ILendingPoolV2.sol"; import "./AaveHelperV2.sol"; import "../utils/SafeERC20.sol"; /// @title Basic compound interactions through the DSProxy contract AaveBasicProxyV2 is GasBurner, AaveHelperV2 { using SafeERC20 for ERC20; /// @notice User deposits tokens to the Aave protocol /// @dev User needs to approve the DSProxy to pull the _tokenAddr tokens /// @param _market address provider for specific market /// @param _tokenAddr The address of the token to be deposited /// @param _amount Amount of tokens to be deposited function deposit(address _market, address _tokenAddr, uint256 _amount) public burnGas(5) payable { address lendingPool = ILendingPoolAddressesProviderV2(_market).getLendingPool(); if (_tokenAddr == ETH_ADDR) { require(msg.value == _amount); TokenInterface(WETH_ADDRESS).deposit{value: _amount}(); _tokenAddr = WETH_ADDRESS; } else { ERC20(_tokenAddr).safeTransferFrom(msg.sender, address(this), _amount); } approveToken(_tokenAddr, lendingPool); ILendingPoolV2(lendingPool).deposit(_tokenAddr, _amount, address(this), AAVE_REFERRAL_CODE); setUserUseReserveAsCollateralIfNeeded(_market, _tokenAddr); } /// @notice User withdraws tokens from the Aave protocol /// @param _market address provider for specific market /// @param _tokenAddr The address of the token to be withdrawn /// @param _amount Amount of tokens to be withdrawn -> send -1 for whole amount function withdraw(address _market, address _tokenAddr, uint256 _amount) public burnGas(8) { address lendingPool = ILendingPoolAddressesProviderV2(_market).getLendingPool(); _tokenAddr = changeToWeth(_tokenAddr); if (_tokenAddr == WETH_ADDRESS) { // if weth, pull to proxy and return ETH to user ILendingPoolV2(lendingPool).withdraw(_tokenAddr, _amount, address(this)); // needs to use balance of in case that amount is -1 for whole debt TokenInterface(WETH_ADDRESS).withdraw(TokenInterface(WETH_ADDRESS).balanceOf(address(this))); msg.sender.transfer(address(this).balance); } else { // if not eth send directly to user ILendingPoolV2(lendingPool).withdraw(_tokenAddr, _amount, msg.sender); } } /// @notice User borrows tokens to the Aave protocol /// @param _market address provider for specific market /// @param _tokenAddr The address of the token to be borrowed /// @param _amount Amount of tokens to be borrowed /// @param _type Send 1 for stable rate and 2 for variable function borrow(address _market, address _tokenAddr, uint256 _amount, uint256 _type) public burnGas(8) { address lendingPool = ILendingPoolAddressesProviderV2(_market).getLendingPool(); _tokenAddr = changeToWeth(_tokenAddr); ILendingPoolV2(lendingPool).borrow(_tokenAddr, _amount, _type, AAVE_REFERRAL_CODE, address(this)); if (_tokenAddr == WETH_ADDRESS) { // we do this so the user gets eth instead of weth TokenInterface(WETH_ADDRESS).withdraw(_amount); _tokenAddr = ETH_ADDR; } withdrawTokens(_tokenAddr); } /// @dev User needs to approve the DSProxy to pull the _tokenAddr tokens /// @notice User paybacks tokens to the Aave protocol /// @param _market address provider for specific market /// @param _tokenAddr The address of the token to be paybacked /// @param _amount Amount of tokens to be payed back function payback(address _market, address _tokenAddr, uint256 _amount, uint256 _rateMode) public burnGas(3) payable { address lendingPool = ILendingPoolAddressesProviderV2(_market).getLendingPool(); _tokenAddr = changeToWeth(_tokenAddr); if (_tokenAddr == WETH_ADDRESS) { TokenInterface(WETH_ADDRESS).deposit{value: msg.value}(); } else { uint amountToPull = min(_amount, ERC20(_tokenAddr).balanceOf(msg.sender)); ERC20(_tokenAddr).safeTransferFrom(msg.sender, address(this), amountToPull); } approveToken(_tokenAddr, lendingPool); ILendingPoolV2(lendingPool).repay(_tokenAddr, _amount, _rateMode, payable(address(this))); if (_tokenAddr == WETH_ADDRESS) { // Pull if we have any eth leftover TokenInterface(WETH_ADDRESS).withdraw(ERC20(WETH_ADDRESS).balanceOf(address(this))); _tokenAddr = ETH_ADDR; } withdrawTokens(_tokenAddr); } /// @dev User needs to approve the DSProxy to pull the _tokenAddr tokens /// @notice User paybacks tokens to the Aave protocol /// @param _market address provider for specific market /// @param _tokenAddr The address of the token to be paybacked /// @param _amount Amount of tokens to be payed back function paybackOnBehalf(address _market, address _tokenAddr, uint256 _amount, uint256 _rateMode, address _onBehalf) public burnGas(3) payable { address lendingPool = ILendingPoolAddressesProviderV2(_market).getLendingPool(); _tokenAddr = changeToWeth(_tokenAddr); if (_tokenAddr == WETH_ADDRESS) { TokenInterface(WETH_ADDRESS).deposit{value: msg.value}(); } else { uint amountToPull = min(_amount, ERC20(_tokenAddr).allowance(msg.sender, address(this))); ERC20(_tokenAddr).safeTransferFrom(msg.sender, address(this), amountToPull); } approveToken(_tokenAddr, lendingPool); ILendingPoolV2(lendingPool).repay(_tokenAddr, _amount, _rateMode, _onBehalf); if (_tokenAddr == WETH_ADDRESS) { // we do this so the user gets eth instead of weth TokenInterface(WETH_ADDRESS).withdraw(_amount); _tokenAddr = ETH_ADDR; } withdrawTokens(_tokenAddr); } /// @notice Helper method to withdraw tokens from the DSProxy /// @param _tokenAddr Address of the token to be withdrawn function withdrawTokens(address _tokenAddr) public { uint256 amount = _tokenAddr == ETH_ADDR ? address(this).balance : ERC20(_tokenAddr).balanceOf(address(this)); if (amount > 0) { if (_tokenAddr != ETH_ADDR) { ERC20(_tokenAddr).safeTransfer(msg.sender, amount); } else { msg.sender.transfer(amount); } } } function setUserUseReserveAsCollateralIfNeeded(address _market, address _tokenAddr) public { address lendingPool = ILendingPoolAddressesProviderV2(_market).getLendingPool(); IAaveProtocolDataProviderV2 dataProvider = getDataProvider(_market); (,,,,,,,,bool collateralEnabled) = dataProvider.getUserReserveData(_tokenAddr, address(this)); if (!collateralEnabled) { ILendingPoolV2(lendingPool).setUserUseReserveAsCollateral(_tokenAddr, true); } } function setUserUseReserveAsCollateral(address _market, address _tokenAddr, bool _true) public { address lendingPool = ILendingPoolAddressesProviderV2(_market).getLendingPool(); ILendingPoolV2(lendingPool).setUserUseReserveAsCollateral(_tokenAddr, _true); } // stable = 1, variable = 2 function swapBorrowRateMode(address _market, address _reserve, uint _rateMode) public { address lendingPool = ILendingPoolAddressesProviderV2(_market).getLendingPool(); ILendingPoolV2(lendingPool).swapBorrowRateMode(_reserve, _rateMode); } function changeToWeth(address _token) private view returns(address) { if (_token == ETH_ADDR) { return WETH_ADDRESS; } return _token; } // solhint-disable-next-line no-empty-blocks receive() external virtual payable {} }
pragma solidity ^0.6.0; import "./AaveHelperV2.sol"; import "../interfaces/ILendingPoolV2.sol"; contract AaveSafetyRatioV2 is AaveHelperV2 { function getSafetyRatio(address _market, address _user) public view returns(uint256) { ILendingPoolV2 lendingPool = ILendingPoolV2(ILendingPoolAddressesProviderV2(_market).getLendingPool()); (,uint256 totalDebtETH,uint256 availableBorrowsETH,,,) = lendingPool.getUserAccountData(_user); if (totalDebtETH == 0) return uint256(0); return wdiv(add(totalDebtETH, availableBorrowsETH), totalDebtETH); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../utils/GasBurner.sol"; import "../../DS/DSMath.sol"; import "../../auth/AdminAuth.sol"; import "../../loggers/DefisaverLogger.sol"; import "../../exchangeV3/DFSExchangeData.sol"; import "./AaveMonitorProxyV2.sol"; import "./AaveSubscriptionsV2.sol"; import "../AaveSafetyRatioV2.sol"; /// @title Contract implements logic of calling boost/repay in the automatic system contract AaveMonitorV2 is AdminAuth, DSMath, AaveSafetyRatioV2, GasBurner { using SafeERC20 for ERC20; string public constant NAME = "AaveMonitorV2"; enum Method { Boost, Repay } uint public REPAY_GAS_TOKEN = 20; uint public BOOST_GAS_TOKEN = 20; uint public MAX_GAS_PRICE = 400000000000; // 400 gwei uint public REPAY_GAS_COST = 2000000; uint public BOOST_GAS_COST = 2000000; address public constant DEFISAVER_LOGGER = 0x5c55B921f590a89C1Ebe84dF170E655a82b62126; address public constant AAVE_MARKET_ADDRESS = 0xB53C1a33016B2DC2fF3653530bfF1848a515c8c5; AaveMonitorProxyV2 public aaveMonitorProxy; AaveSubscriptionsV2 public subscriptionsContract; address public aaveSaverProxy; DefisaverLogger public logger = DefisaverLogger(DEFISAVER_LOGGER); modifier onlyApproved() { require(BotRegistry(BOT_REGISTRY_ADDRESS).botList(msg.sender), "Not auth bot"); _; } /// @param _aaveMonitorProxy Proxy contracts that actually is authorized to call DSProxy /// @param _subscriptions Subscriptions contract for Aave positions /// @param _aaveSaverProxy Contract that actually performs Repay/Boost constructor(address _aaveMonitorProxy, address _subscriptions, address _aaveSaverProxy) public { aaveMonitorProxy = AaveMonitorProxyV2(_aaveMonitorProxy); subscriptionsContract = AaveSubscriptionsV2(_subscriptions); aaveSaverProxy = _aaveSaverProxy; } /// @notice Bots call this method to repay for user when conditions are met /// @dev If the contract ownes gas token it will try and use it for gas price reduction /// @param _exData Exchange data /// @param _user The actual address that owns the Aave position function repayFor( DFSExchangeData.ExchangeData memory _exData, address _user, uint256 _rateMode, uint256 _flAmount ) public payable onlyApproved burnGas(REPAY_GAS_TOKEN) { (bool isAllowed, uint ratioBefore) = canCall(Method.Repay, _user); require(isAllowed); // check if conditions are met uint256 gasCost = calcGasCost(REPAY_GAS_COST); aaveMonitorProxy.callExecute{value: msg.value}( _user, aaveSaverProxy, abi.encodeWithSignature( "repay(address,(address,address,uint256,uint256,uint256,uint256,address,address,bytes,(address,address,address,uint256,uint256,bytes)),uint256,uint256,uint256)", AAVE_MARKET_ADDRESS, _exData, _rateMode, gasCost, _flAmount ) ); (bool isGoodRatio, uint ratioAfter) = ratioGoodAfter(Method.Repay, _user); require(isGoodRatio); // check if the after result of the actions is good returnEth(); logger.Log(address(this), _user, "AutomaticAaveRepayV2", abi.encode(ratioBefore, ratioAfter)); } /// @notice Bots call this method to boost for user when conditions are met /// @dev If the contract ownes gas token it will try and use it for gas price reduction /// @param _exData Exchange data /// @param _user The actual address that owns the Aave position function boostFor( DFSExchangeData.ExchangeData memory _exData, address _user, uint256 _rateMode, uint256 _flAmount ) public payable onlyApproved burnGas(BOOST_GAS_TOKEN) { (bool isAllowed, uint ratioBefore) = canCall(Method.Boost, _user); require(isAllowed); // check if conditions are met uint256 gasCost = calcGasCost(BOOST_GAS_COST); aaveMonitorProxy.callExecute{value: msg.value}( _user, aaveSaverProxy, abi.encodeWithSignature( "boost(address,(address,address,uint256,uint256,uint256,uint256,address,address,bytes,(address,address,address,uint256,uint256,bytes)),uint256,uint256,uint256)", AAVE_MARKET_ADDRESS, _exData, _rateMode, gasCost, _flAmount ) ); (bool isGoodRatio, uint ratioAfter) = ratioGoodAfter(Method.Boost, _user); require(isGoodRatio); // check if the after result of the actions is good returnEth(); logger.Log(address(this), _user, "AutomaticAaveBoostV2", abi.encode(ratioBefore, ratioAfter)); } /******************* INTERNAL METHODS ********************************/ function returnEth() internal { // return if some eth left if (address(this).balance > 0) { msg.sender.transfer(address(this).balance); } } /******************* STATIC METHODS ********************************/ /// @notice Checks if Boost/Repay could be triggered for the CDP /// @dev Called by AaveMonitor to enforce the min/max check /// @param _method Type of action to be called /// @param _user The actual address that owns the Aave position /// @return Boolean if it can be called and the ratio function canCall(Method _method, address _user) public view returns(bool, uint) { bool subscribed = subscriptionsContract.isSubscribed(_user); AaveSubscriptionsV2.AaveHolder memory holder = subscriptionsContract.getHolder(_user); // check if cdp is subscribed if (!subscribed) return (false, 0); // check if boost and boost allowed if (_method == Method.Boost && !holder.boostEnabled) return (false, 0); uint currRatio = getSafetyRatio(AAVE_MARKET_ADDRESS, _user); if (_method == Method.Repay) { return (currRatio < holder.minRatio, currRatio); } else if (_method == Method.Boost) { return (currRatio > holder.maxRatio, currRatio); } } /// @dev After the Boost/Repay check if the ratio doesn't trigger another call /// @param _method Type of action to be called /// @param _user The actual address that owns the Aave position /// @return Boolean if the recent action preformed correctly and the ratio function ratioGoodAfter(Method _method, address _user) public view returns(bool, uint) { AaveSubscriptionsV2.AaveHolder memory holder; holder = subscriptionsContract.getHolder(_user); uint currRatio = getSafetyRatio(AAVE_MARKET_ADDRESS, _user); if (_method == Method.Repay) { return (currRatio < holder.maxRatio, currRatio); } else if (_method == Method.Boost) { return (currRatio > holder.minRatio, currRatio); } } /// @notice Calculates gas cost (in Eth) of tx /// @dev Gas price is limited to MAX_GAS_PRICE to prevent attack of draining user CDP /// @param _gasAmount Amount of gas used for the tx function calcGasCost(uint _gasAmount) public view returns (uint) { uint gasPrice = tx.gasprice <= MAX_GAS_PRICE ? tx.gasprice : MAX_GAS_PRICE; return mul(gasPrice, _gasAmount); } /******************* OWNER ONLY OPERATIONS ********************************/ /// @notice As the code is new, have a emergancy admin saver proxy change function changeAaveSaverProxy(address _newAaveSaverProxy) public onlyAdmin { aaveSaverProxy = _newAaveSaverProxy; } /// @notice Allows owner to change gas cost for boost operation, but only up to 3 millions /// @param _gasCost New gas cost for boost method function changeBoostGasCost(uint _gasCost) public onlyOwner { require(_gasCost < 3000000); BOOST_GAS_COST = _gasCost; } /// @notice Allows owner to change gas cost for repay operation, but only up to 3 millions /// @param _gasCost New gas cost for repay method function changeRepayGasCost(uint _gasCost) public onlyOwner { require(_gasCost < 3000000); REPAY_GAS_COST = _gasCost; } /// @notice Allows owner to change max gas price /// @param _maxGasPrice New max gas price function changeMaxGasPrice(uint _maxGasPrice) public onlyOwner { require(_maxGasPrice < 500000000000); MAX_GAS_PRICE = _maxGasPrice; } /// @notice Allows owner to change gas token amount /// @param _gasTokenAmount New gas token amount /// @param _repay true if repay gas token, false if boost gas token function changeGasTokenAmount(uint _gasTokenAmount, bool _repay) public onlyOwner { if (_repay) { REPAY_GAS_TOKEN = _gasTokenAmount; } else { BOOST_GAS_TOKEN = _gasTokenAmount; } } }
pragma solidity ^0.6.0; import "../../interfaces/DSProxyInterface.sol"; import "../../utils/SafeERC20.sol"; import "../../auth/AdminAuth.sol"; /// @title Contract with the actuall DSProxy permission calls the automation operations contract AaveMonitorProxyV2 is AdminAuth { using SafeERC20 for ERC20; string public constant NAME = "AaveMonitorProxyV2"; uint public CHANGE_PERIOD; address public monitor; address public newMonitor; address public lastMonitor; uint public changeRequestedTimestamp; event MonitorChangeInitiated(address oldMonitor, address newMonitor); event MonitorChangeCanceled(); event MonitorChangeFinished(address monitor); event MonitorChangeReverted(address monitor); modifier onlyMonitor() { require (msg.sender == monitor); _; } constructor(uint _changePeriod) public { CHANGE_PERIOD = _changePeriod * 1 hours; } /// @notice Only monitor contract is able to call execute on users proxy /// @param _owner Address of cdp owner (users DSProxy address) /// @param _aaveSaverProxy Address of AaveSaverProxy /// @param _data Data to send to AaveSaverProxy function callExecute(address _owner, address _aaveSaverProxy, bytes memory _data) public payable onlyMonitor { // execute reverts if calling specific method fails DSProxyInterface(_owner).execute{value: msg.value}(_aaveSaverProxy, _data); // return if anything left if (address(this).balance > 0) { msg.sender.transfer(address(this).balance); } } /// @notice Owner is able to set Monitor contract without any waiting period first time /// @param _monitor Address of Monitor contract function setMonitor(address _monitor) public onlyOwner { require(monitor == address(0)); monitor = _monitor; } /// @notice Owner is able to start procedure for changing monitor /// @dev after CHANGE_PERIOD needs to call confirmNewMonitor to actually make a change /// @param _newMonitor address of new monitor function changeMonitor(address _newMonitor) public onlyOwner { require(changeRequestedTimestamp == 0); changeRequestedTimestamp = now; lastMonitor = monitor; newMonitor = _newMonitor; emit MonitorChangeInitiated(lastMonitor, newMonitor); } /// @notice At any point owner is able to cancel monitor change function cancelMonitorChange() public onlyOwner { require(changeRequestedTimestamp > 0); changeRequestedTimestamp = 0; newMonitor = address(0); emit MonitorChangeCanceled(); } /// @notice Anyone is able to confirm new monitor after CHANGE_PERIOD if process is started function confirmNewMonitor() public onlyOwner { require((changeRequestedTimestamp + CHANGE_PERIOD) < now); require(changeRequestedTimestamp != 0); require(newMonitor != address(0)); monitor = newMonitor; newMonitor = address(0); changeRequestedTimestamp = 0; emit MonitorChangeFinished(monitor); } /// @notice Its possible to revert monitor to last used monitor function revertMonitor() public onlyOwner { require(lastMonitor != address(0)); monitor = lastMonitor; emit MonitorChangeReverted(monitor); } function setChangePeriod(uint _periodInHours) public onlyOwner { require(_periodInHours * 1 hours > CHANGE_PERIOD); CHANGE_PERIOD = _periodInHours * 1 hours; } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../auth/AdminAuth.sol"; /// @title Stores subscription information for Aave automatization contract AaveSubscriptionsV2 is AdminAuth { string public constant NAME = "AaveSubscriptionsV2"; struct AaveHolder { address user; uint128 minRatio; uint128 maxRatio; uint128 optimalRatioBoost; uint128 optimalRatioRepay; bool boostEnabled; } struct SubPosition { uint arrPos; bool subscribed; } AaveHolder[] public subscribers; mapping (address => SubPosition) public subscribersPos; uint public changeIndex; event Subscribed(address indexed user); event Unsubscribed(address indexed user); event Updated(address indexed user); event ParamUpdates(address indexed user, uint128, uint128, uint128, uint128, bool); /// @dev Called by the DSProxy contract which owns the Aave position /// @notice Adds the users Aave poistion in the list of subscriptions so it can be monitored /// @param _minRatio Minimum ratio below which repay is triggered /// @param _maxRatio Maximum ratio after which boost is triggered /// @param _optimalBoost Ratio amount which boost should target /// @param _optimalRepay Ratio amount which repay should target /// @param _boostEnabled Boolean determing if boost is enabled function subscribe(uint128 _minRatio, uint128 _maxRatio, uint128 _optimalBoost, uint128 _optimalRepay, bool _boostEnabled) external { // if boost is not enabled, set max ratio to max uint uint128 localMaxRatio = _boostEnabled ? _maxRatio : uint128(-1); require(checkParams(_minRatio, localMaxRatio), "Must be correct params"); SubPosition storage subInfo = subscribersPos[msg.sender]; AaveHolder memory subscription = AaveHolder({ minRatio: _minRatio, maxRatio: localMaxRatio, optimalRatioBoost: _optimalBoost, optimalRatioRepay: _optimalRepay, user: msg.sender, boostEnabled: _boostEnabled }); changeIndex++; if (subInfo.subscribed) { subscribers[subInfo.arrPos] = subscription; emit Updated(msg.sender); emit ParamUpdates(msg.sender, _minRatio, localMaxRatio, _optimalBoost, _optimalRepay, _boostEnabled); } else { subscribers.push(subscription); subInfo.arrPos = subscribers.length - 1; subInfo.subscribed = true; emit Subscribed(msg.sender); } } /// @notice Called by the users DSProxy /// @dev Owner who subscribed cancels his subscription function unsubscribe() external { _unsubscribe(msg.sender); } /// @dev Checks limit if minRatio is bigger than max /// @param _minRatio Minimum ratio, bellow which repay can be triggered /// @param _maxRatio Maximum ratio, over which boost can be triggered /// @return Returns bool if the params are correct function checkParams(uint128 _minRatio, uint128 _maxRatio) internal pure returns (bool) { if (_minRatio > _maxRatio) { return false; } return true; } /// @dev Internal method to remove a subscriber from the list /// @param _user The actual address that owns the Aave position function _unsubscribe(address _user) internal { require(subscribers.length > 0, "Must have subscribers in the list"); SubPosition storage subInfo = subscribersPos[_user]; require(subInfo.subscribed, "Must first be subscribed"); address lastOwner = subscribers[subscribers.length - 1].user; SubPosition storage subInfo2 = subscribersPos[lastOwner]; subInfo2.arrPos = subInfo.arrPos; subscribers[subInfo.arrPos] = subscribers[subscribers.length - 1]; subscribers.pop(); // remove last element and reduce arr length changeIndex++; subInfo.subscribed = false; subInfo.arrPos = 0; emit Unsubscribed(msg.sender); } /// @dev Checks if the user is subscribed /// @param _user The actual address that owns the Aave position /// @return If the user is subscribed function isSubscribed(address _user) public view returns (bool) { SubPosition storage subInfo = subscribersPos[_user]; return subInfo.subscribed; } /// @dev Returns subscribtion information about a user /// @param _user The actual address that owns the Aave position /// @return Subscription information about the user if exists function getHolder(address _user) public view returns (AaveHolder memory) { SubPosition storage subInfo = subscribersPos[_user]; return subscribers[subInfo.arrPos]; } /// @notice Helper method to return all the subscribed CDPs /// @return List of all subscribers function getSubscribers() public view returns (AaveHolder[] memory) { return subscribers; } /// @notice Helper method for the frontend, returns all the subscribed CDPs paginated /// @param _page What page of subscribers you want /// @param _perPage Number of entries per page /// @return List of all subscribers for that page function getSubscribersByPage(uint _page, uint _perPage) public view returns (AaveHolder[] memory) { AaveHolder[] memory holders = new AaveHolder[](_perPage); uint start = _page * _perPage; uint end = start + _perPage; end = (end > holders.length) ? holders.length : end; uint count = 0; for (uint i = start; i < end; i++) { holders[count] = subscribers[i]; count++; } return holders; } ////////////// ADMIN METHODS /////////////////// /// @notice Admin function to unsubscribe a position /// @param _user The actual address that owns the Aave position function unsubscribeByAdmin(address _user) public onlyOwner { SubPosition storage subInfo = subscribersPos[_user]; if (subInfo.subscribed) { _unsubscribe(_user); } } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "./AaveSafetyRatioV2.sol"; import "../interfaces/IAaveProtocolDataProviderV2.sol"; contract AaveLoanInfoV2 is AaveSafetyRatioV2 { struct LoanData { address user; uint128 ratio; address[] collAddr; address[] borrowAddr; uint256[] collAmounts; uint256[] borrowStableAmounts; uint256[] borrowVariableAmounts; } struct TokenInfo { address aTokenAddress; address underlyingTokenAddress; uint256 collateralFactor; uint256 price; } struct TokenInfoFull { address aTokenAddress; address underlyingTokenAddress; uint256 supplyRate; uint256 borrowRateVariable; uint256 borrowRateStable; uint256 totalSupply; uint256 availableLiquidity; uint256 totalBorrow; uint256 collateralFactor; uint256 liquidationRatio; uint256 price; bool usageAsCollateralEnabled; bool borrowinEnabled; bool stableBorrowRateEnabled; } struct ReserveData { uint256 availableLiquidity; uint256 totalStableDebt; uint256 totalVariableDebt; uint256 liquidityRate; uint256 variableBorrowRate; uint256 stableBorrowRate; } struct UserToken { address token; uint256 balance; uint256 borrowsStable; uint256 borrowsVariable; uint256 stableBorrowRate; bool enabledAsCollateral; } /// @notice Calcualted the ratio of coll/debt for a compound user /// @param _market Address of LendingPoolAddressesProvider for specific market /// @param _user Address of the user function getRatio(address _market, address _user) public view returns (uint256) { // For each asset the account is in return getSafetyRatio(_market, _user); } /// @notice Fetches Aave prices for tokens /// @param _market Address of LendingPoolAddressesProvider for specific market /// @param _tokens Arr. of tokens for which to get the prices /// @return prices Array of prices function getPrices(address _market, address[] memory _tokens) public view returns (uint256[] memory prices) { address priceOracleAddress = ILendingPoolAddressesProviderV2(_market).getPriceOracle(); prices = IPriceOracleGetterAave(priceOracleAddress).getAssetsPrices(_tokens); } /// @notice Fetches Aave collateral factors for tokens /// @param _market Address of LendingPoolAddressesProvider for specific market /// @param _tokens Arr. of tokens for which to get the coll. factors /// @return collFactors Array of coll. factors function getCollFactors(address _market, address[] memory _tokens) public view returns (uint256[] memory collFactors) { IAaveProtocolDataProviderV2 dataProvider = getDataProvider(_market); collFactors = new uint256[](_tokens.length); for (uint256 i = 0; i < _tokens.length; ++i) { (,collFactors[i],,,,,,,,) = dataProvider.getReserveConfigurationData(_tokens[i]); } } function getTokenBalances(address _market, address _user, address[] memory _tokens) public view returns (UserToken[] memory userTokens) { IAaveProtocolDataProviderV2 dataProvider = getDataProvider(_market); userTokens = new UserToken[](_tokens.length); for (uint256 i = 0; i < _tokens.length; i++) { address asset = _tokens[i]; userTokens[i].token = asset; (userTokens[i].balance, userTokens[i].borrowsStable, userTokens[i].borrowsVariable,,,userTokens[i].stableBorrowRate,,,userTokens[i].enabledAsCollateral) = dataProvider.getUserReserveData(asset, _user); } } /// @notice Calcualted the ratio of coll/debt for an aave user /// @param _market Address of LendingPoolAddressesProvider for specific market /// @param _users Addresses of the user /// @return ratios Array of ratios function getRatios(address _market, address[] memory _users) public view returns (uint256[] memory ratios) { ratios = new uint256[](_users.length); for (uint256 i = 0; i < _users.length; ++i) { ratios[i] = getSafetyRatio(_market, _users[i]); } } /// @notice Information about reserves /// @param _market Address of LendingPoolAddressesProvider for specific market /// @param _tokenAddresses Array of tokens addresses /// @return tokens Array of reserves infomartion function getTokensInfo(address _market, address[] memory _tokenAddresses) public view returns(TokenInfo[] memory tokens) { IAaveProtocolDataProviderV2 dataProvider = getDataProvider(_market); address priceOracleAddress = ILendingPoolAddressesProviderV2(_market).getPriceOracle(); tokens = new TokenInfo[](_tokenAddresses.length); for (uint256 i = 0; i < _tokenAddresses.length; ++i) { (,uint256 ltv,,,,,,,,) = dataProvider.getReserveConfigurationData(_tokenAddresses[i]); (address aToken,,) = dataProvider.getReserveTokensAddresses(_tokenAddresses[i]); tokens[i] = TokenInfo({ aTokenAddress: aToken, underlyingTokenAddress: _tokenAddresses[i], collateralFactor: ltv, price: IPriceOracleGetterAave(priceOracleAddress).getAssetPrice(_tokenAddresses[i]) }); } } function getTokenInfoFull(IAaveProtocolDataProviderV2 _dataProvider, address _priceOracleAddress, address _token) private view returns(TokenInfoFull memory _tokenInfo) { ( , // uint256 decimals uint256 ltv, uint256 liquidationThreshold, , // uint256 liquidationBonus , // uint256 reserveFactor bool usageAsCollateralEnabled, bool borrowinEnabled, bool stableBorrowRateEnabled, , // bool isActive // bool isFrozen ) = _dataProvider.getReserveConfigurationData(_token); ReserveData memory t; ( t.availableLiquidity, t.totalStableDebt, t.totalVariableDebt, t.liquidityRate, t.variableBorrowRate, t.stableBorrowRate, , , , ) = _dataProvider.getReserveData(_token); (address aToken,,) = _dataProvider.getReserveTokensAddresses(_token); uint price = IPriceOracleGetterAave(_priceOracleAddress).getAssetPrice(_token); _tokenInfo = TokenInfoFull({ aTokenAddress: aToken, underlyingTokenAddress: _token, supplyRate: t.liquidityRate, borrowRateVariable: t.variableBorrowRate, borrowRateStable: t.stableBorrowRate, totalSupply: ERC20(aToken).totalSupply(), availableLiquidity: t.availableLiquidity, totalBorrow: t.totalVariableDebt+t.totalStableDebt, collateralFactor: ltv, liquidationRatio: liquidationThreshold, price: price, usageAsCollateralEnabled: usageAsCollateralEnabled, borrowinEnabled: borrowinEnabled, stableBorrowRateEnabled: stableBorrowRateEnabled }); } /// @notice Information about reserves /// @param _market Address of LendingPoolAddressesProvider for specific market /// @param _tokenAddresses Array of token addresses /// @return tokens Array of reserves infomartion function getFullTokensInfo(address _market, address[] memory _tokenAddresses) public view returns(TokenInfoFull[] memory tokens) { IAaveProtocolDataProviderV2 dataProvider = getDataProvider(_market); address priceOracleAddress = ILendingPoolAddressesProviderV2(_market).getPriceOracle(); tokens = new TokenInfoFull[](_tokenAddresses.length); for (uint256 i = 0; i < _tokenAddresses.length; ++i) { tokens[i] = getTokenInfoFull(dataProvider, priceOracleAddress, _tokenAddresses[i]); } } /// @notice Fetches all the collateral/debt address and amounts, denominated in ether /// @param _market Address of LendingPoolAddressesProvider for specific market /// @param _user Address of the user /// @return data LoanData information function getLoanData(address _market, address _user) public view returns (LoanData memory data) { IAaveProtocolDataProviderV2 dataProvider = getDataProvider(_market); address priceOracleAddress = ILendingPoolAddressesProviderV2(_market).getPriceOracle(); IAaveProtocolDataProviderV2.TokenData[] memory reserves = dataProvider.getAllReservesTokens(); data = LoanData({ user: _user, ratio: 0, collAddr: new address[](reserves.length), borrowAddr: new address[](reserves.length), collAmounts: new uint[](reserves.length), borrowStableAmounts: new uint[](reserves.length), borrowVariableAmounts: new uint[](reserves.length) }); uint64 collPos = 0; uint64 borrowPos = 0; for (uint64 i = 0; i < reserves.length; i++) { address reserve = reserves[i].tokenAddress; (uint256 aTokenBalance, uint256 borrowsStable, uint256 borrowsVariable,,,,,,) = dataProvider.getUserReserveData(reserve, _user); uint256 price = IPriceOracleGetterAave(priceOracleAddress).getAssetPrice(reserve); if (aTokenBalance > 0) { uint256 userTokenBalanceEth = wmul(aTokenBalance, price) * (10 ** (18 - _getDecimals(reserve))); data.collAddr[collPos] = reserve; data.collAmounts[collPos] = userTokenBalanceEth; collPos++; } // Sum up debt in Eth if (borrowsStable > 0) { uint256 userBorrowBalanceEth = wmul(borrowsStable, price) * (10 ** (18 - _getDecimals(reserve))); data.borrowAddr[borrowPos] = reserve; data.borrowStableAmounts[borrowPos] = userBorrowBalanceEth; } // Sum up debt in Eth if (borrowsVariable > 0) { uint256 userBorrowBalanceEth = wmul(borrowsVariable, price) * (10 ** (18 - _getDecimals(reserve))); data.borrowAddr[borrowPos] = reserve; data.borrowVariableAmounts[borrowPos] = userBorrowBalanceEth; } if (borrowsStable > 0 || borrowsVariable > 0) { borrowPos++; } } data.ratio = uint128(getSafetyRatio(_market, _user)); return data; } /// @notice Fetches all the collateral/debt address and amounts, denominated in ether /// @param _market Address of LendingPoolAddressesProvider for specific market /// @param _users Addresses of the user /// @return loans Array of LoanData information function getLoanDataArr(address _market, address[] memory _users) public view returns (LoanData[] memory loans) { loans = new LoanData[](_users.length); for (uint i = 0; i < _users.length; ++i) { loans[i] = getLoanData(_market, _users[i]); } } }
pragma solidity ^0.6.0; import "./AaveHelper.sol"; contract AaveSafetyRatio is AaveHelper { function getSafetyRatio(address _user) public view returns(uint256) { address lendingPoolAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPool(); (,,uint256 totalBorrowsETH,,uint256 availableBorrowsETH,,,) = ILendingPool(lendingPoolAddress).getUserAccountData(_user); if (totalBorrowsETH == 0) return uint256(0); return wdiv(add(totalBorrowsETH, availableBorrowsETH), totalBorrowsETH); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../utils/GasBurner.sol"; import "./AaveMonitorProxy.sol"; import "./AaveSubscriptions.sol"; import "../../DS/DSMath.sol"; import "../../auth/AdminAuth.sol"; import "../../loggers/DefisaverLogger.sol"; import "../AaveSafetyRatio.sol"; import "../../exchange/SaverExchangeCore.sol"; /// @title Contract implements logic of calling boost/repay in the automatic system contract AaveMonitor is AdminAuth, DSMath, AaveSafetyRatio, GasBurner { using SafeERC20 for ERC20; enum Method { Boost, Repay } uint public REPAY_GAS_TOKEN = 20; uint public BOOST_GAS_TOKEN = 20; uint public MAX_GAS_PRICE = 400000000000; // 400 gwei uint public REPAY_GAS_COST = 2000000; uint public BOOST_GAS_COST = 2000000; address public constant DEFISAVER_LOGGER = 0x5c55B921f590a89C1Ebe84dF170E655a82b62126; AaveMonitorProxy public aaveMonitorProxy; AaveSubscriptions public subscriptionsContract; address public aaveSaverProxy; DefisaverLogger public logger = DefisaverLogger(DEFISAVER_LOGGER); modifier onlyApproved() { require(BotRegistry(BOT_REGISTRY_ADDRESS).botList(msg.sender), "Not auth bot"); _; } /// @param _aaveMonitorProxy Proxy contracts that actually is authorized to call DSProxy /// @param _subscriptions Subscriptions contract for Aave positions /// @param _aaveSaverProxy Contract that actually performs Repay/Boost constructor(address _aaveMonitorProxy, address _subscriptions, address _aaveSaverProxy) public { aaveMonitorProxy = AaveMonitorProxy(_aaveMonitorProxy); subscriptionsContract = AaveSubscriptions(_subscriptions); aaveSaverProxy = _aaveSaverProxy; } /// @notice Bots call this method to repay for user when conditions are met /// @dev If the contract ownes gas token it will try and use it for gas price reduction /// @param _exData Exchange data /// @param _user The actual address that owns the Aave position function repayFor( SaverExchangeCore.ExchangeData memory _exData, address _user ) public payable onlyApproved burnGas(REPAY_GAS_TOKEN) { (bool isAllowed, uint ratioBefore) = canCall(Method.Repay, _user); require(isAllowed); // check if conditions are met uint256 gasCost = calcGasCost(REPAY_GAS_COST); aaveMonitorProxy.callExecute{value: msg.value}( _user, aaveSaverProxy, abi.encodeWithSignature( "repay((address,address,uint256,uint256,uint256,address,address,bytes,uint256),uint256)", _exData, gasCost ) ); (bool isGoodRatio, uint ratioAfter) = ratioGoodAfter(Method.Repay, _user); require(isGoodRatio); // check if the after result of the actions is good returnEth(); logger.Log(address(this), _user, "AutomaticAaveRepay", abi.encode(ratioBefore, ratioAfter)); } /// @notice Bots call this method to boost for user when conditions are met /// @dev If the contract ownes gas token it will try and use it for gas price reduction /// @param _exData Exchange data /// @param _user The actual address that owns the Aave position function boostFor( SaverExchangeCore.ExchangeData memory _exData, address _user ) public payable onlyApproved burnGas(BOOST_GAS_TOKEN) { (bool isAllowed, uint ratioBefore) = canCall(Method.Boost, _user); require(isAllowed); // check if conditions are met uint256 gasCost = calcGasCost(BOOST_GAS_COST); aaveMonitorProxy.callExecute{value: msg.value}( _user, aaveSaverProxy, abi.encodeWithSignature( "boost((address,address,uint256,uint256,uint256,address,address,bytes,uint256),uint256)", _exData, gasCost ) ); (bool isGoodRatio, uint ratioAfter) = ratioGoodAfter(Method.Boost, _user); require(isGoodRatio); // check if the after result of the actions is good returnEth(); logger.Log(address(this), _user, "AutomaticAaveBoost", abi.encode(ratioBefore, ratioAfter)); } /******************* INTERNAL METHODS ********************************/ function returnEth() internal { // return if some eth left if (address(this).balance > 0) { msg.sender.transfer(address(this).balance); } } /******************* STATIC METHODS ********************************/ /// @notice Checks if Boost/Repay could be triggered for the CDP /// @dev Called by AaveMonitor to enforce the min/max check /// @param _method Type of action to be called /// @param _user The actual address that owns the Aave position /// @return Boolean if it can be called and the ratio function canCall(Method _method, address _user) public view returns(bool, uint) { bool subscribed = subscriptionsContract.isSubscribed(_user); AaveSubscriptions.AaveHolder memory holder = subscriptionsContract.getHolder(_user); // check if cdp is subscribed if (!subscribed) return (false, 0); // check if boost and boost allowed if (_method == Method.Boost && !holder.boostEnabled) return (false, 0); uint currRatio = getSafetyRatio(_user); if (_method == Method.Repay) { return (currRatio < holder.minRatio, currRatio); } else if (_method == Method.Boost) { return (currRatio > holder.maxRatio, currRatio); } } /// @dev After the Boost/Repay check if the ratio doesn't trigger another call /// @param _method Type of action to be called /// @param _user The actual address that owns the Aave position /// @return Boolean if the recent action preformed correctly and the ratio function ratioGoodAfter(Method _method, address _user) public view returns(bool, uint) { AaveSubscriptions.AaveHolder memory holder; holder= subscriptionsContract.getHolder(_user); uint currRatio = getSafetyRatio(_user); if (_method == Method.Repay) { return (currRatio < holder.maxRatio, currRatio); } else if (_method == Method.Boost) { return (currRatio > holder.minRatio, currRatio); } } /// @notice Calculates gas cost (in Eth) of tx /// @dev Gas price is limited to MAX_GAS_PRICE to prevent attack of draining user CDP /// @param _gasAmount Amount of gas used for the tx function calcGasCost(uint _gasAmount) public view returns (uint) { uint gasPrice = tx.gasprice <= MAX_GAS_PRICE ? tx.gasprice : MAX_GAS_PRICE; return mul(gasPrice, _gasAmount); } /******************* OWNER ONLY OPERATIONS ********************************/ /// @notice As the code is new, have a emergancy admin saver proxy change function changeAaveSaverProxy(address _newAaveSaverProxy) public onlyAdmin { aaveSaverProxy = _newAaveSaverProxy; } /// @notice Allows owner to change gas cost for boost operation, but only up to 3 millions /// @param _gasCost New gas cost for boost method function changeBoostGasCost(uint _gasCost) public onlyOwner { require(_gasCost < 3000000); BOOST_GAS_COST = _gasCost; } /// @notice Allows owner to change gas cost for repay operation, but only up to 3 millions /// @param _gasCost New gas cost for repay method function changeRepayGasCost(uint _gasCost) public onlyOwner { require(_gasCost < 3000000); REPAY_GAS_COST = _gasCost; } /// @notice Allows owner to change max gas price /// @param _maxGasPrice New max gas price function changeMaxGasPrice(uint _maxGasPrice) public onlyOwner { require(_maxGasPrice < 500000000000); MAX_GAS_PRICE = _maxGasPrice; } /// @notice Allows owner to change gas token amount /// @param _gasTokenAmount New gas token amount /// @param _repay true if repay gas token, false if boost gas token function changeGasTokenAmount(uint _gasTokenAmount, bool _repay) public onlyOwner { if (_repay) { REPAY_GAS_TOKEN = _gasTokenAmount; } else { BOOST_GAS_TOKEN = _gasTokenAmount; } } }
pragma solidity ^0.6.0; import "../../interfaces/DSProxyInterface.sol"; import "../../utils/SafeERC20.sol"; import "../../auth/AdminAuth.sol"; /// @title Contract with the actuall DSProxy permission calls the automation operations contract AaveMonitorProxy is AdminAuth { using SafeERC20 for ERC20; uint public CHANGE_PERIOD; address public monitor; address public newMonitor; address public lastMonitor; uint public changeRequestedTimestamp; mapping(address => bool) public allowed; event MonitorChangeInitiated(address oldMonitor, address newMonitor); event MonitorChangeCanceled(); event MonitorChangeFinished(address monitor); event MonitorChangeReverted(address monitor); // if someone who is allowed become malicious, owner can't be changed modifier onlyAllowed() { require(allowed[msg.sender] || msg.sender == owner); _; } modifier onlyMonitor() { require (msg.sender == monitor); _; } constructor(uint _changePeriod) public { CHANGE_PERIOD = _changePeriod * 1 days; } /// @notice Only monitor contract is able to call execute on users proxy /// @param _owner Address of cdp owner (users DSProxy address) /// @param _aaveSaverProxy Address of AaveSaverProxy /// @param _data Data to send to AaveSaverProxy function callExecute(address _owner, address _aaveSaverProxy, bytes memory _data) public payable onlyMonitor { // execute reverts if calling specific method fails DSProxyInterface(_owner).execute{value: msg.value}(_aaveSaverProxy, _data); // return if anything left if (address(this).balance > 0) { msg.sender.transfer(address(this).balance); } } /// @notice Allowed users are able to set Monitor contract without any waiting period first time /// @param _monitor Address of Monitor contract function setMonitor(address _monitor) public onlyAllowed { require(monitor == address(0)); monitor = _monitor; } /// @notice Allowed users are able to start procedure for changing monitor /// @dev after CHANGE_PERIOD needs to call confirmNewMonitor to actually make a change /// @param _newMonitor address of new monitor function changeMonitor(address _newMonitor) public onlyAllowed { require(changeRequestedTimestamp == 0); changeRequestedTimestamp = now; lastMonitor = monitor; newMonitor = _newMonitor; emit MonitorChangeInitiated(lastMonitor, newMonitor); } /// @notice At any point allowed users are able to cancel monitor change function cancelMonitorChange() public onlyAllowed { require(changeRequestedTimestamp > 0); changeRequestedTimestamp = 0; newMonitor = address(0); emit MonitorChangeCanceled(); } /// @notice Anyone is able to confirm new monitor after CHANGE_PERIOD if process is started function confirmNewMonitor() public onlyAllowed { require((changeRequestedTimestamp + CHANGE_PERIOD) < now); require(changeRequestedTimestamp != 0); require(newMonitor != address(0)); monitor = newMonitor; newMonitor = address(0); changeRequestedTimestamp = 0; emit MonitorChangeFinished(monitor); } /// @notice Its possible to revert monitor to last used monitor function revertMonitor() public onlyAllowed { require(lastMonitor != address(0)); monitor = lastMonitor; emit MonitorChangeReverted(monitor); } /// @notice Allowed users are able to add new allowed user /// @param _user Address of user that will be allowed function addAllowed(address _user) public onlyAllowed { allowed[_user] = true; } /// @notice Allowed users are able to remove allowed user /// @dev owner is always allowed even if someone tries to remove it from allowed mapping /// @param _user Address of allowed user function removeAllowed(address _user) public onlyAllowed { allowed[_user] = false; } function setChangePeriod(uint _periodInDays) public onlyAllowed { require(_periodInDays * 1 days > CHANGE_PERIOD); CHANGE_PERIOD = _periodInDays * 1 days; } /// @notice In case something is left in contract, owner is able to withdraw it /// @param _token address of token to withdraw balance function withdrawToken(address _token) public onlyOwner { uint balance = ERC20(_token).balanceOf(address(this)); ERC20(_token).safeTransfer(msg.sender, balance); } /// @notice In case something is left in contract, owner is able to withdraw it function withdrawEth() public onlyOwner { uint balance = address(this).balance; msg.sender.transfer(balance); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../auth/AdminAuth.sol"; /// @title Stores subscription information for Aave automatization contract AaveSubscriptions is AdminAuth { struct AaveHolder { address user; uint128 minRatio; uint128 maxRatio; uint128 optimalRatioBoost; uint128 optimalRatioRepay; bool boostEnabled; } struct SubPosition { uint arrPos; bool subscribed; } AaveHolder[] public subscribers; mapping (address => SubPosition) public subscribersPos; uint public changeIndex; event Subscribed(address indexed user); event Unsubscribed(address indexed user); event Updated(address indexed user); event ParamUpdates(address indexed user, uint128, uint128, uint128, uint128, bool); /// @dev Called by the DSProxy contract which owns the Aave position /// @notice Adds the users Aave poistion in the list of subscriptions so it can be monitored /// @param _minRatio Minimum ratio below which repay is triggered /// @param _maxRatio Maximum ratio after which boost is triggered /// @param _optimalBoost Ratio amount which boost should target /// @param _optimalRepay Ratio amount which repay should target /// @param _boostEnabled Boolean determing if boost is enabled function subscribe(uint128 _minRatio, uint128 _maxRatio, uint128 _optimalBoost, uint128 _optimalRepay, bool _boostEnabled) external { // if boost is not enabled, set max ratio to max uint uint128 localMaxRatio = _boostEnabled ? _maxRatio : uint128(-1); require(checkParams(_minRatio, localMaxRatio), "Must be correct params"); SubPosition storage subInfo = subscribersPos[msg.sender]; AaveHolder memory subscription = AaveHolder({ minRatio: _minRatio, maxRatio: localMaxRatio, optimalRatioBoost: _optimalBoost, optimalRatioRepay: _optimalRepay, user: msg.sender, boostEnabled: _boostEnabled }); changeIndex++; if (subInfo.subscribed) { subscribers[subInfo.arrPos] = subscription; emit Updated(msg.sender); emit ParamUpdates(msg.sender, _minRatio, localMaxRatio, _optimalBoost, _optimalRepay, _boostEnabled); } else { subscribers.push(subscription); subInfo.arrPos = subscribers.length - 1; subInfo.subscribed = true; emit Subscribed(msg.sender); } } /// @notice Called by the users DSProxy /// @dev Owner who subscribed cancels his subscription function unsubscribe() external { _unsubscribe(msg.sender); } /// @dev Checks limit if minRatio is bigger than max /// @param _minRatio Minimum ratio, bellow which repay can be triggered /// @param _maxRatio Maximum ratio, over which boost can be triggered /// @return Returns bool if the params are correct function checkParams(uint128 _minRatio, uint128 _maxRatio) internal pure returns (bool) { if (_minRatio > _maxRatio) { return false; } return true; } /// @dev Internal method to remove a subscriber from the list /// @param _user The actual address that owns the Aave position function _unsubscribe(address _user) internal { require(subscribers.length > 0, "Must have subscribers in the list"); SubPosition storage subInfo = subscribersPos[_user]; require(subInfo.subscribed, "Must first be subscribed"); address lastOwner = subscribers[subscribers.length - 1].user; SubPosition storage subInfo2 = subscribersPos[lastOwner]; subInfo2.arrPos = subInfo.arrPos; subscribers[subInfo.arrPos] = subscribers[subscribers.length - 1]; subscribers.pop(); // remove last element and reduce arr length changeIndex++; subInfo.subscribed = false; subInfo.arrPos = 0; emit Unsubscribed(msg.sender); } /// @dev Checks if the user is subscribed /// @param _user The actual address that owns the Aave position /// @return If the user is subscribed function isSubscribed(address _user) public view returns (bool) { SubPosition storage subInfo = subscribersPos[_user]; return subInfo.subscribed; } /// @dev Returns subscribtion information about a user /// @param _user The actual address that owns the Aave position /// @return Subscription information about the user if exists function getHolder(address _user) public view returns (AaveHolder memory) { SubPosition storage subInfo = subscribersPos[_user]; return subscribers[subInfo.arrPos]; } /// @notice Helper method to return all the subscribed CDPs /// @return List of all subscribers function getSubscribers() public view returns (AaveHolder[] memory) { return subscribers; } /// @notice Helper method for the frontend, returns all the subscribed CDPs paginated /// @param _page What page of subscribers you want /// @param _perPage Number of entries per page /// @return List of all subscribers for that page function getSubscribersByPage(uint _page, uint _perPage) public view returns (AaveHolder[] memory) { AaveHolder[] memory holders = new AaveHolder[](_perPage); uint start = _page * _perPage; uint end = start + _perPage; end = (end > holders.length) ? holders.length : end; uint count = 0; for (uint i = start; i < end; i++) { holders[count] = subscribers[i]; count++; } return holders; } ////////////// ADMIN METHODS /////////////////// /// @notice Admin function to unsubscribe a position /// @param _user The actual address that owns the Aave position function unsubscribeByAdmin(address _user) public onlyOwner { SubPosition storage subInfo = subscribersPos[_user]; if (subInfo.subscribed) { _unsubscribe(_user); } } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "./AaveSafetyRatio.sol"; contract AaveLoanInfo is AaveSafetyRatio { struct LoanData { address user; uint128 ratio; address[] collAddr; address[] borrowAddr; uint256[] collAmounts; uint256[] borrowAmounts; } struct TokenInfo { address aTokenAddress; address underlyingTokenAddress; uint256 collateralFactor; uint256 price; } struct TokenInfoFull { address aTokenAddress; address underlyingTokenAddress; uint256 supplyRate; uint256 borrowRate; uint256 borrowRateStable; uint256 totalSupply; uint256 availableLiquidity; uint256 totalBorrow; uint256 collateralFactor; uint256 liquidationRatio; uint256 price; bool usageAsCollateralEnabled; } struct UserToken { address token; uint256 balance; uint256 borrows; uint256 borrowRateMode; uint256 borrowRate; bool enabledAsCollateral; } /// @notice Calcualted the ratio of coll/debt for a compound user /// @param _user Address of the user function getRatio(address _user) public view returns (uint256) { // For each asset the account is in return getSafetyRatio(_user); } /// @notice Fetches Aave prices for tokens /// @param _tokens Arr. of tokens for which to get the prices /// @return prices Array of prices function getPrices(address[] memory _tokens) public view returns (uint256[] memory prices) { address priceOracleAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getPriceOracle(); prices = new uint[](_tokens.length); for (uint256 i = 0; i < _tokens.length; ++i) { prices[i] = IPriceOracleGetterAave(priceOracleAddress).getAssetPrice(_tokens[i]); } } /// @notice Fetches Aave collateral factors for tokens /// @param _tokens Arr. of tokens for which to get the coll. factors /// @return collFactors Array of coll. factors function getCollFactors(address[] memory _tokens) public view returns (uint256[] memory collFactors) { address lendingPoolCoreAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPoolCore(); collFactors = new uint256[](_tokens.length); for (uint256 i = 0; i < _tokens.length; ++i) { (,collFactors[i],,) = ILendingPool(lendingPoolCoreAddress).getReserveConfiguration(_tokens[i]); } } function getTokenBalances(address _user, address[] memory _tokens) public view returns (UserToken[] memory userTokens) { address lendingPoolAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPool(); userTokens = new UserToken[](_tokens.length); for (uint256 i = 0; i < _tokens.length; i++) { address asset = _tokens[i]; userTokens[i].token = asset; (userTokens[i].balance, userTokens[i].borrows,,userTokens[i].borrowRateMode,userTokens[i].borrowRate,,,,,userTokens[i].enabledAsCollateral) = ILendingPool(lendingPoolAddress).getUserReserveData(asset, _user); } } /// @notice Calcualted the ratio of coll/debt for an aave user /// @param _users Addresses of the user /// @return ratios Array of ratios function getRatios(address[] memory _users) public view returns (uint256[] memory ratios) { ratios = new uint256[](_users.length); for (uint256 i = 0; i < _users.length; ++i) { ratios[i] = getSafetyRatio(_users[i]); } } /// @notice Information about reserves /// @param _tokenAddresses Array of tokens addresses /// @return tokens Array of reserves infomartion function getTokensInfo(address[] memory _tokenAddresses) public view returns(TokenInfo[] memory tokens) { address lendingPoolCoreAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPoolCore(); address priceOracleAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getPriceOracle(); tokens = new TokenInfo[](_tokenAddresses.length); for (uint256 i = 0; i < _tokenAddresses.length; ++i) { (,uint256 ltv,,) = ILendingPool(lendingPoolCoreAddress).getReserveConfiguration(_tokenAddresses[i]); tokens[i] = TokenInfo({ aTokenAddress: ILendingPool(lendingPoolCoreAddress).getReserveATokenAddress(_tokenAddresses[i]), underlyingTokenAddress: _tokenAddresses[i], collateralFactor: ltv, price: IPriceOracleGetterAave(priceOracleAddress).getAssetPrice(_tokenAddresses[i]) }); } } /// @notice Information about reserves /// @param _tokenAddresses Array of token addresses /// @return tokens Array of reserves infomartion function getFullTokensInfo(address[] memory _tokenAddresses) public view returns(TokenInfoFull[] memory tokens) { address lendingPoolCoreAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPoolCore(); address lendingPoolAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPool(); address priceOracleAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getPriceOracle(); tokens = new TokenInfoFull[](_tokenAddresses.length); for (uint256 i = 0; i < _tokenAddresses.length; ++i) { (uint256 ltv, uint256 liqRatio,,, bool usageAsCollateralEnabled, bool borrowingEnabled, bool stableBorrowingEnabled,) = ILendingPool(lendingPoolAddress).getReserveConfigurationData(_tokenAddresses[i]); tokens[i] = TokenInfoFull({ aTokenAddress: ILendingPool(lendingPoolCoreAddress).getReserveATokenAddress(_tokenAddresses[i]), underlyingTokenAddress: _tokenAddresses[i], supplyRate: ILendingPool(lendingPoolCoreAddress).getReserveCurrentLiquidityRate(_tokenAddresses[i]), borrowRate: borrowingEnabled ? ILendingPool(lendingPoolCoreAddress).getReserveCurrentVariableBorrowRate(_tokenAddresses[i]) : 0, borrowRateStable: stableBorrowingEnabled ? ILendingPool(lendingPoolCoreAddress).getReserveCurrentStableBorrowRate(_tokenAddresses[i]) : 0, totalSupply: ILendingPool(lendingPoolCoreAddress).getReserveTotalLiquidity(_tokenAddresses[i]), availableLiquidity: ILendingPool(lendingPoolCoreAddress).getReserveAvailableLiquidity(_tokenAddresses[i]), totalBorrow: ILendingPool(lendingPoolCoreAddress).getReserveTotalBorrowsVariable(_tokenAddresses[i]) + ILendingPool(lendingPoolCoreAddress).getReserveTotalBorrowsStable(_tokenAddresses[i]), collateralFactor: ltv, liquidationRatio: liqRatio, price: IPriceOracleGetterAave(priceOracleAddress).getAssetPrice(_tokenAddresses[i]), usageAsCollateralEnabled: usageAsCollateralEnabled }); } } /// @notice Fetches all the collateral/debt address and amounts, denominated in ether /// @param _user Address of the user /// @return data LoanData information function getLoanData(address _user) public view returns (LoanData memory data) { address lendingPoolAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPool(); address priceOracleAddress = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getPriceOracle(); address[] memory reserves = ILendingPool(lendingPoolAddress).getReserves(); data = LoanData({ user: _user, ratio: 0, collAddr: new address[](reserves.length), borrowAddr: new address[](reserves.length), collAmounts: new uint[](reserves.length), borrowAmounts: new uint[](reserves.length) }); uint64 collPos = 0; uint64 borrowPos = 0; for (uint64 i = 0; i < reserves.length; i++) { address reserve = reserves[i]; (uint256 aTokenBalance, uint256 borrowBalance,,,,,,,,) = ILendingPool(lendingPoolAddress).getUserReserveData(reserve, _user); uint256 price = IPriceOracleGetterAave(priceOracleAddress).getAssetPrice(reserves[i]); if (aTokenBalance > 0) { uint256 userTokenBalanceEth = wmul(aTokenBalance, price) * (10 ** (18 - _getDecimals(reserve))); data.collAddr[collPos] = reserve; data.collAmounts[collPos] = userTokenBalanceEth; collPos++; } // Sum up debt in Eth if (borrowBalance > 0) { uint256 userBorrowBalanceEth = wmul(borrowBalance, price) * (10 ** (18 - _getDecimals(reserve))); data.borrowAddr[borrowPos] = reserve; data.borrowAmounts[borrowPos] = userBorrowBalanceEth; borrowPos++; } } data.ratio = uint128(getSafetyRatio(_user)); return data; } /// @notice Fetches all the collateral/debt address and amounts, denominated in ether /// @param _users Addresses of the user /// @return loans Array of LoanData information function getLoanDataArr(address[] memory _users) public view returns (LoanData[] memory loans) { loans = new LoanData[](_users.length); for (uint i = 0; i < _users.length; ++i) { loans[i] = getLoanData(_users[i]); } } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../interfaces/GasTokenInterface.sol"; import "./DFSExchangeCore.sol"; import "../DS/DSMath.sol"; import "../loggers/DefisaverLogger.sol"; import "../auth/AdminAuth.sol"; import "../utils/GasBurner.sol"; import "../utils/SafeERC20.sol"; contract DFSExchange is DFSExchangeCore, AdminAuth, GasBurner { using SafeERC20 for ERC20; uint256 public constant SERVICE_FEE = 800; // 0.125% Fee // solhint-disable-next-line const-name-snakecase DefisaverLogger public constant logger = DefisaverLogger(0x5c55B921f590a89C1Ebe84dF170E655a82b62126); uint public burnAmount = 10; /// @notice Takes a src amount of tokens and converts it into the dest token /// @dev Takes fee from the _srcAmount before the exchange /// @param exData [srcAddr, destAddr, srcAmount, destAmount, minPrice, exchangeType, exchangeAddr, callData, price0x] /// @param _user User address who called the exchange function sell(ExchangeData memory exData, address payable _user) public payable burnGas(burnAmount) { exData.dfsFeeDivider = SERVICE_FEE; exData.user = _user; // Perform the exchange (address wrapper, uint destAmount) = _sell(exData); // send back any leftover ether or tokens sendLeftover(exData.srcAddr, exData.destAddr, _user); // log the event logger.Log(address(this), msg.sender, "ExchangeSell", abi.encode(wrapper, exData.srcAddr, exData.destAddr, exData.srcAmount, destAmount)); } /// @notice Takes a dest amount of tokens and converts it from the src token /// @dev Send always more than needed for the swap, extra will be returned /// @param exData [srcAddr, destAddr, srcAmount, destAmount, minPrice, exchangeType, exchangeAddr, callData, price0x] /// @param _user User address who called the exchange function buy(ExchangeData memory exData, address payable _user) public payable burnGas(burnAmount){ exData.dfsFeeDivider = SERVICE_FEE; exData.user = _user; // Perform the exchange (address wrapper, uint srcAmount) = _buy(exData); // send back any leftover ether or tokens sendLeftover(exData.srcAddr, exData.destAddr, _user); // log the event logger.Log(address(this), msg.sender, "ExchangeBuy", abi.encode(wrapper, exData.srcAddr, exData.destAddr, srcAmount, exData.destAmount)); } /// @notice Changes the amount of gas token we burn for each call /// @dev Only callable by the owner /// @param _newBurnAmount New amount of gas tokens to be burned function changeBurnAmount(uint _newBurnAmount) public { require(owner == msg.sender); burnAmount = _newBurnAmount; } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../auth/AdminAuth.sol"; import "./DFSExchange.sol"; import "../utils/SafeERC20.sol"; contract AllowanceProxy is AdminAuth { using SafeERC20 for ERC20; address public constant KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; DFSExchange dfsExchange = DFSExchange(0xc2Ce04e2FB4DD20964b4410FcE718b95963a1587); function callSell(DFSExchangeCore.ExchangeData memory exData) public payable { pullAndSendTokens(exData.srcAddr, exData.srcAmount); dfsExchange.sell{value: msg.value}(exData, msg.sender); } function callBuy(DFSExchangeCore.ExchangeData memory exData) public payable { pullAndSendTokens(exData.srcAddr, exData.srcAmount); dfsExchange.buy{value: msg.value}(exData, msg.sender); } function pullAndSendTokens(address _tokenAddr, uint _amount) internal { if (_tokenAddr == KYBER_ETH_ADDRESS) { require(msg.value >= _amount, "msg.value smaller than amount"); } else { ERC20(_tokenAddr).safeTransferFrom(msg.sender, address(dfsExchange), _amount); } } function ownerChangeExchange(address payable _newExchange) public onlyOwner { dfsExchange = DFSExchange(_newExchange); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../utils/BotRegistry.sol"; import "../../utils/GasBurner.sol"; import "./CompoundMonitorProxy.sol"; import "./CompoundSubscriptions.sol"; import "../../interfaces/GasTokenInterface.sol"; import "../../DS/DSMath.sol"; import "../../auth/AdminAuth.sol"; import "../../loggers/DefisaverLogger.sol"; import "../CompoundSafetyRatio.sol"; import "../../exchange/SaverExchangeCore.sol"; /// @title Contract implements logic of calling boost/repay in the automatic system contract CompoundMonitor is AdminAuth, DSMath, CompoundSafetyRatio, GasBurner { using SafeERC20 for ERC20; enum Method { Boost, Repay } uint public REPAY_GAS_TOKEN = 20; uint public BOOST_GAS_TOKEN = 20; uint constant public MAX_GAS_PRICE = 500000000000; // 500 gwei uint public REPAY_GAS_COST = 1500000; uint public BOOST_GAS_COST = 1000000; address public constant GAS_TOKEN_INTERFACE_ADDRESS = 0x0000000000b3F879cb30FE243b4Dfee438691c04; address public constant DEFISAVER_LOGGER = 0x5c55B921f590a89C1Ebe84dF170E655a82b62126; address public constant BOT_REGISTRY_ADDRESS = 0x637726f8b08a7ABE3aE3aCaB01A80E2d8ddeF77B; CompoundMonitorProxy public compoundMonitorProxy; CompoundSubscriptions public subscriptionsContract; address public compoundFlashLoanTakerAddress; DefisaverLogger public logger = DefisaverLogger(DEFISAVER_LOGGER); modifier onlyApproved() { require(BotRegistry(BOT_REGISTRY_ADDRESS).botList(msg.sender), "Not auth bot"); _; } /// @param _compoundMonitorProxy Proxy contracts that actually is authorized to call DSProxy /// @param _subscriptions Subscriptions contract for Compound positions /// @param _compoundFlashLoanTaker Contract that actually performs Repay/Boost constructor(address _compoundMonitorProxy, address _subscriptions, address _compoundFlashLoanTaker) public { compoundMonitorProxy = CompoundMonitorProxy(_compoundMonitorProxy); subscriptionsContract = CompoundSubscriptions(_subscriptions); compoundFlashLoanTakerAddress = _compoundFlashLoanTaker; } /// @notice Bots call this method to repay for user when conditions are met /// @dev If the contract ownes gas token it will try and use it for gas price reduction /// @param _exData Exchange data /// @param _cAddresses cTokens addreses and exchange [cCollAddress, cBorrowAddress, exchangeAddress] /// @param _user The actual address that owns the Compound position function repayFor( SaverExchangeCore.ExchangeData memory _exData, address[2] memory _cAddresses, // cCollAddress, cBorrowAddress address _user ) public payable onlyApproved burnGas(REPAY_GAS_TOKEN) { (bool isAllowed, uint ratioBefore) = canCall(Method.Repay, _user); require(isAllowed); // check if conditions are met uint256 gasCost = calcGasCost(REPAY_GAS_COST); compoundMonitorProxy.callExecute{value: msg.value}( _user, compoundFlashLoanTakerAddress, abi.encodeWithSignature( "repayWithLoan((address,address,uint256,uint256,uint256,address,address,bytes,uint256),address[2],uint256)", _exData, _cAddresses, gasCost ) ); (bool isGoodRatio, uint ratioAfter) = ratioGoodAfter(Method.Repay, _user); require(isGoodRatio); // check if the after result of the actions is good returnEth(); logger.Log(address(this), _user, "AutomaticCompoundRepay", abi.encode(ratioBefore, ratioAfter)); } /// @notice Bots call this method to boost for user when conditions are met /// @dev If the contract ownes gas token it will try and use it for gas price reduction /// @param _exData Exchange data /// @param _cAddresses cTokens addreses and exchange [cCollAddress, cBorrowAddress, exchangeAddress] /// @param _user The actual address that owns the Compound position function boostFor( SaverExchangeCore.ExchangeData memory _exData, address[2] memory _cAddresses, // cCollAddress, cBorrowAddress address _user ) public payable onlyApproved burnGas(BOOST_GAS_TOKEN) { (bool isAllowed, uint ratioBefore) = canCall(Method.Boost, _user); require(isAllowed); // check if conditions are met uint256 gasCost = calcGasCost(BOOST_GAS_COST); compoundMonitorProxy.callExecute{value: msg.value}( _user, compoundFlashLoanTakerAddress, abi.encodeWithSignature( "boostWithLoan((address,address,uint256,uint256,uint256,address,address,bytes,uint256),address[2],uint256)", _exData, _cAddresses, gasCost ) ); (bool isGoodRatio, uint ratioAfter) = ratioGoodAfter(Method.Boost, _user); require(isGoodRatio); // check if the after result of the actions is good returnEth(); logger.Log(address(this), _user, "AutomaticCompoundBoost", abi.encode(ratioBefore, ratioAfter)); } /******************* INTERNAL METHODS ********************************/ function returnEth() internal { // return if some eth left if (address(this).balance > 0) { msg.sender.transfer(address(this).balance); } } /******************* STATIC METHODS ********************************/ /// @notice Checks if Boost/Repay could be triggered for the CDP /// @dev Called by MCDMonitor to enforce the min/max check /// @param _method Type of action to be called /// @param _user The actual address that owns the Compound position /// @return Boolean if it can be called and the ratio function canCall(Method _method, address _user) public view returns(bool, uint) { bool subscribed = subscriptionsContract.isSubscribed(_user); CompoundSubscriptions.CompoundHolder memory holder = subscriptionsContract.getHolder(_user); // check if cdp is subscribed if (!subscribed) return (false, 0); // check if boost and boost allowed if (_method == Method.Boost && !holder.boostEnabled) return (false, 0); uint currRatio = getSafetyRatio(_user); if (_method == Method.Repay) { return (currRatio < holder.minRatio, currRatio); } else if (_method == Method.Boost) { return (currRatio > holder.maxRatio, currRatio); } } /// @dev After the Boost/Repay check if the ratio doesn't trigger another call /// @param _method Type of action to be called /// @param _user The actual address that owns the Compound position /// @return Boolean if the recent action preformed correctly and the ratio function ratioGoodAfter(Method _method, address _user) public view returns(bool, uint) { CompoundSubscriptions.CompoundHolder memory holder; holder= subscriptionsContract.getHolder(_user); uint currRatio = getSafetyRatio(_user); if (_method == Method.Repay) { return (currRatio < holder.maxRatio, currRatio); } else if (_method == Method.Boost) { return (currRatio > holder.minRatio, currRatio); } } /// @notice Calculates gas cost (in Eth) of tx /// @dev Gas price is limited to MAX_GAS_PRICE to prevent attack of draining user CDP /// @param _gasAmount Amount of gas used for the tx function calcGasCost(uint _gasAmount) public view returns (uint) { uint gasPrice = tx.gasprice <= MAX_GAS_PRICE ? tx.gasprice : MAX_GAS_PRICE; return mul(gasPrice, _gasAmount); } /******************* OWNER ONLY OPERATIONS ********************************/ /// @notice As the code is new, have a emergancy admin saver proxy change function changeCompoundFlashLoanTaker(address _newCompoundFlashLoanTakerAddress) public onlyAdmin { compoundFlashLoanTakerAddress = _newCompoundFlashLoanTakerAddress; } /// @notice Allows owner to change gas cost for boost operation, but only up to 3 millions /// @param _gasCost New gas cost for boost method function changeBoostGasCost(uint _gasCost) public onlyOwner { require(_gasCost < 3000000); BOOST_GAS_COST = _gasCost; } /// @notice Allows owner to change gas cost for repay operation, but only up to 3 millions /// @param _gasCost New gas cost for repay method function changeRepayGasCost(uint _gasCost) public onlyOwner { require(_gasCost < 3000000); REPAY_GAS_COST = _gasCost; } /// @notice If any tokens gets stuck in the contract owner can withdraw it /// @param _tokenAddress Address of the ERC20 token /// @param _to Address of the receiver /// @param _amount The amount to be sent function transferERC20(address _tokenAddress, address _to, uint _amount) public onlyOwner { ERC20(_tokenAddress).safeTransfer(_to, _amount); } /// @notice If any Eth gets stuck in the contract owner can withdraw it /// @param _to Address of the receiver /// @param _amount The amount to be sent function transferEth(address payable _to, uint _amount) public onlyOwner { _to.transfer(_amount); } }
pragma solidity ^0.6.0; import "../../interfaces/DSProxyInterface.sol"; import "../../utils/SafeERC20.sol"; import "../../auth/AdminAuth.sol"; /// @title Contract with the actuall DSProxy permission calls the automation operations contract CompoundMonitorProxy is AdminAuth { using SafeERC20 for ERC20; uint public CHANGE_PERIOD; address public monitor; address public newMonitor; address public lastMonitor; uint public changeRequestedTimestamp; mapping(address => bool) public allowed; event MonitorChangeInitiated(address oldMonitor, address newMonitor); event MonitorChangeCanceled(); event MonitorChangeFinished(address monitor); event MonitorChangeReverted(address monitor); // if someone who is allowed become malicious, owner can't be changed modifier onlyAllowed() { require(allowed[msg.sender] || msg.sender == owner); _; } modifier onlyMonitor() { require (msg.sender == monitor); _; } constructor(uint _changePeriod) public { CHANGE_PERIOD = _changePeriod * 1 days; } /// @notice Only monitor contract is able to call execute on users proxy /// @param _owner Address of cdp owner (users DSProxy address) /// @param _compoundSaverProxy Address of CompoundSaverProxy /// @param _data Data to send to CompoundSaverProxy function callExecute(address _owner, address _compoundSaverProxy, bytes memory _data) public payable onlyMonitor { // execute reverts if calling specific method fails DSProxyInterface(_owner).execute{value: msg.value}(_compoundSaverProxy, _data); // return if anything left if (address(this).balance > 0) { msg.sender.transfer(address(this).balance); } } /// @notice Allowed users are able to set Monitor contract without any waiting period first time /// @param _monitor Address of Monitor contract function setMonitor(address _monitor) public onlyAllowed { require(monitor == address(0)); monitor = _monitor; } /// @notice Allowed users are able to start procedure for changing monitor /// @dev after CHANGE_PERIOD needs to call confirmNewMonitor to actually make a change /// @param _newMonitor address of new monitor function changeMonitor(address _newMonitor) public onlyAllowed { require(changeRequestedTimestamp == 0); changeRequestedTimestamp = now; lastMonitor = monitor; newMonitor = _newMonitor; emit MonitorChangeInitiated(lastMonitor, newMonitor); } /// @notice At any point allowed users are able to cancel monitor change function cancelMonitorChange() public onlyAllowed { require(changeRequestedTimestamp > 0); changeRequestedTimestamp = 0; newMonitor = address(0); emit MonitorChangeCanceled(); } /// @notice Anyone is able to confirm new monitor after CHANGE_PERIOD if process is started function confirmNewMonitor() public onlyAllowed { require((changeRequestedTimestamp + CHANGE_PERIOD) < now); require(changeRequestedTimestamp != 0); require(newMonitor != address(0)); monitor = newMonitor; newMonitor = address(0); changeRequestedTimestamp = 0; emit MonitorChangeFinished(monitor); } /// @notice Its possible to revert monitor to last used monitor function revertMonitor() public onlyAllowed { require(lastMonitor != address(0)); monitor = lastMonitor; emit MonitorChangeReverted(monitor); } /// @notice Allowed users are able to add new allowed user /// @param _user Address of user that will be allowed function addAllowed(address _user) public onlyAllowed { allowed[_user] = true; } /// @notice Allowed users are able to remove allowed user /// @dev owner is always allowed even if someone tries to remove it from allowed mapping /// @param _user Address of allowed user function removeAllowed(address _user) public onlyAllowed { allowed[_user] = false; } function setChangePeriod(uint _periodInDays) public onlyAllowed { require(_periodInDays * 1 days > CHANGE_PERIOD); CHANGE_PERIOD = _periodInDays * 1 days; } /// @notice In case something is left in contract, owner is able to withdraw it /// @param _token address of token to withdraw balance function withdrawToken(address _token) public onlyOwner { uint balance = ERC20(_token).balanceOf(address(this)); ERC20(_token).safeTransfer(msg.sender, balance); } /// @notice In case something is left in contract, owner is able to withdraw it function withdrawEth() public onlyOwner { uint balance = address(this).balance; msg.sender.transfer(balance); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../auth/AdminAuth.sol"; /// @title Stores subscription information for Compound automatization contract CompoundSubscriptions is AdminAuth { struct CompoundHolder { address user; uint128 minRatio; uint128 maxRatio; uint128 optimalRatioBoost; uint128 optimalRatioRepay; bool boostEnabled; } struct SubPosition { uint arrPos; bool subscribed; } CompoundHolder[] public subscribers; mapping (address => SubPosition) public subscribersPos; uint public changeIndex; event Subscribed(address indexed user); event Unsubscribed(address indexed user); event Updated(address indexed user); event ParamUpdates(address indexed user, uint128, uint128, uint128, uint128, bool); /// @dev Called by the DSProxy contract which owns the Compound position /// @notice Adds the users Compound poistion in the list of subscriptions so it can be monitored /// @param _minRatio Minimum ratio below which repay is triggered /// @param _maxRatio Maximum ratio after which boost is triggered /// @param _optimalBoost Ratio amount which boost should target /// @param _optimalRepay Ratio amount which repay should target /// @param _boostEnabled Boolean determing if boost is enabled function subscribe(uint128 _minRatio, uint128 _maxRatio, uint128 _optimalBoost, uint128 _optimalRepay, bool _boostEnabled) external { // if boost is not enabled, set max ratio to max uint uint128 localMaxRatio = _boostEnabled ? _maxRatio : uint128(-1); require(checkParams(_minRatio, localMaxRatio), "Must be correct params"); SubPosition storage subInfo = subscribersPos[msg.sender]; CompoundHolder memory subscription = CompoundHolder({ minRatio: _minRatio, maxRatio: localMaxRatio, optimalRatioBoost: _optimalBoost, optimalRatioRepay: _optimalRepay, user: msg.sender, boostEnabled: _boostEnabled }); changeIndex++; if (subInfo.subscribed) { subscribers[subInfo.arrPos] = subscription; emit Updated(msg.sender); emit ParamUpdates(msg.sender, _minRatio, localMaxRatio, _optimalBoost, _optimalRepay, _boostEnabled); } else { subscribers.push(subscription); subInfo.arrPos = subscribers.length - 1; subInfo.subscribed = true; emit Subscribed(msg.sender); } } /// @notice Called by the users DSProxy /// @dev Owner who subscribed cancels his subscription function unsubscribe() external { _unsubscribe(msg.sender); } /// @dev Checks limit if minRatio is bigger than max /// @param _minRatio Minimum ratio, bellow which repay can be triggered /// @param _maxRatio Maximum ratio, over which boost can be triggered /// @return Returns bool if the params are correct function checkParams(uint128 _minRatio, uint128 _maxRatio) internal pure returns (bool) { if (_minRatio > _maxRatio) { return false; } return true; } /// @dev Internal method to remove a subscriber from the list /// @param _user The actual address that owns the Compound position function _unsubscribe(address _user) internal { require(subscribers.length > 0, "Must have subscribers in the list"); SubPosition storage subInfo = subscribersPos[_user]; require(subInfo.subscribed, "Must first be subscribed"); address lastOwner = subscribers[subscribers.length - 1].user; SubPosition storage subInfo2 = subscribersPos[lastOwner]; subInfo2.arrPos = subInfo.arrPos; subscribers[subInfo.arrPos] = subscribers[subscribers.length - 1]; subscribers.pop(); // remove last element and reduce arr length changeIndex++; subInfo.subscribed = false; subInfo.arrPos = 0; emit Unsubscribed(msg.sender); } /// @dev Checks if the user is subscribed /// @param _user The actual address that owns the Compound position /// @return If the user is subscribed function isSubscribed(address _user) public view returns (bool) { SubPosition storage subInfo = subscribersPos[_user]; return subInfo.subscribed; } /// @dev Returns subscribtion information about a user /// @param _user The actual address that owns the Compound position /// @return Subscription information about the user if exists function getHolder(address _user) public view returns (CompoundHolder memory) { SubPosition storage subInfo = subscribersPos[_user]; return subscribers[subInfo.arrPos]; } /// @notice Helper method to return all the subscribed CDPs /// @return List of all subscribers function getSubscribers() public view returns (CompoundHolder[] memory) { return subscribers; } /// @notice Helper method for the frontend, returns all the subscribed CDPs paginated /// @param _page What page of subscribers you want /// @param _perPage Number of entries per page /// @return List of all subscribers for that page function getSubscribersByPage(uint _page, uint _perPage) public view returns (CompoundHolder[] memory) { CompoundHolder[] memory holders = new CompoundHolder[](_perPage); uint start = _page * _perPage; uint end = start + _perPage; end = (end > holders.length) ? holders.length : end; uint count = 0; for (uint i = start; i < end; i++) { holders[count] = subscribers[i]; count++; } return holders; } ////////////// ADMIN METHODS /////////////////// /// @notice Admin function to unsubscribe a CDP /// @param _user The actual address that owns the Compound position function unsubscribeByAdmin(address _user) public onlyOwner { SubPosition storage subInfo = subscribersPos[_user]; if (subInfo.subscribed) { _unsubscribe(_user); } } }
pragma solidity ^0.6.0; import "../DS/DSMath.sol"; import "../interfaces/CompoundOracleInterface.sol"; import "../interfaces/ComptrollerInterface.sol"; import "../interfaces/CTokenInterface.sol"; import "./helpers/Exponential.sol"; contract CompoundSafetyRatio is Exponential, DSMath { // solhint-disable-next-line const-name-snakecase ComptrollerInterface public constant comp = ComptrollerInterface(0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B); /// @notice Calcualted the ratio of debt / adjusted collateral /// @param _user Address of the user function getSafetyRatio(address _user) public view returns (uint) { // For each asset the account is in address[] memory assets = comp.getAssetsIn(_user); address oracleAddr = comp.oracle(); uint sumCollateral = 0; uint sumBorrow = 0; for (uint i = 0; i < assets.length; i++) { address asset = assets[i]; (, uint cTokenBalance, uint borrowBalance, uint exchangeRateMantissa) = CTokenInterface(asset).getAccountSnapshot(_user); Exp memory oraclePrice; if (cTokenBalance != 0 || borrowBalance != 0) { oraclePrice = Exp({mantissa: CompoundOracleInterface(oracleAddr).getUnderlyingPrice(asset)}); } // Sum up collateral in Usd if (cTokenBalance != 0) { (, uint collFactorMantissa) = comp.markets(address(asset)); Exp memory collateralFactor = Exp({mantissa: collFactorMantissa}); Exp memory exchangeRate = Exp({mantissa: exchangeRateMantissa}); (, Exp memory tokensToUsd) = mulExp3(collateralFactor, exchangeRate, oraclePrice); (, sumCollateral) = mulScalarTruncateAddUInt(tokensToUsd, cTokenBalance, sumCollateral); } // Sum up debt in Usd if (borrowBalance != 0) { (, sumBorrow) = mulScalarTruncateAddUInt(oraclePrice, borrowBalance, sumBorrow); } } if (sumBorrow == 0) return uint(-1); uint borrowPowerUsed = (sumBorrow * 10**18) / sumCollateral; return wdiv(1e18, borrowPowerUsed); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "./CompoundSafetyRatio.sol"; import "./helpers/CompoundSaverHelper.sol"; /// @title Gets data about Compound positions contract CompoundLoanInfo is CompoundSafetyRatio { struct LoanData { address user; uint128 ratio; address[] collAddr; address[] borrowAddr; uint[] collAmounts; uint[] borrowAmounts; } struct TokenInfo { address cTokenAddress; address underlyingTokenAddress; uint collateralFactor; uint price; } struct TokenInfoFull { address underlyingTokenAddress; uint supplyRate; uint borrowRate; uint exchangeRate; uint marketLiquidity; uint totalSupply; uint totalBorrow; uint collateralFactor; uint price; uint borrowCap; } address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant CETH_ADDRESS = 0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5; /// @notice Calcualted the ratio of coll/debt for a compound user /// @param _user Address of the user function getRatio(address _user) public view returns (uint) { // For each asset the account is in return getSafetyRatio(_user); } /// @notice Fetches Compound prices for tokens /// @param _cTokens Arr. of cTokens for which to get the prices /// @return prices Array of prices function getPrices(address[] memory _cTokens) public view returns (uint[] memory prices) { prices = new uint[](_cTokens.length); address oracleAddr = comp.oracle(); for (uint i = 0; i < _cTokens.length; ++i) { prices[i] = CompoundOracleInterface(oracleAddr).getUnderlyingPrice(_cTokens[i]); } } /// @notice Fetches Compound collateral factors for tokens /// @param _cTokens Arr. of cTokens for which to get the coll. factors /// @return collFactors Array of coll. factors function getCollFactors(address[] memory _cTokens) public view returns (uint[] memory collFactors) { collFactors = new uint[](_cTokens.length); for (uint i = 0; i < _cTokens.length; ++i) { (, collFactors[i]) = comp.markets(_cTokens[i]); } } /// @notice Fetches all the collateral/debt address and amounts, denominated in usd /// @param _user Address of the user /// @return data LoanData information function getLoanData(address _user) public view returns (LoanData memory data) { address[] memory assets = comp.getAssetsIn(_user); address oracleAddr = comp.oracle(); data = LoanData({ user: _user, ratio: 0, collAddr: new address[](assets.length), borrowAddr: new address[](assets.length), collAmounts: new uint[](assets.length), borrowAmounts: new uint[](assets.length) }); uint collPos = 0; uint borrowPos = 0; for (uint i = 0; i < assets.length; i++) { address asset = assets[i]; (, uint cTokenBalance, uint borrowBalance, uint exchangeRateMantissa) = CTokenInterface(asset).getAccountSnapshot(_user); Exp memory oraclePrice; if (cTokenBalance != 0 || borrowBalance != 0) { oraclePrice = Exp({mantissa: CompoundOracleInterface(oracleAddr).getUnderlyingPrice(asset)}); } // Sum up collateral in Usd if (cTokenBalance != 0) { Exp memory exchangeRate = Exp({mantissa: exchangeRateMantissa}); (, Exp memory tokensToUsd) = mulExp(exchangeRate, oraclePrice); data.collAddr[collPos] = asset; (, data.collAmounts[collPos]) = mulScalarTruncate(tokensToUsd, cTokenBalance); collPos++; } // Sum up debt in Usd if (borrowBalance != 0) { data.borrowAddr[borrowPos] = asset; (, data.borrowAmounts[borrowPos]) = mulScalarTruncate(oraclePrice, borrowBalance); borrowPos++; } } data.ratio = uint128(getSafetyRatio(_user)); return data; } function getTokenBalances(address _user, address[] memory _cTokens) public view returns (uint[] memory balances, uint[] memory borrows) { balances = new uint[](_cTokens.length); borrows = new uint[](_cTokens.length); for (uint i = 0; i < _cTokens.length; i++) { address asset = _cTokens[i]; (, uint cTokenBalance, uint borrowBalance, uint exchangeRateMantissa) = CTokenInterface(asset).getAccountSnapshot(_user); Exp memory exchangeRate = Exp({mantissa: exchangeRateMantissa}); (, balances[i]) = mulScalarTruncate(exchangeRate, cTokenBalance); borrows[i] = borrowBalance; } } /// @notice Fetches all the collateral/debt address and amounts, denominated in usd /// @param _users Addresses of the user /// @return loans Array of LoanData information function getLoanDataArr(address[] memory _users) public view returns (LoanData[] memory loans) { loans = new LoanData[](_users.length); for (uint i = 0; i < _users.length; ++i) { loans[i] = getLoanData(_users[i]); } } /// @notice Calcualted the ratio of coll/debt for a compound user /// @param _users Addresses of the user /// @return ratios Array of ratios function getRatios(address[] memory _users) public view returns (uint[] memory ratios) { ratios = new uint[](_users.length); for (uint i = 0; i < _users.length; ++i) { ratios[i] = getSafetyRatio(_users[i]); } } /// @notice Information about cTokens /// @param _cTokenAddresses Array of cTokens addresses /// @return tokens Array of cTokens infomartion function getTokensInfo(address[] memory _cTokenAddresses) public returns(TokenInfo[] memory tokens) { tokens = new TokenInfo[](_cTokenAddresses.length); address oracleAddr = comp.oracle(); for (uint i = 0; i < _cTokenAddresses.length; ++i) { (, uint collFactor) = comp.markets(_cTokenAddresses[i]); tokens[i] = TokenInfo({ cTokenAddress: _cTokenAddresses[i], underlyingTokenAddress: getUnderlyingAddr(_cTokenAddresses[i]), collateralFactor: collFactor, price: CompoundOracleInterface(oracleAddr).getUnderlyingPrice(_cTokenAddresses[i]) }); } } /// @notice Information about cTokens /// @param _cTokenAddresses Array of cTokens addresses /// @return tokens Array of cTokens infomartion function getFullTokensInfo(address[] memory _cTokenAddresses) public returns(TokenInfoFull[] memory tokens) { tokens = new TokenInfoFull[](_cTokenAddresses.length); address oracleAddr = comp.oracle(); for (uint i = 0; i < _cTokenAddresses.length; ++i) { (, uint collFactor) = comp.markets(_cTokenAddresses[i]); CTokenInterface cToken = CTokenInterface(_cTokenAddresses[i]); tokens[i] = TokenInfoFull({ underlyingTokenAddress: getUnderlyingAddr(_cTokenAddresses[i]), supplyRate: cToken.supplyRatePerBlock(), borrowRate: cToken.borrowRatePerBlock(), exchangeRate: cToken.exchangeRateCurrent(), marketLiquidity: cToken.getCash(), totalSupply: cToken.totalSupply(), totalBorrow: cToken.totalBorrowsCurrent(), collateralFactor: collFactor, price: CompoundOracleInterface(oracleAddr).getUnderlyingPrice(_cTokenAddresses[i]), borrowCap: comp.borrowCaps(_cTokenAddresses[i]) }); } } /// @notice Returns the underlying address of the cToken asset /// @param _cTokenAddress cToken address /// @return Token address of the cToken specified function getUnderlyingAddr(address _cTokenAddress) internal returns (address) { if (_cTokenAddress == CETH_ADDRESS) { return ETH_ADDRESS; } else { return CTokenInterface(_cTokenAddress).underlying(); } } }
pragma solidity ^0.6.0; import "../../interfaces/CTokenInterface.sol"; import "../../interfaces/ComptrollerInterface.sol"; import "../../utils/SafeERC20.sol"; contract CreamBorrowProxy { using SafeERC20 for ERC20; address public constant ETH_ADDR = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant COMPTROLLER_ADDR = 0x3d5BC3c8d13dcB8bF317092d84783c2697AE9258; function borrow(address _cCollToken, address _cBorrowToken, address _borrowToken, uint _amount) public { address[] memory markets = new address[](2); markets[0] = _cCollToken; markets[1] = _cBorrowToken; ComptrollerInterface(COMPTROLLER_ADDR).enterMarkets(markets); require(CTokenInterface(_cBorrowToken).borrow(_amount) == 0); // withdraw funds to msg.sender if (_borrowToken != ETH_ADDR) { ERC20(_borrowToken).safeTransfer(msg.sender, ERC20(_borrowToken).balanceOf(address(this))); } else { msg.sender.transfer(address(this).balance); } } }
pragma solidity ^0.6.0; import "../DS/DSMath.sol"; import "../interfaces/CompoundOracleInterface.sol"; import "../interfaces/ComptrollerInterface.sol"; import "../interfaces/CTokenInterface.sol"; import "../compound/helpers/Exponential.sol"; contract CreamSafetyRatio is Exponential, DSMath { // solhint-disable-next-line const-name-snakecase ComptrollerInterface public constant comp = ComptrollerInterface(0x3d5BC3c8d13dcB8bF317092d84783c2697AE9258); /// @notice Calcualted the ratio of debt / adjusted collateral /// @param _user Address of the user function getSafetyRatio(address _user) public view returns (uint) { // For each asset the account is in address[] memory assets = comp.getAssetsIn(_user); address oracleAddr = comp.oracle(); uint sumCollateral = 0; uint sumBorrow = 0; for (uint i = 0; i < assets.length; i++) { address asset = assets[i]; (, uint cTokenBalance, uint borrowBalance, uint exchangeRateMantissa) = CTokenInterface(asset).getAccountSnapshot(_user); Exp memory oraclePrice; if (cTokenBalance != 0 || borrowBalance != 0) { oraclePrice = Exp({mantissa: CompoundOracleInterface(oracleAddr).getUnderlyingPrice(asset)}); } // Sum up collateral in Eth if (cTokenBalance != 0) { (, uint collFactorMantissa) = comp.markets(address(asset)); Exp memory collateralFactor = Exp({mantissa: collFactorMantissa}); Exp memory exchangeRate = Exp({mantissa: exchangeRateMantissa}); (, Exp memory tokensToEther) = mulExp3(collateralFactor, exchangeRate, oraclePrice); (, sumCollateral) = mulScalarTruncateAddUInt(tokensToEther, cTokenBalance, sumCollateral); } // Sum up debt in Eth if (borrowBalance != 0) { (, sumBorrow) = mulScalarTruncateAddUInt(oraclePrice, borrowBalance, sumBorrow); } } if (sumBorrow == 0) return uint(-1); uint borrowPowerUsed = (sumBorrow * 10**18) / sumCollateral; return wdiv(1e18, borrowPowerUsed); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "./CreamSafetyRatio.sol"; import "./helpers/CreamSaverHelper.sol"; /// @title Gets data about cream positions contract CreamLoanInfo is CreamSafetyRatio { struct LoanData { address user; uint128 ratio; address[] collAddr; address[] borrowAddr; uint[] collAmounts; uint[] borrowAmounts; } struct TokenInfo { address cTokenAddress; address underlyingTokenAddress; uint collateralFactor; uint price; } struct TokenInfoFull { address underlyingTokenAddress; uint supplyRate; uint borrowRate; uint exchangeRate; uint marketLiquidity; uint totalSupply; uint totalBorrow; uint collateralFactor; uint price; } address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant CETH_ADDRESS = 0xD06527D5e56A3495252A528C4987003b712860eE; /// @notice Calcualted the ratio of coll/debt for a cream user /// @param _user Address of the user function getRatio(address _user) public view returns (uint) { // For each asset the account is in return getSafetyRatio(_user); } /// @notice Fetches cream prices for tokens /// @param _cTokens Arr. of cTokens for which to get the prices /// @return prices Array of prices function getPrices(address[] memory _cTokens) public view returns (uint[] memory prices) { prices = new uint[](_cTokens.length); address oracleAddr = comp.oracle(); for (uint i = 0; i < _cTokens.length; ++i) { prices[i] = CompoundOracleInterface(oracleAddr).getUnderlyingPrice(_cTokens[i]); } } /// @notice Fetches cream collateral factors for tokens /// @param _cTokens Arr. of cTokens for which to get the coll. factors /// @return collFactors Array of coll. factors function getCollFactors(address[] memory _cTokens) public view returns (uint[] memory collFactors) { collFactors = new uint[](_cTokens.length); for (uint i = 0; i < _cTokens.length; ++i) { (, collFactors[i]) = comp.markets(_cTokens[i]); } } /// @notice Fetches all the collateral/debt address and amounts, denominated in eth /// @param _user Address of the user /// @return data LoanData information function getLoanData(address _user) public view returns (LoanData memory data) { address[] memory assets = comp.getAssetsIn(_user); address oracleAddr = comp.oracle(); data = LoanData({ user: _user, ratio: 0, collAddr: new address[](assets.length), borrowAddr: new address[](assets.length), collAmounts: new uint[](assets.length), borrowAmounts: new uint[](assets.length) }); uint collPos = 0; uint borrowPos = 0; for (uint i = 0; i < assets.length; i++) { address asset = assets[i]; (, uint cTokenBalance, uint borrowBalance, uint exchangeRateMantissa) = CTokenInterface(asset).getAccountSnapshot(_user); Exp memory oraclePrice; if (cTokenBalance != 0 || borrowBalance != 0) { oraclePrice = Exp({mantissa: CompoundOracleInterface(oracleAddr).getUnderlyingPrice(asset)}); } // Sum up collateral in eth if (cTokenBalance != 0) { Exp memory exchangeRate = Exp({mantissa: exchangeRateMantissa}); (, Exp memory tokensToEth) = mulExp(exchangeRate, oraclePrice); data.collAddr[collPos] = asset; (, data.collAmounts[collPos]) = mulScalarTruncate(tokensToEth, cTokenBalance); collPos++; } // Sum up debt in eth if (borrowBalance != 0) { data.borrowAddr[borrowPos] = asset; (, data.borrowAmounts[borrowPos]) = mulScalarTruncate(oraclePrice, borrowBalance); borrowPos++; } } data.ratio = uint128(getSafetyRatio(_user)); return data; } function getTokenBalances(address _user, address[] memory _cTokens) public view returns (uint[] memory balances, uint[] memory borrows) { balances = new uint[](_cTokens.length); borrows = new uint[](_cTokens.length); for (uint i = 0; i < _cTokens.length; i++) { address asset = _cTokens[i]; (, uint cTokenBalance, uint borrowBalance, uint exchangeRateMantissa) = CTokenInterface(asset).getAccountSnapshot(_user); Exp memory exchangeRate = Exp({mantissa: exchangeRateMantissa}); (, balances[i]) = mulScalarTruncate(exchangeRate, cTokenBalance); borrows[i] = borrowBalance; } } /// @notice Fetches all the collateral/debt address and amounts, denominated in eth /// @param _users Addresses of the user /// @return loans Array of LoanData information function getLoanDataArr(address[] memory _users) public view returns (LoanData[] memory loans) { loans = new LoanData[](_users.length); for (uint i = 0; i < _users.length; ++i) { loans[i] = getLoanData(_users[i]); } } /// @notice Calcualted the ratio of coll/debt for a cream user /// @param _users Addresses of the user /// @return ratios Array of ratios function getRatios(address[] memory _users) public view returns (uint[] memory ratios) { ratios = new uint[](_users.length); for (uint i = 0; i < _users.length; ++i) { ratios[i] = getSafetyRatio(_users[i]); } } /// @notice Information about cTokens /// @param _cTokenAddresses Array of cTokens addresses /// @return tokens Array of cTokens infomartion function getTokensInfo(address[] memory _cTokenAddresses) public returns(TokenInfo[] memory tokens) { tokens = new TokenInfo[](_cTokenAddresses.length); address oracleAddr = comp.oracle(); for (uint i = 0; i < _cTokenAddresses.length; ++i) { (, uint collFactor) = comp.markets(_cTokenAddresses[i]); tokens[i] = TokenInfo({ cTokenAddress: _cTokenAddresses[i], underlyingTokenAddress: getUnderlyingAddr(_cTokenAddresses[i]), collateralFactor: collFactor, price: CompoundOracleInterface(oracleAddr).getUnderlyingPrice(_cTokenAddresses[i]) }); } } /// @notice Information about cTokens /// @param _cTokenAddresses Array of cTokens addresses /// @return tokens Array of cTokens infomartion function getFullTokensInfo(address[] memory _cTokenAddresses) public returns(TokenInfoFull[] memory tokens) { tokens = new TokenInfoFull[](_cTokenAddresses.length); address oracleAddr = comp.oracle(); for (uint i = 0; i < _cTokenAddresses.length; ++i) { (, uint collFactor) = comp.markets(_cTokenAddresses[i]); CTokenInterface cToken = CTokenInterface(_cTokenAddresses[i]); tokens[i] = TokenInfoFull({ underlyingTokenAddress: getUnderlyingAddr(_cTokenAddresses[i]), supplyRate: cToken.supplyRatePerBlock(), borrowRate: cToken.borrowRatePerBlock(), exchangeRate: cToken.exchangeRateCurrent(), marketLiquidity: cToken.getCash(), totalSupply: cToken.totalSupply(), totalBorrow: cToken.totalBorrowsCurrent(), collateralFactor: collFactor, price: CompoundOracleInterface(oracleAddr).getUnderlyingPrice(_cTokenAddresses[i]) }); } } /// @notice Returns the underlying address of the cToken asset /// @param _cTokenAddress cToken address /// @return Token address of the cToken specified function getUnderlyingAddr(address _cTokenAddress) internal returns (address) { if (_cTokenAddress == CETH_ADDRESS) { return ETH_ADDRESS; } else { return CTokenInterface(_cTokenAddress).underlying(); } } }
pragma solidity ^0.6.0; import "../utils/GasBurner.sol"; import "../utils/SafeERC20.sol"; import "../interfaces/CTokenInterface.sol"; import "../interfaces/CEtherInterface.sol"; import "../interfaces/ComptrollerInterface.sol"; /// @title Basic cream interactions through the DSProxy contract CreamBasicProxy is GasBurner { address public constant ETH_ADDR = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant COMPTROLLER_ADDR = 0x3d5BC3c8d13dcB8bF317092d84783c2697AE9258; using SafeERC20 for ERC20; /// @notice User deposits tokens to the cream protocol /// @dev User needs to approve the DSProxy to pull the _tokenAddr tokens /// @param _tokenAddr The address of the token to be deposited /// @param _cTokenAddr CTokens to be deposited /// @param _amount Amount of tokens to be deposited /// @param _inMarket True if the token is already in market for that address function deposit(address _tokenAddr, address _cTokenAddr, uint _amount, bool _inMarket) public burnGas(5) payable { if (_tokenAddr != ETH_ADDR) { ERC20(_tokenAddr).safeTransferFrom(msg.sender, address(this), _amount); } approveToken(_tokenAddr, _cTokenAddr); if (!_inMarket) { enterMarket(_cTokenAddr); } if (_tokenAddr != ETH_ADDR) { require(CTokenInterface(_cTokenAddr).mint(_amount) == 0); } else { CEtherInterface(_cTokenAddr).mint{value: msg.value}(); // reverts on fail } } /// @notice User withdraws tokens to the cream protocol /// @param _tokenAddr The address of the token to be withdrawn /// @param _cTokenAddr CTokens to be withdrawn /// @param _amount Amount of tokens to be withdrawn /// @param _isCAmount If true _amount is cTokens if falls _amount is underlying tokens function withdraw(address _tokenAddr, address _cTokenAddr, uint _amount, bool _isCAmount) public burnGas(5) { if (_isCAmount) { require(CTokenInterface(_cTokenAddr).redeem(_amount) == 0); } else { require(CTokenInterface(_cTokenAddr).redeemUnderlying(_amount) == 0); } // withdraw funds to msg.sender if (_tokenAddr != ETH_ADDR) { ERC20(_tokenAddr).safeTransfer(msg.sender, ERC20(_tokenAddr).balanceOf(address(this))); } else { msg.sender.transfer(address(this).balance); } } /// @notice User borrows tokens to the cream protocol /// @param _tokenAddr The address of the token to be borrowed /// @param _cTokenAddr CTokens to be borrowed /// @param _amount Amount of tokens to be borrowed /// @param _inMarket True if the token is already in market for that address function borrow(address _tokenAddr, address _cTokenAddr, uint _amount, bool _inMarket) public burnGas(8) { if (!_inMarket) { enterMarket(_cTokenAddr); } require(CTokenInterface(_cTokenAddr).borrow(_amount) == 0); // withdraw funds to msg.sender if (_tokenAddr != ETH_ADDR) { ERC20(_tokenAddr).safeTransfer(msg.sender, ERC20(_tokenAddr).balanceOf(address(this))); } else { msg.sender.transfer(address(this).balance); } } /// @dev User needs to approve the DSProxy to pull the _tokenAddr tokens /// @notice User paybacks tokens to the cream protocol /// @param _tokenAddr The address of the token to be paybacked /// @param _cTokenAddr CTokens to be paybacked /// @param _amount Amount of tokens to be payedback /// @param _wholeDebt If true the _amount will be set to the whole amount of the debt function payback(address _tokenAddr, address _cTokenAddr, uint _amount, bool _wholeDebt) public burnGas(5) payable { approveToken(_tokenAddr, _cTokenAddr); if (_wholeDebt) { _amount = CTokenInterface(_cTokenAddr).borrowBalanceCurrent(address(this)); } if (_tokenAddr != ETH_ADDR) { ERC20(_tokenAddr).safeTransferFrom(msg.sender, address(this), _amount); require(CTokenInterface(_cTokenAddr).repayBorrow(_amount) == 0); } else { CEtherInterface(_cTokenAddr).repayBorrow{value: msg.value}(); msg.sender.transfer(address(this).balance); // send back the extra eth } } /// @notice Helper method to withdraw tokens from the DSProxy /// @param _tokenAddr Address of the token to be withdrawn function withdrawTokens(address _tokenAddr) public { if (_tokenAddr != ETH_ADDR) { ERC20(_tokenAddr).safeTransfer(msg.sender, ERC20(_tokenAddr).balanceOf(address(this))); } else { msg.sender.transfer(address(this).balance); } } /// @notice Enters the cream market so it can be deposited/borrowed /// @param _cTokenAddr CToken address of the token function enterMarket(address _cTokenAddr) public { address[] memory markets = new address[](1); markets[0] = _cTokenAddr; ComptrollerInterface(COMPTROLLER_ADDR).enterMarkets(markets); } /// @notice Exits the cream market so it can't be deposited/borrowed /// @param _cTokenAddr CToken address of the token function exitMarket(address _cTokenAddr) public { ComptrollerInterface(COMPTROLLER_ADDR).exitMarket(_cTokenAddr); } /// @notice Approves CToken contract to pull underlying tokens from the DSProxy /// @param _tokenAddr Token we are trying to approve /// @param _cTokenAddr Address which will gain the approval function approveToken(address _tokenAddr, address _cTokenAddr) internal { if (_tokenAddr != ETH_ADDR) { ERC20(_tokenAddr).safeApprove(_cTokenAddr, uint(-1)); } } }
pragma solidity ^0.6.0; import "../utils/GasBurner.sol"; import "../utils/SafeERC20.sol"; import "../interfaces/CTokenInterface.sol"; import "../interfaces/CEtherInterface.sol"; import "../interfaces/ComptrollerInterface.sol"; /// @title Basic compound interactions through the DSProxy contract CompoundBasicProxy is GasBurner { address public constant ETH_ADDR = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant COMPTROLLER_ADDR = 0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B; using SafeERC20 for ERC20; /// @notice User deposits tokens to the Compound protocol /// @dev User needs to approve the DSProxy to pull the _tokenAddr tokens /// @param _tokenAddr The address of the token to be deposited /// @param _cTokenAddr CTokens to be deposited /// @param _amount Amount of tokens to be deposited /// @param _inMarket True if the token is already in market for that address function deposit(address _tokenAddr, address _cTokenAddr, uint _amount, bool _inMarket) public burnGas(5) payable { if (_tokenAddr != ETH_ADDR) { ERC20(_tokenAddr).safeTransferFrom(msg.sender, address(this), _amount); } approveToken(_tokenAddr, _cTokenAddr); if (!_inMarket) { enterMarket(_cTokenAddr); } if (_tokenAddr != ETH_ADDR) { require(CTokenInterface(_cTokenAddr).mint(_amount) == 0); } else { CEtherInterface(_cTokenAddr).mint{value: msg.value}(); // reverts on fail } } /// @notice User withdraws tokens to the Compound protocol /// @param _tokenAddr The address of the token to be withdrawn /// @param _cTokenAddr CTokens to be withdrawn /// @param _amount Amount of tokens to be withdrawn /// @param _isCAmount If true _amount is cTokens if falls _amount is underlying tokens function withdraw(address _tokenAddr, address _cTokenAddr, uint _amount, bool _isCAmount) public burnGas(5) { if (_isCAmount) { require(CTokenInterface(_cTokenAddr).redeem(_amount) == 0); } else { require(CTokenInterface(_cTokenAddr).redeemUnderlying(_amount) == 0); } // withdraw funds to msg.sender if (_tokenAddr != ETH_ADDR) { ERC20(_tokenAddr).safeTransfer(msg.sender, ERC20(_tokenAddr).balanceOf(address(this))); } else { msg.sender.transfer(address(this).balance); } } /// @notice User borrows tokens to the Compound protocol /// @param _tokenAddr The address of the token to be borrowed /// @param _cTokenAddr CTokens to be borrowed /// @param _amount Amount of tokens to be borrowed /// @param _inMarket True if the token is already in market for that address function borrow(address _tokenAddr, address _cTokenAddr, uint _amount, bool _inMarket) public burnGas(8) { if (!_inMarket) { enterMarket(_cTokenAddr); } require(CTokenInterface(_cTokenAddr).borrow(_amount) == 0); // withdraw funds to msg.sender if (_tokenAddr != ETH_ADDR) { ERC20(_tokenAddr).safeTransfer(msg.sender, ERC20(_tokenAddr).balanceOf(address(this))); } else { msg.sender.transfer(address(this).balance); } } /// @dev User needs to approve the DSProxy to pull the _tokenAddr tokens /// @notice User paybacks tokens to the Compound protocol /// @param _tokenAddr The address of the token to be paybacked /// @param _cTokenAddr CTokens to be paybacked /// @param _amount Amount of tokens to be payedback /// @param _wholeDebt If true the _amount will be set to the whole amount of the debt function payback(address _tokenAddr, address _cTokenAddr, uint _amount, bool _wholeDebt) public burnGas(5) payable { approveToken(_tokenAddr, _cTokenAddr); if (_wholeDebt) { _amount = CTokenInterface(_cTokenAddr).borrowBalanceCurrent(address(this)); } if (_tokenAddr != ETH_ADDR) { ERC20(_tokenAddr).safeTransferFrom(msg.sender, address(this), _amount); require(CTokenInterface(_cTokenAddr).repayBorrow(_amount) == 0); } else { CEtherInterface(_cTokenAddr).repayBorrow{value: msg.value}(); msg.sender.transfer(address(this).balance); // send back the extra eth } } /// @notice Helper method to withdraw tokens from the DSProxy /// @param _tokenAddr Address of the token to be withdrawn function withdrawTokens(address _tokenAddr) public { if (_tokenAddr != ETH_ADDR) { ERC20(_tokenAddr).safeTransfer(msg.sender, ERC20(_tokenAddr).balanceOf(address(this))); } else { msg.sender.transfer(address(this).balance); } } /// @notice Enters the Compound market so it can be deposited/borrowed /// @param _cTokenAddr CToken address of the token function enterMarket(address _cTokenAddr) public { address[] memory markets = new address[](1); markets[0] = _cTokenAddr; ComptrollerInterface(COMPTROLLER_ADDR).enterMarkets(markets); } /// @notice Exits the Compound market so it can't be deposited/borrowed /// @param _cTokenAddr CToken address of the token function exitMarket(address _cTokenAddr) public { ComptrollerInterface(COMPTROLLER_ADDR).exitMarket(_cTokenAddr); } /// @notice Approves CToken contract to pull underlying tokens from the DSProxy /// @param _tokenAddr Token we are trying to approve /// @param _cTokenAddr Address which will gain the approval function approveToken(address _tokenAddr, address _cTokenAddr) internal { if (_tokenAddr != ETH_ADDR) { ERC20(_tokenAddr).safeApprove(_cTokenAddr, uint(-1)); } } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "./CompBalance.sol"; import "../../exchangeV3/DFSExchangeCore.sol"; import "../../loggers/DefisaverLogger.sol"; import "../../interfaces/DSProxyInterface.sol"; import "../CompoundBasicProxy.sol"; contract CompLeverage is DFSExchangeCore, CompBalance { address public constant C_COMP_ADDR = 0x70e36f6BF80a52b3B46b3aF8e106CC0ed743E8e4; address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant CETH_ADDRESS = 0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5; address public constant DISCOUNT_ADDR = 0x1b14E8D511c9A4395425314f849bD737BAF8208F; address public constant COMPTROLLER_ADDR = 0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B; address public constant DEFISAVER_LOGGER = 0x5c55B921f590a89C1Ebe84dF170E655a82b62126; DefisaverLogger public constant logger = DefisaverLogger(0x5c55B921f590a89C1Ebe84dF170E655a82b62126); /// @notice Should claim COMP and sell it to the specified token and deposit it back /// @param exchangeData Standard Exchange struct /// @param _cTokensSupply List of cTokens user is supplying /// @param _cTokensBorrow List of cTokens user is borrowing /// @param _cDepositAddr The cToken address of the asset you want to deposit /// @param _inMarket Flag if the cToken is used as collateral function claimAndSell( ExchangeData memory exchangeData, address[] memory _cTokensSupply, address[] memory _cTokensBorrow, address _cDepositAddr, bool _inMarket ) public payable { // Claim COMP token _claim(address(this), _cTokensSupply, _cTokensBorrow); uint compBalance = ERC20(COMP_ADDR).balanceOf(address(this)); uint depositAmount = 0; // Exchange COMP if (exchangeData.srcAddr != address(0)) { exchangeData.user = msg.sender; exchangeData.dfsFeeDivider = 400; // 0.25% exchangeData.srcAmount = compBalance; (, depositAmount) = _sell(exchangeData); // if we have no deposit after, send back tokens to the user if (_cDepositAddr == address(0)) { if (exchangeData.destAddr != ETH_ADDRESS) { ERC20(exchangeData.destAddr).safeTransfer(msg.sender, depositAmount); } else { msg.sender.transfer(address(this).balance); } } } // Deposit back a token if (_cDepositAddr != address(0)) { // if we are just depositing COMP without a swap if (_cDepositAddr == C_COMP_ADDR) { depositAmount = compBalance; } address tokenAddr = getUnderlyingAddr(_cDepositAddr); deposit(tokenAddr, _cDepositAddr, depositAmount, _inMarket); } logger.Log(address(this), msg.sender, "CompLeverage", abi.encode(compBalance, depositAmount, _cDepositAddr, exchangeData.destAmount)); } function getUnderlyingAddr(address _cTokenAddress) internal returns (address) { if (_cTokenAddress == CETH_ADDRESS) { return ETH_ADDRESS; } else { return CTokenInterface(_cTokenAddress).underlying(); } } function deposit(address _tokenAddr, address _cTokenAddr, uint _amount, bool _inMarket) public burnGas(5) payable { approveToken(_tokenAddr, _cTokenAddr); if (!_inMarket) { enterMarket(_cTokenAddr); } if (_tokenAddr != ETH_ADDRESS) { require(CTokenInterface(_cTokenAddr).mint(_amount) == 0); } else { CEtherInterface(_cTokenAddr).mint{value: _amount}(); // reverts on fail } } function enterMarket(address _cTokenAddr) public { address[] memory markets = new address[](1); markets[0] = _cTokenAddr; ComptrollerInterface(COMPTROLLER_ADDR).enterMarkets(markets); } function approveToken(address _tokenAddr, address _cTokenAddr) internal { if (_tokenAddr != ETH_ADDRESS) { ERC20(_tokenAddr).safeApprove(_cTokenAddr, uint(-1)); } } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../helpers/Exponential.sol"; import "../../utils/SafeERC20.sol"; import "../../utils/GasBurner.sol"; import "../../interfaces/CTokenInterface.sol"; import "../../interfaces/ComptrollerInterface.sol"; contract CompBalance is Exponential, GasBurner { ComptrollerInterface public constant comp = ComptrollerInterface( 0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B ); address public constant COMP_ADDR = 0xc00e94Cb662C3520282E6f5717214004A7f26888; uint224 public constant compInitialIndex = 1e36; function claimComp( address _user, address[] memory _cTokensSupply, address[] memory _cTokensBorrow ) public burnGas(8) { _claim(_user, _cTokensSupply, _cTokensBorrow); ERC20(COMP_ADDR).transfer(msg.sender, ERC20(COMP_ADDR).balanceOf(address(this))); } function _claim( address _user, address[] memory _cTokensSupply, address[] memory _cTokensBorrow ) internal { address[] memory u = new address[](1); u[0] = _user; comp.claimComp(u, _cTokensSupply, false, true); comp.claimComp(u, _cTokensBorrow, true, false); } function getBalance(address _user, address[] memory _cTokens) public view returns (uint256) { uint256 compBalance = 0; for (uint256 i = 0; i < _cTokens.length; ++i) { compBalance += getSuppyBalance(_cTokens[i], _user); compBalance += getBorrowBalance(_cTokens[i], _user); } compBalance = add_(comp.compAccrued(_user), compBalance); compBalance += ERC20(COMP_ADDR).balanceOf(_user); return compBalance; } function getClaimableAssets(address[] memory _cTokens, address _user) public view returns (bool[] memory supplyClaims, bool[] memory borrowClaims) { supplyClaims = new bool[](_cTokens.length); borrowClaims = new bool[](_cTokens.length); for (uint256 i = 0; i < _cTokens.length; ++i) { supplyClaims[i] = getSuppyBalance(_cTokens[i], _user) > 0; borrowClaims[i] = getBorrowBalance(_cTokens[i], _user) > 0; } } function getSuppyBalance(address _cToken, address _supplier) public view returns (uint256 supplierAccrued) { ComptrollerInterface.CompMarketState memory supplyState = comp.compSupplyState(_cToken); Double memory supplyIndex = Double({mantissa: supplyState.index}); Double memory supplierIndex = Double({ mantissa: comp.compSupplierIndex(_cToken, _supplier) }); if (supplierIndex.mantissa == 0 && supplyIndex.mantissa > 0) { supplierIndex.mantissa = compInitialIndex; } Double memory deltaIndex = sub_(supplyIndex, supplierIndex); uint256 supplierTokens = CTokenInterface(_cToken).balanceOf(_supplier); uint256 supplierDelta = mul_(supplierTokens, deltaIndex); supplierAccrued = supplierDelta; } function getBorrowBalance(address _cToken, address _borrower) public view returns (uint256 borrowerAccrued) { ComptrollerInterface.CompMarketState memory borrowState = comp.compBorrowState(_cToken); Double memory borrowIndex = Double({mantissa: borrowState.index}); Double memory borrowerIndex = Double({ mantissa: comp.compBorrowerIndex(_cToken, _borrower) }); Exp memory marketBorrowIndex = Exp({mantissa: CTokenInterface(_cToken).borrowIndex()}); if (borrowerIndex.mantissa > 0) { Double memory deltaIndex = sub_(borrowIndex, borrowerIndex); uint256 borrowerAmount = div_( CTokenInterface(_cToken).borrowBalanceStored(_borrower), marketBorrowIndex ); uint256 borrowerDelta = mul_(borrowerAmount, deltaIndex); borrowerAccrued = borrowerDelta; } } }
pragma solidity ^0.6.0; import "../../interfaces/ERC20.sol"; import "../../interfaces/CTokenInterface.sol"; import "../../interfaces/ComptrollerInterface.sol"; import "../../utils/SafeERC20.sol"; contract CompoundBorrowProxy { using SafeERC20 for ERC20; address public constant ETH_ADDR = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant COMPTROLLER_ADDR = 0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B; function borrow(address _cCollToken, address _cBorrowToken, address _borrowToken, uint _amount) public { address[] memory markets = new address[](2); markets[0] = _cCollToken; markets[1] = _cBorrowToken; ComptrollerInterface(COMPTROLLER_ADDR).enterMarkets(markets); require(CTokenInterface(_cBorrowToken).borrow(_amount) == 0); // withdraw funds to msg.sender if (_borrowToken != ETH_ADDR) { ERC20(_borrowToken).safeTransfer(msg.sender, ERC20(_borrowToken).balanceOf(address(this))); } else { msg.sender.transfer(address(this).balance); } } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../auth/AdminAuth.sol"; import "./SaverExchange.sol"; import "../utils/SafeERC20.sol"; contract AllowanceProxy is AdminAuth { using SafeERC20 for ERC20; address public constant KYBER_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; // TODO: Real saver exchange address SaverExchange saverExchange = SaverExchange(0x235abFAd01eb1BDa28Ef94087FBAA63E18074926); function callSell(SaverExchangeCore.ExchangeData memory exData) public payable { pullAndSendTokens(exData.srcAddr, exData.srcAmount); saverExchange.sell{value: msg.value}(exData, msg.sender); } function callBuy(SaverExchangeCore.ExchangeData memory exData) public payable { pullAndSendTokens(exData.srcAddr, exData.srcAmount); saverExchange.buy{value: msg.value}(exData, msg.sender); } function pullAndSendTokens(address _tokenAddr, uint _amount) internal { if (_tokenAddr == KYBER_ETH_ADDRESS) { require(msg.value >= _amount, "msg.value smaller than amount"); } else { ERC20(_tokenAddr).safeTransferFrom(msg.sender, address(saverExchange), _amount); } } function ownerChangeExchange(address payable _newExchange) public onlyOwner { saverExchange = SaverExchange(_newExchange); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../exchange/SaverExchangeCore.sol"; import "../../loggers/DefisaverLogger.sol"; import "../helpers/CreamSaverHelper.sol"; /// @title Contract that implements repay/boost functionality contract CreamSaverProxy is CreamSaverHelper, SaverExchangeCore { DefisaverLogger public constant logger = DefisaverLogger(0x5c55B921f590a89C1Ebe84dF170E655a82b62126); /// @notice Withdraws collateral, converts to borrowed token and repays debt /// @dev Called through the DSProxy /// @param _exData Exchange data /// @param _cAddresses Coll/Debt addresses [cCollAddress, cBorrowAddress] /// @param _gasCost Gas cost for specific transaction function repay( ExchangeData memory _exData, address[2] memory _cAddresses, // cCollAddress, cBorrowAddress uint256 _gasCost ) public payable { enterMarket(_cAddresses[0], _cAddresses[1]); address payable user = payable(getUserAddress()); uint maxColl = getMaxCollateral(_cAddresses[0], address(this)); uint collAmount = (_exData.srcAmount > maxColl) ? maxColl : _exData.srcAmount; require(CTokenInterface(_cAddresses[0]).redeemUnderlying(collAmount) == 0); address collToken = getUnderlyingAddr(_cAddresses[0]); address borrowToken = getUnderlyingAddr(_cAddresses[1]); uint swapAmount = 0; if (collToken != borrowToken) { (, swapAmount) = _sell(_exData); swapAmount -= getFee(swapAmount, user, _gasCost, _cAddresses[1]); } else { swapAmount = collAmount; swapAmount -= getGasCost(swapAmount, _gasCost, _cAddresses[1]); } paybackDebt(swapAmount, _cAddresses[1], borrowToken, user); // handle 0x fee tx.origin.transfer(address(this).balance); // log amount, collToken, borrowToken logger.Log(address(this), msg.sender, "CreamRepay", abi.encode(_exData.srcAmount, swapAmount, collToken, borrowToken)); } /// @notice Borrows token, converts to collateral, and adds to position /// @dev Called through the DSProxy /// @param _exData Exchange data /// @param _cAddresses Coll/Debt addresses [cCollAddress, cBorrowAddress] /// @param _gasCost Gas cost for specific transaction function boost( ExchangeData memory _exData, address[2] memory _cAddresses, // cCollAddress, cBorrowAddress uint256 _gasCost ) public payable { enterMarket(_cAddresses[0], _cAddresses[1]); address payable user = payable(getUserAddress()); uint maxBorrow = getMaxBorrow(_cAddresses[1], address(this)); uint borrowAmount = (_exData.srcAmount > maxBorrow) ? maxBorrow : _exData.srcAmount; require(CTokenInterface(_cAddresses[1]).borrow(borrowAmount) == 0); address collToken = getUnderlyingAddr(_cAddresses[0]); address borrowToken = getUnderlyingAddr(_cAddresses[1]); uint swapAmount = 0; if (collToken != borrowToken) { borrowAmount -= getFee(borrowAmount, user, _gasCost, _cAddresses[1]); _exData.srcAmount = borrowAmount; (,swapAmount) = _sell(_exData); } else { swapAmount = borrowAmount; swapAmount -= getGasCost(swapAmount, _gasCost, _cAddresses[1]); } approveCToken(collToken, _cAddresses[0]); if (collToken != ETH_ADDRESS) { require(CTokenInterface(_cAddresses[0]).mint(swapAmount) == 0); } else { CEtherInterface(_cAddresses[0]).mint{value: swapAmount}(); // reverts on fail } // handle 0x fee tx.origin.transfer(address(this).balance); // log amount, collToken, borrowToken logger.Log(address(this), msg.sender, "CreamBoost", abi.encode(_exData.srcAmount, swapAmount, collToken, borrowToken)); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../utils/GasBurner.sol"; import "../../interfaces/ILendingPool.sol"; import "./CreamSaverProxy.sol"; import "../../loggers/DefisaverLogger.sol"; import "../../auth/ProxyPermission.sol"; /// @title Entry point for the FL Repay Boosts, called by DSProxy contract CreamFlashLoanTaker is CreamSaverProxy, ProxyPermission, GasBurner { ILendingPool public constant lendingPool = ILendingPool(0x398eC7346DcD622eDc5ae82352F02bE94C62d119); address payable public constant COMPOUND_SAVER_FLASH_LOAN = 0x3ceD2067c0B057611e4E2686Dbe40028962Cc625; address public constant AAVE_POOL_CORE = 0x3dfd23A6c5E8BbcFc9581d2E864a68feb6a076d3; /// @notice Repays the position with it's own fund or with FL if needed /// @param _exData Exchange data /// @param _cAddresses cTokens addreses and exchange [cCollAddress, cBorrowAddress, exchangeAddress] /// @param _gasCost Gas cost for specific transaction function repayWithLoan( ExchangeData memory _exData, address[2] memory _cAddresses, // cCollAddress, cBorrowAddress uint256 _gasCost ) public payable burnGas(25) { uint maxColl = getMaxCollateral(_cAddresses[0], address(this)); uint availableLiquidity = getAvailableLiquidity(_exData.srcAddr); if (_exData.srcAmount <= maxColl || availableLiquidity == 0) { repay(_exData, _cAddresses, _gasCost); } else { // 0x fee COMPOUND_SAVER_FLASH_LOAN.transfer(msg.value); uint loanAmount = (_exData.srcAmount - maxColl); bytes memory encoded = packExchangeData(_exData); bytes memory paramsData = abi.encode(encoded, _cAddresses, _gasCost, true, address(this)); givePermission(COMPOUND_SAVER_FLASH_LOAN); lendingPool.flashLoan(COMPOUND_SAVER_FLASH_LOAN, getUnderlyingAddr(_cAddresses[0]), loanAmount, paramsData); removePermission(COMPOUND_SAVER_FLASH_LOAN); logger.Log(address(this), msg.sender, "CreamFlashRepay", abi.encode(loanAmount, _exData.srcAmount, _cAddresses[0])); } } /// @notice Boosts the position with it's own fund or with FL if needed /// @param _exData Exchange data /// @param _cAddresses cTokens addreses and exchange [cCollAddress, cBorrowAddress, exchangeAddress] /// @param _gasCost Gas cost for specific transaction function boostWithLoan( ExchangeData memory _exData, address[2] memory _cAddresses, // cCollAddress, cBorrowAddress uint256 _gasCost ) public payable burnGas(20) { uint maxBorrow = getMaxBorrow(_cAddresses[1], address(this)); uint availableLiquidity = getAvailableLiquidity(_exData.srcAddr); if (_exData.srcAmount <= maxBorrow || availableLiquidity == 0) { boost(_exData, _cAddresses, _gasCost); } else { // 0x fee COMPOUND_SAVER_FLASH_LOAN.transfer(msg.value); uint loanAmount = (_exData.srcAmount - maxBorrow); bytes memory paramsData = abi.encode(packExchangeData(_exData), _cAddresses, _gasCost, false, address(this)); givePermission(COMPOUND_SAVER_FLASH_LOAN); lendingPool.flashLoan(COMPOUND_SAVER_FLASH_LOAN, getUnderlyingAddr(_cAddresses[1]), loanAmount, paramsData); removePermission(COMPOUND_SAVER_FLASH_LOAN); logger.Log(address(this), msg.sender, "CreamFlashBoost", abi.encode(loanAmount, _exData.srcAmount, _cAddresses[1])); } } function getAvailableLiquidity(address _tokenAddr) internal view returns (uint liquidity) { if (_tokenAddr == KYBER_ETH_ADDRESS) { liquidity = AAVE_POOL_CORE.balance; } else { liquidity = ERC20(_tokenAddr).balanceOf(AAVE_POOL_CORE); } } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../utils/GasBurner.sol"; import "../../interfaces/ILendingPool.sol"; import "./CompoundSaverProxy.sol"; import "../../loggers/DefisaverLogger.sol"; import "../../auth/ProxyPermission.sol"; /// @title Entry point for the FL Repay Boosts, called by DSProxy contract CompoundFlashLoanTaker is CompoundSaverProxy, ProxyPermission, GasBurner { ILendingPool public constant lendingPool = ILendingPool(0x398eC7346DcD622eDc5ae82352F02bE94C62d119); address payable public constant COMPOUND_SAVER_FLASH_LOAN = 0x819879d4725944b679371cE64474d3B92253cAb6; address public constant AAVE_POOL_CORE = 0x3dfd23A6c5E8BbcFc9581d2E864a68feb6a076d3; /// @notice Repays the position with it's own fund or with FL if needed /// @param _exData Exchange data /// @param _cAddresses cTokens addreses and exchange [cCollAddress, cBorrowAddress, exchangeAddress] /// @param _gasCost Gas cost for specific transaction function repayWithLoan( ExchangeData memory _exData, address[2] memory _cAddresses, // cCollAddress, cBorrowAddress uint256 _gasCost ) public payable burnGas(25) { uint maxColl = getMaxCollateral(_cAddresses[0], address(this)); uint availableLiquidity = getAvailableLiquidity(_exData.srcAddr); if (_exData.srcAmount <= maxColl || availableLiquidity == 0) { repay(_exData, _cAddresses, _gasCost); } else { // 0x fee COMPOUND_SAVER_FLASH_LOAN.transfer(msg.value); uint loanAmount = (_exData.srcAmount - maxColl); if (loanAmount > availableLiquidity) loanAmount = availableLiquidity; bytes memory encoded = packExchangeData(_exData); bytes memory paramsData = abi.encode(encoded, _cAddresses, _gasCost, true, address(this)); givePermission(COMPOUND_SAVER_FLASH_LOAN); lendingPool.flashLoan(COMPOUND_SAVER_FLASH_LOAN, getUnderlyingAddr(_cAddresses[0]), loanAmount, paramsData); removePermission(COMPOUND_SAVER_FLASH_LOAN); logger.Log(address(this), msg.sender, "CompoundFlashRepay", abi.encode(loanAmount, _exData.srcAmount, _cAddresses[0])); } } /// @notice Boosts the position with it's own fund or with FL if needed /// @param _exData Exchange data /// @param _cAddresses cTokens addreses and exchange [cCollAddress, cBorrowAddress, exchangeAddress] /// @param _gasCost Gas cost for specific transaction function boostWithLoan( ExchangeData memory _exData, address[2] memory _cAddresses, // cCollAddress, cBorrowAddress uint256 _gasCost ) public payable burnGas(20) { uint maxBorrow = getMaxBorrow(_cAddresses[1], address(this)); uint availableLiquidity = getAvailableLiquidity(_exData.srcAddr); if (_exData.srcAmount <= maxBorrow || availableLiquidity == 0) { boost(_exData, _cAddresses, _gasCost); } else { // 0x fee COMPOUND_SAVER_FLASH_LOAN.transfer(msg.value); uint loanAmount = (_exData.srcAmount - maxBorrow); if (loanAmount > availableLiquidity) loanAmount = availableLiquidity; bytes memory paramsData = abi.encode(packExchangeData(_exData), _cAddresses, _gasCost, false, address(this)); givePermission(COMPOUND_SAVER_FLASH_LOAN); lendingPool.flashLoan(COMPOUND_SAVER_FLASH_LOAN, getUnderlyingAddr(_cAddresses[1]), loanAmount, paramsData); removePermission(COMPOUND_SAVER_FLASH_LOAN); logger.Log(address(this), msg.sender, "CompoundFlashBoost", abi.encode(loanAmount, _exData.srcAmount, _cAddresses[1])); } } function getAvailableLiquidity(address _tokenAddr) internal view returns (uint liquidity) { if (_tokenAddr == KYBER_ETH_ADDRESS) { liquidity = AAVE_POOL_CORE.balance; } else { liquidity = ERC20(_tokenAddr).balanceOf(AAVE_POOL_CORE); } } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../exchangeV3/DFSExchangeCore.sol"; import "../../loggers/DefisaverLogger.sol"; import "../helpers/CompoundSaverHelper.sol"; /// @title Contract that implements repay/boost functionality contract CompoundSaverProxy is CompoundSaverHelper, DFSExchangeCore { DefisaverLogger public constant logger = DefisaverLogger(0x5c55B921f590a89C1Ebe84dF170E655a82b62126); /// @notice Withdraws collateral, converts to borrowed token and repays debt /// @dev Called through the DSProxy /// @param _exData Exchange data /// @param _cAddresses Coll/Debt addresses [cCollAddress, cBorrowAddress] /// @param _gasCost Gas cost for specific transaction function repay( ExchangeData memory _exData, address[2] memory _cAddresses, // cCollAddress, cBorrowAddress uint256 _gasCost ) public payable { enterMarket(_cAddresses[0], _cAddresses[1]); address payable user = payable(getUserAddress()); uint maxColl = getMaxCollateral(_cAddresses[0], address(this)); uint collAmount = (_exData.srcAmount > maxColl) ? maxColl : _exData.srcAmount; require(CTokenInterface(_cAddresses[0]).redeemUnderlying(collAmount) == 0); address collToken = getUnderlyingAddr(_cAddresses[0]); address borrowToken = getUnderlyingAddr(_cAddresses[1]); uint swapAmount = 0; if (collToken != borrowToken) { _exData.srcAmount = collAmount; _exData.dfsFeeDivider = isAutomation() ? AUTOMATIC_SERVICE_FEE : MANUAL_SERVICE_FEE; _exData.user = user; (, swapAmount) = _sell(_exData); swapAmount -= getGasCost(swapAmount, _gasCost, _cAddresses[1]); } else { swapAmount = collAmount; swapAmount -= getGasCost(swapAmount, _gasCost, _cAddresses[1]); } paybackDebt(swapAmount, _cAddresses[1], borrowToken, user); // handle 0x fee tx.origin.transfer(address(this).balance); // log amount, collToken, borrowToken logger.Log(address(this), msg.sender, "CompoundRepay", abi.encode(_exData.srcAmount, swapAmount, collToken, borrowToken)); } /// @notice Borrows token, converts to collateral, and adds to position /// @dev Called through the DSProxy /// @param _exData Exchange data /// @param _cAddresses Coll/Debt addresses [cCollAddress, cBorrowAddress] /// @param _gasCost Gas cost for specific transaction function boost( ExchangeData memory _exData, address[2] memory _cAddresses, // cCollAddress, cBorrowAddress uint256 _gasCost ) public payable { enterMarket(_cAddresses[0], _cAddresses[1]); address payable user = payable(getUserAddress()); uint maxBorrow = getMaxBorrow(_cAddresses[1], address(this)); uint borrowAmount = (_exData.srcAmount > maxBorrow) ? maxBorrow : _exData.srcAmount; require(CTokenInterface(_cAddresses[1]).borrow(borrowAmount) == 0); address collToken = getUnderlyingAddr(_cAddresses[0]); address borrowToken = getUnderlyingAddr(_cAddresses[1]); uint swapAmount = 0; if (collToken != borrowToken) { _exData.dfsFeeDivider = isAutomation() ? AUTOMATIC_SERVICE_FEE : MANUAL_SERVICE_FEE; _exData.user = user; _exData.srcAmount = borrowAmount; (, swapAmount) = _sell(_exData); swapAmount -= getGasCost(swapAmount, _gasCost, _cAddresses[1]); } else { swapAmount = borrowAmount; swapAmount -= getGasCost(swapAmount, _gasCost, _cAddresses[1]); } approveCToken(collToken, _cAddresses[0]); if (collToken != ETH_ADDRESS) { require(CTokenInterface(_cAddresses[0]).mint(swapAmount) == 0); } else { CEtherInterface(_cAddresses[0]).mint{value: swapAmount}(); // reverts on fail } // handle 0x fee tx.origin.transfer(address(this).balance); // log amount, collToken, borrowToken logger.Log(address(this), msg.sender, "CompoundBoost", abi.encode(_exData.srcAmount, swapAmount, collToken, borrowToken)); } }
pragma solidity ^0.6.0; import "../../utils/GasBurner.sol"; import "../../auth/ProxyPermission.sol"; import "../../loggers/DefisaverLogger.sol"; import "../../interfaces/ILendingPool.sol"; import "../../interfaces/CTokenInterface.sol"; import "../../interfaces/ProxyRegistryInterface.sol"; import "../helpers/CompoundSaverHelper.sol"; /// @title Imports Compound position from the account to DSProxy contract CompoundImportTaker is CompoundSaverHelper, ProxyPermission, GasBurner { ILendingPool public constant lendingPool = ILendingPool(0x398eC7346DcD622eDc5ae82352F02bE94C62d119); address payable public constant COMPOUND_IMPORT_FLASH_LOAN = 0x1DB68Ba0B85800FD323387E8B69d9AE867e00B94; address public constant PROXY_REGISTRY_ADDRESS = 0x4678f0a6958e4D2Bc4F1BAF7Bc52E8F3564f3fE4; DefisaverLogger public constant logger = DefisaverLogger(0x5c55B921f590a89C1Ebe84dF170E655a82b62126); /// @notice Starts the process to move users position 1 collateral and 1 borrow /// @dev User must approve DSProxy to pull _cCollateralToken /// @param _cCollateralToken Collateral we are moving to DSProxy /// @param _cBorrowToken Borrow token we are moving to DSProxy function importLoan(address _cCollateralToken, address _cBorrowToken) external burnGas(20) { uint loanAmount = CTokenInterface(_cBorrowToken).borrowBalanceCurrent(msg.sender); bytes memory paramsData = abi.encode(_cCollateralToken, _cBorrowToken, address(this)); givePermission(COMPOUND_IMPORT_FLASH_LOAN); lendingPool.flashLoan(COMPOUND_IMPORT_FLASH_LOAN, getUnderlyingAddr(_cBorrowToken), loanAmount, paramsData); removePermission(COMPOUND_IMPORT_FLASH_LOAN); logger.Log(address(this), msg.sender, "CompoundImport", abi.encode(loanAmount, 0, _cCollateralToken)); } }
pragma solidity ^0.6.0; import "../../auth/ProxyPermission.sol"; import "../../interfaces/ICompoundSubscription.sol"; /// @title SubscriptionsProxy handles authorization and interaction with the Subscriptions contract contract CompoundSubscriptionsProxy is ProxyPermission { address public constant COMPOUND_SUBSCRIPTION_ADDRESS = 0x52015EFFD577E08f498a0CCc11905925D58D6207; address public constant COMPOUND_MONITOR_PROXY = 0xB1cF8DE8e791E4Ed1Bd86c03E2fc1f14389Cb10a; /// @notice Calls subscription contract and creates a DSGuard if non existent /// @param _minRatio Minimum ratio below which repay is triggered /// @param _maxRatio Maximum ratio after which boost is triggered /// @param _optimalRatioBoost Ratio amount which boost should target /// @param _optimalRatioRepay Ratio amount which repay should target /// @param _boostEnabled Boolean determing if boost is enabled function subscribe( uint128 _minRatio, uint128 _maxRatio, uint128 _optimalRatioBoost, uint128 _optimalRatioRepay, bool _boostEnabled ) public { givePermission(COMPOUND_MONITOR_PROXY); ICompoundSubscription(COMPOUND_SUBSCRIPTION_ADDRESS).subscribe( _minRatio, _maxRatio, _optimalRatioBoost, _optimalRatioRepay, _boostEnabled); } /// @notice Calls subscription contract and updated existing parameters /// @dev If subscription is non existent this will create one /// @param _minRatio Minimum ratio below which repay is triggered /// @param _maxRatio Maximum ratio after which boost is triggered /// @param _optimalRatioBoost Ratio amount which boost should target /// @param _optimalRatioRepay Ratio amount which repay should target /// @param _boostEnabled Boolean determing if boost is enabled function update( uint128 _minRatio, uint128 _maxRatio, uint128 _optimalRatioBoost, uint128 _optimalRatioRepay, bool _boostEnabled ) public { ICompoundSubscription(COMPOUND_SUBSCRIPTION_ADDRESS).subscribe(_minRatio, _maxRatio, _optimalRatioBoost, _optimalRatioRepay, _boostEnabled); } /// @notice Calls the subscription contract to unsubscribe the caller function unsubscribe() public { removePermission(COMPOUND_MONITOR_PROXY); ICompoundSubscription(COMPOUND_SUBSCRIPTION_ADDRESS).unsubscribe(); } }
pragma solidity ^0.6.0; abstract contract ICompoundSubscription { function subscribe(uint128 _minRatio, uint128 _maxRatio, uint128 _optimalBoost, uint128 _optimalRepay, bool _boostEnabled) public virtual; function unsubscribe() public virtual; }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../utils/GasBurner.sol"; import "../../auth/AdminAuth.sol"; import "../../auth/ProxyPermission.sol"; import "../../utils/DydxFlashLoanBase.sol"; import "../../loggers/DefisaverLogger.sol"; import "../../interfaces/ProxyRegistryInterface.sol"; import "../../interfaces/TokenInterface.sol"; import "../../interfaces/ERC20.sol"; import "../../exchangeV3/DFSExchangeData.sol"; /// @title Import Aave position from account to wallet /// @dev Contract needs to have enough wei in WETH for all transactions (2 WETH wei per transaction) contract AaveSaverTakerV2 is DydxFlashLoanBase, ProxyPermission, GasBurner, DFSExchangeData { address public constant WETH_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address payable public constant AAVE_RECEIVER = 0x5a7689F1452d57E92878e0c0Be47cA3525e8Fcc9; address public constant DEFISAVER_LOGGER = 0x5c55B921f590a89C1Ebe84dF170E655a82b62126; address public constant PROXY_REGISTRY_ADDRESS = 0x4678f0a6958e4D2Bc4F1BAF7Bc52E8F3564f3fE4; function repay(address _market, ExchangeData memory _data, uint _rateMode, uint256 _gasCost, uint _flAmount) public payable { _flashLoan(_market, _data, _rateMode,_gasCost, true, _flAmount); } function boost(address _market, ExchangeData memory _data, uint _rateMode, uint256 _gasCost, uint _flAmount) public payable { _flashLoan(_market, _data, _rateMode, _gasCost, false, _flAmount); } /// @notice Starts the process to move users position 1 collateral and 1 borrow /// @dev User must send 2 wei with this transaction function _flashLoan(address _market, ExchangeData memory _data, uint _rateMode, uint _gasCost, bool _isRepay, uint _flAmount) internal { ISoloMargin solo = ISoloMargin(SOLO_MARGIN_ADDRESS); uint256 ethAmount = _flAmount; // Get marketId from token address uint256 marketId = _getMarketIdFromTokenAddress(WETH_ADDR); // Calculate repay amount (_amount + (2 wei)) // Approve transfer from uint256 repayAmount = _getRepaymentAmountInternal(ethAmount); ERC20(WETH_ADDR).approve(SOLO_MARGIN_ADDRESS, repayAmount); Actions.ActionArgs[] memory operations = new Actions.ActionArgs[](3); operations[0] = _getWithdrawAction(marketId, ethAmount, AAVE_RECEIVER); AAVE_RECEIVER.transfer(msg.value); bytes memory encodedData = packExchangeData(_data); operations[1] = _getCallAction( abi.encode(encodedData, _market, _rateMode, _gasCost, _isRepay, ethAmount, msg.value, proxyOwner(), address(this)), AAVE_RECEIVER ); operations[2] = _getDepositAction(marketId, repayAmount, address(this)); Account.Info[] memory accountInfos = new Account.Info[](1); accountInfos[0] = _getAccountInfo(); givePermission(AAVE_RECEIVER); solo.operate(accountInfos, operations); removePermission(AAVE_RECEIVER); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../utils/GasBurner.sol"; import "../../auth/AdminAuth.sol"; import "../../auth/ProxyPermission.sol"; import "../../utils/DydxFlashLoanBase.sol"; import "../../loggers/DefisaverLogger.sol"; import "../../interfaces/ProxyRegistryInterface.sol"; import "../../interfaces/TokenInterface.sol"; import "../../interfaces/ERC20.sol"; /// @title Import Aave position from account to wallet /// @dev Contract needs to have enough wei in WETH for all transactions (2 WETH wei per transaction) contract AaveImportTakerV2 is DydxFlashLoanBase, ProxyPermission { address public constant WETH_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address payable public constant AAVE_IMPORT = 0x1C9B7FBD410Adcd213C5d6CBA12e651300061eaD; address public constant DEFISAVER_LOGGER = 0x5c55B921f590a89C1Ebe84dF170E655a82b62126; address public constant PROXY_REGISTRY_ADDRESS = 0x4678f0a6958e4D2Bc4F1BAF7Bc52E8F3564f3fE4; /// @notice Starts the process to move users position 1 collateral and 1 borrow /// @dev User must send 2 wei with this transaction /// @dev User must approve DSProxy to pull _aCollateralToken /// @param _market Market in which we want to import /// @param _collateralToken Collateral token we are moving to DSProxy /// @param _borrowToken Borrow token we are moving to DSProxy /// @param _ethAmount ETH amount that needs to be pulled from dydx function importLoan(address _market, address _collateralToken, address _borrowToken, uint _ethAmount) public { ISoloMargin solo = ISoloMargin(SOLO_MARGIN_ADDRESS); // Get marketId from token address uint256 marketId = _getMarketIdFromTokenAddress(WETH_ADDR); // Calculate repay amount (_amount + (2 wei)) // Approve transfer from uint256 repayAmount = _getRepaymentAmountInternal(_ethAmount); ERC20(WETH_ADDR).approve(SOLO_MARGIN_ADDRESS, repayAmount); Actions.ActionArgs[] memory operations = new Actions.ActionArgs[](3); operations[0] = _getWithdrawAction(marketId, _ethAmount, AAVE_IMPORT); operations[1] = _getCallAction( abi.encode(_market, _collateralToken, _borrowToken, _ethAmount, address(this)), AAVE_IMPORT ); operations[2] = _getDepositAction(marketId, repayAmount, address(this)); Account.Info[] memory accountInfos = new Account.Info[](1); accountInfos[0] = _getAccountInfo(); givePermission(AAVE_IMPORT); solo.operate(accountInfos, operations); removePermission(AAVE_IMPORT); DefisaverLogger(DEFISAVER_LOGGER).Log(address(this), msg.sender, "AaveImport", abi.encode(_collateralToken, _borrowToken)); } }
pragma solidity ^0.6.0; import "../../auth/ProxyPermission.sol"; import "../../interfaces/IAaveSubscription.sol"; /// @title SubscriptionsProxy handles authorization and interaction with the Subscriptions contract contract AaveSubscriptionsProxyV2 is ProxyPermission { string public constant NAME = "AaveSubscriptionsProxyV2"; address public constant AAVE_SUBSCRIPTION_ADDRESS = 0x6B25043BF08182d8e86056C6548847aF607cd7CD; address public constant AAVE_MONITOR_PROXY = 0x380982902872836ceC629171DaeAF42EcC02226e; /// @notice Calls subscription contract and creates a DSGuard if non existent /// @param _minRatio Minimum ratio below which repay is triggered /// @param _maxRatio Maximum ratio after which boost is triggered /// @param _optimalRatioBoost Ratio amount which boost should target /// @param _optimalRatioRepay Ratio amount which repay should target /// @param _boostEnabled Boolean determing if boost is enabled function subscribe( uint128 _minRatio, uint128 _maxRatio, uint128 _optimalRatioBoost, uint128 _optimalRatioRepay, bool _boostEnabled ) public { givePermission(AAVE_MONITOR_PROXY); IAaveSubscription(AAVE_SUBSCRIPTION_ADDRESS).subscribe( _minRatio, _maxRatio, _optimalRatioBoost, _optimalRatioRepay, _boostEnabled); } /// @notice Calls subscription contract and updated existing parameters /// @dev If subscription is non existent this will create one /// @param _minRatio Minimum ratio below which repay is triggered /// @param _maxRatio Maximum ratio after which boost is triggered /// @param _optimalRatioBoost Ratio amount which boost should target /// @param _optimalRatioRepay Ratio amount which repay should target /// @param _boostEnabled Boolean determing if boost is enabled function update( uint128 _minRatio, uint128 _maxRatio, uint128 _optimalRatioBoost, uint128 _optimalRatioRepay, bool _boostEnabled ) public { IAaveSubscription(AAVE_SUBSCRIPTION_ADDRESS).subscribe(_minRatio, _maxRatio, _optimalRatioBoost, _optimalRatioRepay, _boostEnabled); } /// @notice Calls the subscription contract to unsubscribe the caller function unsubscribe() public { removePermission(AAVE_MONITOR_PROXY); IAaveSubscription(AAVE_SUBSCRIPTION_ADDRESS).unsubscribe(); } }
pragma solidity ^0.6.0; abstract contract IAaveSubscription { function subscribe(uint128 _minRatio, uint128 _maxRatio, uint128 _optimalBoost, uint128 _optimalRepay, bool _boostEnabled) public virtual; function unsubscribe() public virtual; }
pragma solidity ^0.6.0; import "../../auth/ProxyPermission.sol"; import "../../interfaces/IAaveSubscription.sol"; /// @title SubscriptionsProxy handles authorization and interaction with the Subscriptions contract contract AaveSubscriptionsProxy is ProxyPermission { address public constant AAVE_SUBSCRIPTION_ADDRESS = 0xe08ff7A2BADb634F0b581E675E6B3e583De086FC; address public constant AAVE_MONITOR_PROXY = 0xfA560Dba3a8D0B197cA9505A2B98120DD89209AC; /// @notice Calls subscription contract and creates a DSGuard if non existent /// @param _minRatio Minimum ratio below which repay is triggered /// @param _maxRatio Maximum ratio after which boost is triggered /// @param _optimalRatioBoost Ratio amount which boost should target /// @param _optimalRatioRepay Ratio amount which repay should target /// @param _boostEnabled Boolean determing if boost is enabled function subscribe( uint128 _minRatio, uint128 _maxRatio, uint128 _optimalRatioBoost, uint128 _optimalRatioRepay, bool _boostEnabled ) public { givePermission(AAVE_MONITOR_PROXY); IAaveSubscription(AAVE_SUBSCRIPTION_ADDRESS).subscribe( _minRatio, _maxRatio, _optimalRatioBoost, _optimalRatioRepay, _boostEnabled); } /// @notice Calls subscription contract and updated existing parameters /// @dev If subscription is non existent this will create one /// @param _minRatio Minimum ratio below which repay is triggered /// @param _maxRatio Maximum ratio after which boost is triggered /// @param _optimalRatioBoost Ratio amount which boost should target /// @param _optimalRatioRepay Ratio amount which repay should target /// @param _boostEnabled Boolean determing if boost is enabled function update( uint128 _minRatio, uint128 _maxRatio, uint128 _optimalRatioBoost, uint128 _optimalRatioRepay, bool _boostEnabled ) public { IAaveSubscription(AAVE_SUBSCRIPTION_ADDRESS).subscribe(_minRatio, _maxRatio, _optimalRatioBoost, _optimalRatioRepay, _boostEnabled); } /// @notice Calls the subscription contract to unsubscribe the caller function unsubscribe() public { removePermission(AAVE_MONITOR_PROXY); IAaveSubscription(AAVE_SUBSCRIPTION_ADDRESS).unsubscribe(); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../utils/GasBurner.sol"; import "../../auth/AdminAuth.sol"; import "../../auth/ProxyPermission.sol"; import "../../utils/DydxFlashLoanBase.sol"; import "../../loggers/DefisaverLogger.sol"; import "../../interfaces/ProxyRegistryInterface.sol"; import "../../interfaces/TokenInterface.sol"; import "../../interfaces/ERC20.sol"; import "../../exchange/SaverExchangeCore.sol"; /// @title Import Aave position from account to wallet /// @dev Contract needs to have enough wei in WETH for all transactions (2 WETH wei per transaction) contract AaveSaverTaker is DydxFlashLoanBase, ProxyPermission, GasBurner, SaverExchangeCore { address public constant WETH_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address payable public constant AAVE_RECEIVER = 0x969DfE84ac318531f13B731c7f21af9918802B94; address public constant DEFISAVER_LOGGER = 0x5c55B921f590a89C1Ebe84dF170E655a82b62126; address public constant PROXY_REGISTRY_ADDRESS = 0x4678f0a6958e4D2Bc4F1BAF7Bc52E8F3564f3fE4; function repay(ExchangeData memory _data, uint256 _gasCost) public payable { _flashLoan(_data, _gasCost, true); } function boost(ExchangeData memory _data, uint256 _gasCost) public payable { _flashLoan(_data, _gasCost, false); } /// @notice Starts the process to move users position 1 collateral and 1 borrow /// @dev User must send 2 wei with this transaction function _flashLoan(ExchangeData memory _data, uint _gasCost, bool _isRepay) internal { ISoloMargin solo = ISoloMargin(SOLO_MARGIN_ADDRESS); uint256 ethAmount = ERC20(WETH_ADDR).balanceOf(SOLO_MARGIN_ADDRESS); // Get marketId from token address uint256 marketId = _getMarketIdFromTokenAddress(WETH_ADDR); // Calculate repay amount (_amount + (2 wei)) // Approve transfer from uint256 repayAmount = _getRepaymentAmountInternal(ethAmount); ERC20(WETH_ADDR).approve(SOLO_MARGIN_ADDRESS, repayAmount); Actions.ActionArgs[] memory operations = new Actions.ActionArgs[](3); operations[0] = _getWithdrawAction(marketId, ethAmount, AAVE_RECEIVER); AAVE_RECEIVER.transfer(msg.value); bytes memory encodedData = packExchangeData(_data); operations[1] = _getCallAction( abi.encode(encodedData, _gasCost, _isRepay, ethAmount, msg.value, proxyOwner(), address(this)), AAVE_RECEIVER ); operations[2] = _getDepositAction(marketId, repayAmount, address(this)); Account.Info[] memory accountInfos = new Account.Info[](1); accountInfos[0] = _getAccountInfo(); givePermission(AAVE_RECEIVER); solo.operate(accountInfos, operations); removePermission(AAVE_RECEIVER); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../utils/GasBurner.sol"; import "../../auth/AdminAuth.sol"; import "../../auth/ProxyPermission.sol"; import "../../utils/DydxFlashLoanBase.sol"; import "../../loggers/DefisaverLogger.sol"; import "../../interfaces/ProxyRegistryInterface.sol"; import "../../interfaces/TokenInterface.sol"; import "../../interfaces/ERC20.sol"; /// @title Import Aave position from account to wallet /// @dev Contract needs to have enough wei in WETH for all transactions (2 WETH wei per transaction) contract AaveImportTaker is DydxFlashLoanBase, ProxyPermission { address public constant WETH_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address payable public constant AAVE_IMPORT = 0x5cD4239D2AA5b487bA87c3715127eA53685B4926; address public constant DEFISAVER_LOGGER = 0x5c55B921f590a89C1Ebe84dF170E655a82b62126; address public constant PROXY_REGISTRY_ADDRESS = 0x4678f0a6958e4D2Bc4F1BAF7Bc52E8F3564f3fE4; /// @notice Starts the process to move users position 1 collateral and 1 borrow /// @dev User must send 2 wei with this transaction /// @dev User must approve DSProxy to pull _aCollateralToken /// @param _collateralToken Collateral token we are moving to DSProxy /// @param _borrowToken Borrow token we are moving to DSProxy /// @param _ethAmount ETH amount that needs to be pulled from dydx function importLoan(address _collateralToken, address _borrowToken, uint _ethAmount) public { ISoloMargin solo = ISoloMargin(SOLO_MARGIN_ADDRESS); // Get marketId from token address uint256 marketId = _getMarketIdFromTokenAddress(WETH_ADDR); // Calculate repay amount (_amount + (2 wei)) // Approve transfer from uint256 repayAmount = _getRepaymentAmountInternal(_ethAmount); ERC20(WETH_ADDR).approve(SOLO_MARGIN_ADDRESS, repayAmount); Actions.ActionArgs[] memory operations = new Actions.ActionArgs[](3); operations[0] = _getWithdrawAction(marketId, _ethAmount, AAVE_IMPORT); operations[1] = _getCallAction( abi.encode(_collateralToken, _borrowToken, _ethAmount, address(this)), AAVE_IMPORT ); operations[2] = _getDepositAction(marketId, repayAmount, address(this)); Account.Info[] memory accountInfos = new Account.Info[](1); accountInfos[0] = _getAccountInfo(); givePermission(AAVE_IMPORT); solo.operate(accountInfos, operations); removePermission(AAVE_IMPORT); DefisaverLogger(DEFISAVER_LOGGER).Log(address(this), msg.sender, "AaveImport", abi.encode(_collateralToken, _borrowToken)); } }
pragma solidity ^0.6.0; import "../../DS/DSGuard.sol"; import "../../DS/DSAuth.sol"; contract SubscriptionsInterfaceV2 { function subscribe(uint _cdpId, uint128 _minRatio, uint128 _maxRatio, uint128 _optimalBoost, uint128 _optimalRepay, bool _boostEnabled, bool _nextPriceEnabled) external {} function unsubscribe(uint _cdpId) external {} } /// @title SubscriptionsProxy handles authorization and interaction with the Subscriptions contract contract SubscriptionsProxyV2 { address public constant MONITOR_PROXY_ADDRESS = 0x1816A86C4DA59395522a42b871bf11A4E96A1C7a; address public constant OLD_SUBSCRIPTION = 0x83152CAA0d344a2Fd428769529e2d490A88f4393; address public constant FACTORY_ADDRESS = 0x5a15566417e6C1c9546523066500bDDBc53F88C7; function migrate(uint _cdpId, uint128 _minRatio, uint128 _maxRatio, uint128 _optimalRatioBoost, uint128 _optimalRatioRepay, bool _boostEnabled, bool _nextPriceEnabled, address _subscriptions) public { SubscriptionsInterfaceV2(OLD_SUBSCRIPTION).unsubscribe(_cdpId); subscribe(_cdpId, _minRatio, _maxRatio, _optimalRatioBoost, _optimalRatioRepay, _boostEnabled, _nextPriceEnabled, _subscriptions); } function subscribe(uint _cdpId, uint128 _minRatio, uint128 _maxRatio, uint128 _optimalRatioBoost, uint128 _optimalRatioRepay, bool _boostEnabled, bool _nextPriceEnabled, address _subscriptions) public { address currAuthority = address(DSAuth(address(this)).authority()); DSGuard guard = DSGuard(currAuthority); if (currAuthority == address(0)) { guard = DSGuardFactory(FACTORY_ADDRESS).newGuard(); DSAuth(address(this)).setAuthority(DSAuthority(address(guard))); } guard.permit(MONITOR_PROXY_ADDRESS, address(this), bytes4(keccak256("execute(address,bytes)"))); SubscriptionsInterfaceV2(_subscriptions).subscribe(_cdpId, _minRatio, _maxRatio, _optimalRatioBoost, _optimalRatioRepay, _boostEnabled, _nextPriceEnabled); } function update(uint _cdpId, uint128 _minRatio, uint128 _maxRatio, uint128 _optimalRatioBoost, uint128 _optimalRatioRepay, bool _boostEnabled, bool _nextPriceEnabled, address _subscriptions) public { SubscriptionsInterfaceV2(_subscriptions).subscribe(_cdpId, _minRatio, _maxRatio, _optimalRatioBoost, _optimalRatioRepay, _boostEnabled, _nextPriceEnabled); } function unsubscribe(uint _cdpId, address _subscriptions) public { SubscriptionsInterfaceV2(_subscriptions).unsubscribe(_cdpId); } }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../utils/FlashLoanReceiverBase.sol"; import "../../interfaces/DSProxyInterface.sol"; import "../../exchangeV3/DFSExchangeData.sol"; /// @title Contract that receives the FL from Aave for Repays/Boost contract CompoundSaverFlashLoan is FlashLoanReceiverBase, DFSExchangeData { ILendingPoolAddressesProvider public LENDING_POOL_ADDRESS_PROVIDER = ILendingPoolAddressesProvider(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8); address payable public COMPOUND_SAVER_FLASH_PROXY = 0xcaB974d1702a056e6FF16f1DaA34646E41Ef485E; address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public owner; using SafeERC20 for ERC20; constructor() FlashLoanReceiverBase(LENDING_POOL_ADDRESS_PROVIDER) public { owner = msg.sender; } /// @notice Called by Aave when sending back the FL amount /// @param _reserve The address of the borrowed token /// @param _amount Amount of FL tokens received /// @param _fee FL Aave fee /// @param _params The params that are sent from the original FL caller contract function executeOperation( address _reserve, uint256 _amount, uint256 _fee, bytes calldata _params) external override { // Format the call data for DSProxy (bytes memory proxyData, address payable proxyAddr) = packFunctionCall(_amount, _fee, _params); // Send Flash loan amount to DSProxy sendLoanToProxy(proxyAddr, _reserve, _amount); // Execute the DSProxy call DSProxyInterface(proxyAddr).execute(COMPOUND_SAVER_FLASH_PROXY, proxyData); // Repay the loan with the money DSProxy sent back transferFundsBackToPoolInternal(_reserve, _amount.add(_fee)); // if there is some eth left (0x fee), return it to user if (address(this).balance > 0) { tx.origin.transfer(address(this).balance); } } /// @notice Formats function data call so we can call it through DSProxy /// @param _amount Amount of FL /// @param _fee Fee of the FL /// @param _params Saver proxy params /// @return proxyData Formated function call data function packFunctionCall(uint _amount, uint _fee, bytes memory _params) internal pure returns (bytes memory proxyData, address payable) { ( bytes memory exDataBytes, address[2] memory cAddresses, // cCollAddress, cBorrowAddress uint256 gasCost, bool isRepay, address payable proxyAddr ) = abi.decode(_params, (bytes,address[2],uint256,bool,address)); ExchangeData memory _exData = unpackExchangeData(exDataBytes); uint[2] memory flashLoanData = [_amount, _fee]; if (isRepay) { proxyData = abi.encodeWithSignature("flashRepay((address,address,uint256,uint256,uint256,address,address,bytes,uint256),address[2],uint256,uint256[2])", _exData, cAddresses, gasCost, flashLoanData); } else { proxyData = abi.encodeWithSignature("flashBoost((address,address,uint256,uint256,uint256,address,address,bytes,uint256),address[2],uint256,uint256[2])", _exData, cAddresses, gasCost, flashLoanData); } return (proxyData, proxyAddr); } /// @notice Send the FL funds received to DSProxy /// @param _proxy DSProxy address /// @param _reserve Token address /// @param _amount Amount of tokens function sendLoanToProxy(address payable _proxy, address _reserve, uint _amount) internal { if (_reserve != ETH_ADDRESS) { ERC20(_reserve).safeTransfer(_proxy, _amount); } _proxy.transfer(address(this).balance); } receive() external override(FlashLoanReceiverBase) payable {} }
pragma solidity ^0.6.0; import "../../auth/AdminAuth.sol"; import "../../utils/FlashLoanReceiverBase.sol"; import "../../interfaces/ProxyRegistryInterface.sol"; import "../../interfaces/CTokenInterface.sol"; import "../../utils/SafeERC20.sol"; /// @title Receives FL from Aave and imports the position to DSProxy contract CompoundImportFlashLoan is FlashLoanReceiverBase, AdminAuth { using SafeERC20 for ERC20; ILendingPoolAddressesProvider public LENDING_POOL_ADDRESS_PROVIDER = ILendingPoolAddressesProvider(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8); address public constant COMPOUND_BORROW_PROXY = 0xb7EDC39bE76107e2Cc645f0f6a3D164f5e173Ee2; address public constant PULL_TOKENS_PROXY = 0x45431b79F783e0BF0fe7eF32D06A3e061780bfc4; // solhint-disable-next-line no-empty-blocks constructor() public FlashLoanReceiverBase(LENDING_POOL_ADDRESS_PROVIDER) {} /// @notice Called by Aave when sending back the FL amount /// @param _reserve The address of the borrowed token /// @param _amount Amount of FL tokens received /// @param _fee FL Aave fee /// @param _params The params that are sent from the original FL caller contract function executeOperation( address _reserve, uint256 _amount, uint256 _fee, bytes calldata _params ) external override { (address cCollAddr, address cBorrowAddr, address proxy) = abi.decode(_params, (address, address, address)); address user = DSProxyInterface(proxy).owner(); uint256 usersCTokenBalance = CTokenInterface(cCollAddr).balanceOf(user); // approve FL tokens so we can repay them ERC20(_reserve).safeApprove(cBorrowAddr, _amount); // repay compound debt on behalf of the user require( CTokenInterface(cBorrowAddr).repayBorrowBehalf(user, uint256(-1)) == 0, "Repay borrow behalf fail" ); bytes memory depositProxyCallData = formatDSProxyPullTokensCall(cCollAddr, usersCTokenBalance); DSProxyInterface(proxy).execute(PULL_TOKENS_PROXY, depositProxyCallData); // borrow debt now on ds proxy bytes memory borrowProxyCallData = formatDSProxyBorrowCall(cCollAddr, cBorrowAddr, _reserve, (_amount + _fee)); DSProxyInterface(proxy).execute(COMPOUND_BORROW_PROXY, borrowProxyCallData); // repay the loan with the money DSProxy sent back transferFundsBackToPoolInternal(_reserve, _amount.add(_fee)); } /// @notice Formats function data call to pull tokens to DSProxy /// @param _cTokenAddr CToken address of the collateral /// @param _amount Amount of cTokens to pull function formatDSProxyPullTokensCall( address _cTokenAddr, uint256 _amount ) internal pure returns (bytes memory) { return abi.encodeWithSignature( "pullTokens(address,uint256)", _cTokenAddr, _amount ); } /// @notice Formats function data call borrow through DSProxy /// @param _cCollToken CToken address of collateral /// @param _cBorrowToken CToken address we will borrow /// @param _borrowToken Token address we will borrow /// @param _amount Amount that will be borrowed function formatDSProxyBorrowCall( address _cCollToken, address _cBorrowToken, address _borrowToken, uint256 _amount ) internal pure returns (bytes memory) { return abi.encodeWithSignature( "borrow(address,address,address,uint256)", _cCollToken, _cBorrowToken, _borrowToken, _amount ); } }
pragma solidity ^0.6.0; import "../../interfaces/OsmMom.sol"; import "../../interfaces/Osm.sol"; import "../../auth/AdminAuth.sol"; import "../../interfaces/Manager.sol"; contract MCDPriceVerifier is AdminAuth { OsmMom public osmMom = OsmMom(0x76416A4d5190d071bfed309861527431304aA14f); Manager public manager = Manager(0x5ef30b9986345249bc32d8928B7ee64DE9435E39); mapping(address => bool) public authorized; function verifyVaultNextPrice(uint _nextPrice, uint _cdpId) public view returns(bool) { require(authorized[msg.sender]); bytes32 ilk = manager.ilks(_cdpId); return verifyNextPrice(_nextPrice, ilk); } function verifyNextPrice(uint _nextPrice, bytes32 _ilk) public view returns(bool) { require(authorized[msg.sender]); address osmAddress = osmMom.osms(_ilk); uint whitelisted = Osm(osmAddress).bud(address(this)); // If contracts doesn't have access return true if (whitelisted != 1) return true; (bytes32 price, bool has) = Osm(osmAddress).peep(); return has ? uint(price) == _nextPrice : false; } function setAuthorized(address _address, bool _allowed) public onlyOwner { authorized[_address] = _allowed; } }
pragma solidity ^0.6.0; abstract contract OsmMom { mapping (bytes32 => address) public osms; }
pragma solidity ^0.6.0; abstract contract Osm { mapping(address => uint256) public bud; function peep() external view virtual returns (bytes32, bool); }
pragma solidity ^0.6.0; import "./ERC20.sol"; //TODO: currenlty only adjusted to kyber, but should be genric interfaces for more dec. exchanges interface ExchangeInterface { function swapEtherToToken(uint256 _ethAmount, address _tokenAddress, uint256 _maxAmount) external payable returns (uint256, uint256); function swapTokenToEther(address _tokenAddress, uint256 _amount, uint256 _maxAmount) external returns (uint256); function swapTokenToToken(address _src, address _dest, uint256 _amount) external payable returns (uint256); function getExpectedRate(address src, address dest, uint256 srcQty) external view returns (uint256 expectedRate); }
{ "optimizer": { "enabled": true, "runs": 200 }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "abi" ] } } }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"DISCOUNT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DISCOUNT_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERR_DEST_AMOUNT_MISSING","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERR_NOT_ZEROX_EXCHANGE","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERR_OFFCHAIN_DATA_INVALID","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERR_SLIPPAGE_HIT","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERR_WRAPPER_INVALID","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ETH_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXCHANGE_WETH_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"KYBER_ETH_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LENDING_POOL_ADDRESS_PROVIDER","outputs":[{"internalType":"contract ILendingPoolAddressesProvider","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SAVER_EXCHANGE_REGISTRY","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SERVICE_FEE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ZRX_ALLOWLIST_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"_feeRecipient","outputs":[{"internalType":"contract IFeeRecipient","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"addressesProvider","outputs":[{"internalType":"contract ILendingPoolAddressesProvider","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_reserve","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_fee","type":"uint256"},{"internalType":"bytes","name":"_params","type":"bytes"}],"name":"executeOperation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"kill","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"srcAddr","type":"address"},{"internalType":"address","name":"destAddr","type":"address"},{"internalType":"uint256","name":"srcAmount","type":"uint256"},{"internalType":"uint256","name":"destAmount","type":"uint256"},{"internalType":"uint256","name":"minPrice","type":"uint256"},{"internalType":"uint256","name":"dfsFeeDivider","type":"uint256"},{"internalType":"address","name":"user","type":"address"},{"internalType":"address","name":"wrapper","type":"address"},{"internalType":"bytes","name":"wrapperData","type":"bytes"},{"components":[{"internalType":"address","name":"wrapper","type":"address"},{"internalType":"address","name":"exchangeAddr","type":"address"},{"internalType":"address","name":"allowanceTarget","type":"address"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"protocolFee","type":"uint256"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct DFSExchangeData.OffchainData","name":"offchainData","type":"tuple"}],"internalType":"struct DFSExchangeData.ExchangeData","name":"_exData","type":"tuple"}],"name":"packExchangeData","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"}],"name":"setAdminByAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"}],"name":"setAdminByOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"setOwnerByAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"shifterRegistry","outputs":[{"internalType":"contract ShifterRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"unpackExchangeData","outputs":[{"components":[{"internalType":"address","name":"srcAddr","type":"address"},{"internalType":"address","name":"destAddr","type":"address"},{"internalType":"uint256","name":"srcAmount","type":"uint256"},{"internalType":"uint256","name":"destAmount","type":"uint256"},{"internalType":"uint256","name":"minPrice","type":"uint256"},{"internalType":"uint256","name":"dfsFeeDivider","type":"uint256"},{"internalType":"address","name":"user","type":"address"},{"internalType":"address","name":"wrapper","type":"address"},{"internalType":"bytes","name":"wrapperData","type":"bytes"},{"components":[{"internalType":"address","name":"wrapper","type":"address"},{"internalType":"address","name":"exchangeAddr","type":"address"},{"internalType":"address","name":"allowanceTarget","type":"address"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"protocolFee","type":"uint256"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct DFSExchangeData.OffchainData","name":"offchainData","type":"tuple"}],"internalType":"struct DFSExchangeData.ExchangeData","name":"_exData","type":"tuple"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdrawStuckFunds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]
Contract Creation Code
6080604052600380546001600160a01b0319167324a42fd28c976a61df5d00d0599c34c4f90748c817905534801561003657600080fd5b50600354600080546001600160a01b03199081166001600160a01b0390931692909217905560018054821633179055600280549091167325efa336886c74ea8e282ac466bdcd0199f85bb9179055612fc7806100936000396000f3fe6080604052600436106101a05760003560e01c806398ab3d49116100ec578063c50ebaf81161008a578063deca5f8811610064578063deca5f88146103db578063e6a12ca9146103fb578063ee87255814610410578063f851a44014610430576101a7565b8063c50ebaf81461039c578063c72c4d10146103b1578063cc694d48146103c6576101a7565b8063a46a66c9116100c6578063a46a66c9146102bc578063a7304bf714610367578063a734f06e14610219578063ae08fd1014610387576101a7565b806398ab3d4914610310578063a342f23814610325578063a3b8e5d11461033a576101a7565b806341c0e1b5116101595780637b925ab1116101335780637b925ab1146102bc57806381b94280146102d15780638c8a7958146102e65780638da5cb5b146102fb576101a7565b806341c0e1b514610270578063449b9ffa1461028557806350c86de51461029a576101a7565b806308d4f52a146101ac5780631e48907b146101e2578063278d58311461020457806329f7fc9e14610219578063314b63321461023b5780633a12832214610250576101a7565b366101a757005b600080fd5b3480156101b857600080fd5b506101cc6101c736600461290b565b610445565b6040516101d99190612dac565b60405180910390f35b3480156101ee57600080fd5b506102026101fd366004612780565b61046f565b005b34801561021057600080fd5b506101cc6104a8565b34801561022557600080fd5b5061022e6104d0565b6040516101d99190612cdb565b34801561024757600080fd5b5061022e6104e8565b34801561025c57600080fd5b5061020261026b3660046127b8565b610500565b34801561027c57600080fd5b5061020261059a565b34801561029157600080fd5b506101cc6105bf565b3480156102a657600080fd5b506102af6105f1565b6040516101d99190612eb6565b3480156102c857600080fd5b5061022e6105f7565b3480156102dd57600080fd5b5061022e61060f565b3480156102f257600080fd5b5061022e610627565b34801561030757600080fd5b5061022e61063f565b34801561031c57600080fd5b5061022e61064e565b34801561033157600080fd5b5061022e61065d565b34801561034657600080fd5b5061035a6103553660046128d8565b610675565b6040516101d99190612e77565b34801561037357600080fd5b50610202610382366004612780565b610697565b34801561039357600080fd5b506101cc6106d0565b3480156103a857600080fd5b506101cc610701565b3480156103bd57600080fd5b5061022e610730565b3480156103d257600080fd5b506101cc61073f565b3480156103e757600080fd5b506102026103f6366004612780565b61076a565b34801561040757600080fd5b5061022e610797565b34801561041c57600080fd5b5061020261042b3660046127e3565b6107af565b34801561043c57600080fd5b5061022e610c8c565b6060816040516020016104589190612e77565b60405160208183030381529060405290505b919050565b6002546001600160a01b0316331461048657600080fd5b600180546001600160a01b0319166001600160a01b0392909216919091179055565b6040518060400160405280600c81526020016b14db1a5c1c1859d9481a1a5d60a21b81525081565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee81565b7325dd3f51e0c3c3ff164ddc02a8e4d65bb9cbb12d81565b6001546001600160a01b0316331461051757600080fd5b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b038316141561057c576001546040516001600160a01b039091169082156108fc029083906000818181858888f19350505050158015610576573d6000803e3d6000fd5b50610596565b600154610596906001600160a01b03848116911683610c9b565b5050565b6001546001600160a01b031633146105b157600080fd5b6001546001600160a01b0316ff5b6040518060400160405280601681526020017516995c9bde08195e18da185b99d9481a5b9d985b1a5960521b81525081565b61019081565b731b14e8d511c9a4395425314f849bd737baf8208f81565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b734ba1f38427b33b8ab7bb0490200dae1f1c36823f81565b6001546001600160a01b031681565b6003546001600160a01b031681565b7339c4a92dc506300c3ea4c67ca4ca611102ee6f2a81565b61067d61230e565b818060200190518101906106919190612a13565b92915050565b6002546001600160a01b031633146106ae57600080fd5b600280546001600160a01b0319166001600160a01b0392909216919091179055565b6040518060400160405280601581526020017413d99998da185a5b8819185d18481a5b9d985b1a59605a1b81525081565b604051806040016040528060138152602001724465737420616d6f756e74206d697373696e6760681b81525081565b6000546001600160a01b031681565b6040518060400160405280600f81526020016e15dc985c1c195c881a5b9d985b1a59608a1b81525081565b6001546001600160a01b0316331461078157600080fd5b6002546001600160a01b0316156106ae57600080fd5b73597c52281b31b9d949a9d8feba08f7a2530a965e81565b6107b761238a565b6107bf61230e565b610800868686868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610cf692505050565b91509150600073597c52281b31b9d949a9d8feba08f7a2530a965e6001600160a01b031663d502db97610836856080015161102c565b6040518263ffffffff1660e01b81526004016108529190612dac565b60206040518083038186803b15801561086a57600080fd5b505afa15801561087e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108a2919061279c565b9050600073597c52281b31b9d949a9d8feba08f7a2530a965e6001600160a01b031663d502db976108d68660a0015161102c565b6040518263ffffffff1660e01b81526004016108f29190612dac565b60206040518083038186803b15801561090a57600080fd5b505afa15801561091e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610942919061279c565b905061095384604001518a8a611093565b60408085015185519151631cff79cd60e01b81526001600160a01b0390911691631cff79cd91610987918691600401612d6f565b602060405180830381600087803b1580156109a157600080fd5b505af11580156109b5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109d991906128c0565b506101908360a001818152505083604001516001600160a01b0316638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381600087803b158015610a2557600080fd5b505af1158015610a39573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a5d919061279c565b6001600160a01b031660c08481019190915284015160ff1660011415610ac1576040830151602084015184516001600160a01b03908116911614610aa857610aa48461110b565b9150505b610abb856040015185602001518361145b565b50610bb1565b8360c0015160ff1660021415610b975782602001516001600160a01b031683600001516001600160a01b031614610b92578787016060840152610b0383611493565b5050604080850151845191516370a0823160e01b8152610b9292906001600160a01b038216906370a0823190610b3d903090600401612cdb565b60206040518083038186803b158015610b5557600080fd5b505afa158015610b69573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b8d91906128c0565b611093565b610bb1565b60408401518351610bb19190610bac81611817565b61145b565b83604001516001600160a01b0316631cff79cd8286602001516040518363ffffffff1660e01b8152600401610be7929190612d6f565b602060405180830381600087803b158015610c0157600080fd5b505af1158015610c15573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c3991906128c0565b50610c4d89610c488a8a6118c1565b6118ed565b4715610c815760405132904780156108fc02916000818181858888f19350505050158015610c7f573d6000803e3d6000fd5b505b505050505050505050565b6002546001600160a01b031681565b610cf18363a9059cbb60e01b8484604051602401610cba929190612d93565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152611981565b505050565b610cfe61238a565b610d0661230e565b610d0e6123c6565b600084806020019051810190610d249190612a46565b9094509092509050606080888801600085516001811115610d4157fe5b1415610d9c5761016085015161012086015160a0870151604051610d6b9392918e91602401612ede565b60408051601f198184030181529190526020810180516001600160e01b0316638dff4f7760e01b1790529250610e73565b600185516001811115610dab57fe5b1415610e7357600285604001516002811115610dc357fe5b1415610e1e578460e001518561010001518b8860400151604051602401610ded9493929190612d46565b60408051601f198184030181529190526020810180516001600160e01b031663b7c2940360e01b1790529250610e73565b8461012001518560e001518660a001518760c00151604051602401610e469493929190612d46565b60408051601f198184030181529190526020810180516001600160e01b0316630784262160e31b17905292505b600085602001516001811115610e8557fe5b1415610edb5784610180015185610140015182604051602401610eaa93929190612ebf565b60408051601f198184030181529190526020810180516001600160e01b031663b6147ed560e01b1790529150610f9e565b600185602001516001811115610eed57fe5b1415610f9e57600285604001516002811115610f0557fe5b1415610f5257846101000151604051602401610f219190612cdb565b60408051601f198184030181529190526020810180516001600160e01b0316637c27383b60e01b1790529150610f9e565b84610140015185610100015182604051602401610f7193929190612cef565b60408051601f198184030181529190526020810180516001600160e01b0316637a79382160e11b17905291505b6040518060e00160405280848152602001838152602001856001600160a01b031681526020018660e001516001600160a01b0316815260200186600001516001811115610fe757fe5b60ff16815260200186602001516001811115610fff57fe5b60ff1681526020018660400151600281111561101757fe5b60ff1681525096505050505050935093915050565b606060ff821661105e575060408051808201909152600b81526a26a1a22fa9a424a32a22a960a91b602082015261046a565b8160ff166001141561046a575060408051808201909152600c81526b21a7a6a82fa9a424a32a22a960a11b602082015261046a565b6001600160a01b03821673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee146110d0576110cb6001600160a01b0383168483610c9b565b610cf1565b6040516001600160a01b038416904780156108fc02916000818181858888f19350505050158015611105573d6000803e3d6000fd5b50505050565b600080600080600073eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b031686600001516001600160a01b031614156111c657855161115090611a10565b6001600160a01b031686526040808701518151630d0e30db60e41b8152915173c02aaa39b223fe8d0a0e5c4f27ead9083c756cc29263d0e30db09291600480830192600092919082900301818588803b1580156111ac57600080fd5b505af11580156111c0573d6000803e3d6000fd5b50505050505b6111e286604001518760c0015188600001518960a00151611a57565b604087018051919091039052610120860151606001511561121f57611208866000611cab565b92509050801561121f578561012001516020015192505b806112395761122f866000611ea9565b91508560e0015192505b600061125873c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2611817565b111561133e576040516370a0823160e01b815273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc290632e1a7d4d9082906370a082319061129d903090600401612cdb565b602060405180830381600087803b1580156112b757600080fd5b505af11580156112cb573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112ef91906128c0565b6040518263ffffffff1660e01b815260040161130b9190612eb6565b600060405180830381600087803b15801561132557600080fd5b505af1158015611339573d6000803e3d6000fd5b505050505b60208601516001600160a01b031673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc214156113ea57611379866080015187604001516120eb565b61139673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee611817565b10156040518060400160405280600c81526020016b14db1a5c1c1859d9481a1a5d60a21b815250906113e45760405162461bcd60e51b81526004016113db9190612dac565b60405180910390fd5b50611450565b6113fc866080015187604001516120eb565b6114098760200151611817565b10156040518060400160405280600c81526020016b14db1a5c1c1859d9481a1a5d60a21b8152509061144e5760405162461bcd60e51b81526004016113db9190612dac565b505b509092509050915091565b6001600160a01b03821673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee146110d0576110d06001600160a01b0383168483610c9b565b6000806000806000856060015160001415604051806040016040528060138152602001724465737420616d6f756e74206d697373696e6760681b815250906114ee5760405162461bcd60e51b81526004016113db9190612dac565b5061150b86604001518760c0015188600001518960a00151611a57565b60408701805191909103905285516001600160a01b031673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee14156115be57855161154890611a10565b6001600160a01b031686526040808701518151630d0e30db60e41b8152915173c02aaa39b223fe8d0a0e5c4f27ead9083c756cc29263d0e30db09291600480830192600092919082900301818588803b1580156115a457600080fd5b505af11580156115b8573d6000803e3d6000fd5b50505050505b61012086015160600151156115ef576115d8866001611cab565b9250905080156115ef578561012001516020015192505b80611609576115ff866001611ea9565b91508560e0015192505b600061162873c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2611817565b111561170e576040516370a0823160e01b815273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc290632e1a7d4d9082906370a082319061166d903090600401612cdb565b602060405180830381600087803b15801561168757600080fd5b505af115801561169b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116bf91906128c0565b6040518263ffffffff1660e01b81526004016116db9190612eb6565b600060405180830381600087803b1580156116f557600080fd5b505af1158015611709573d6000803e3d6000fd5b505050505b60208601516001600160a01b031673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc214156117a457856060015161175973eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee611817565b10156040518060400160405280600c81526020016b14db1a5c1c1859d9481a1a5d60a21b8152509061179e5760405162461bcd60e51b81526004016113db9190612dac565b506117fd565b85606001516117b68760200151611817565b10156040518060400160405280600c81526020016b14db1a5c1c1859d9481a1a5d60a21b815250906117fb5760405162461bcd60e51b81526004016113db9190612dac565b505b8261180b8760200151611817565b94509450505050915091565b60006001600160a01b03821673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee141561184557504761046a565b6040516370a0823160e01b81526001600160a01b038316906370a0823190611871903090600401612cdb565b60206040518083038186803b15801561188957600080fd5b505afa15801561189d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061069191906128c0565b6000828201838110156118e65760405162461bcd60e51b81526004016113db90612dbf565b9392505050565b60008060009054906101000a90046001600160a01b03166001600160a01b031663ed6ff7606040518163ffffffff1660e01b815260040160206040518083038186803b15801561193c57600080fd5b505afa158015611950573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611974919061279c565b9050610cf1818484612120565b60606119d6826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166121b69092919063ffffffff16565b805190915015610cf157808060200190518101906119f49190612874565b610cf15760405162461bcd60e51b81526004016113db90612e2d565b60006001600160a01b03821673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee14611a3c5781610691565b5073c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2919050565b60008115801590611aea5750604051632cdc77ab60e21b8152731b14e8d511c9a4395425314f849bd737baf8208f9063b371deac90611a9a908790600401612cdb565b60206040518083038186803b158015611ab257600080fd5b505afa158015611ac6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611aea9190612874565b15611b7957604051636eeb543160e01b8152731b14e8d511c9a4395425314f849bd737baf8208f90636eeb543190611b26908790600401612cdb565b60206040518083038186803b158015611b3e57600080fd5b505afa158015611b52573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b7691906128c0565b91505b81611b8657506000611ca3565b818581611b8f57fe5b049050600a8504811115611ba35750600a84045b60007339c4a92dc506300c3ea4c67ca4ca611102ee6f2a6001600160a01b031663b38779eb6040518163ffffffff1660e01b815260040160206040518083038186803b158015611bf257600080fd5b505afa158015611c06573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c2a919061279c565b90506001600160a01b03841673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1415611c8d576040516001600160a01b0382169083156108fc029084906000818181858888f19350505050158015611c87573d6000803e3d6000fd5b50611ca1565b611ca16001600160a01b0385168284610c9b565b505b949350505050565b610120820151602001516040516302f5cc7960e11b81526000918291734ba1f38427b33b8ab7bb0490200dae1f1c36823f916305eb98f291611cf09190600401612cdb565b60206040518083038186803b158015611d0857600080fd5b505afa158015611d1c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d409190612874565b611d4f57506000905080611ea2565b6101208401515160405163e0aa279760e01b81527325dd3f51e0c3c3ff164ddc02a8e4d65bb9cbb12d9163e0aa279791611d8c9190600401612cdb565b60206040518083038186803b158015611da457600080fd5b505afa158015611db8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ddc9190612874565b611deb57506000905080611ea2565b6101208401515160408501518551611e0e926001600160a01b0390911691610c9b565b610120840151805160809091015160405163097396a160e31b81526001600160a01b0390921691634b9cb5089190611e4c9088908890600401612e8a565b60408051808303818588803b158015611e6457600080fd5b505af1158015611e78573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190611e9d9190612894565b915091505b9250929050565b60e082015160405163e0aa279760e01b81526000917325dd3f51e0c3c3ff164ddc02a8e4d65bb9cbb12d9163e0aa279791611ee691600401612cdb565b60206040518083038186803b158015611efe57600080fd5b505afa158015611f12573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f369190612874565b6040518060400160405280600f81526020016e15dc985c1c195c881a5b9d985b1a59608a1b81525090611f7c5760405162461bcd60e51b81526004016113db9190612dac565b5060e083015160408401518451611f9e926001600160a01b0390911691610c9b565b6000826001811115611fac57fe5b1415612052578260e001516001600160a01b0316635b6f36fc8460000151856020015186604001518761010001516040518563ffffffff1660e01b8152600401611ff99493929190612d13565b602060405180830381600087803b15801561201357600080fd5b505af1158015612027573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061204b91906128c0565b9050610691565b8260e001516001600160a01b0316633924db668460000151856020015186606001518761010001516040518563ffffffff1660e01b81526004016120999493929190612d13565b602060405180830381600087803b1580156120b357600080fd5b505af11580156120c7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118e691906128c0565b6000670de0b6b3a764000061211161210385856121c5565b6706f05b59d3b200006121e9565b8161211857fe5b049392505050565b6121286121f9565b6001600160a01b0316826001600160a01b031614156121a257826001600160a01b03168160405161215890612cd8565b60006040518083038185875af1925050503d8060008114612195576040519150601f19603f3d011682016040523d82523d6000602084013e61219a565b606091505b505050610cf1565b610cf16001600160a01b0383168483610c9b565b6060611ca38484600085612211565b60008115806121e0575050808202828282816121dd57fe5b04145b61069157600080fd5b8082018281101561069157600080fd5b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee90565b606061221c856122d5565b6122385760405162461bcd60e51b81526004016113db90612df6565b60006060866001600160a01b031685876040516122559190612cbc565b60006040518083038185875af1925050503d8060008114612292576040519150601f19603f3d011682016040523d82523d6000602084013e612297565b606091505b509150915081156122ab579150611ca39050565b8051156122bb5780518082602001fd5b8360405162461bcd60e51b81526004016113db9190612dac565b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470818114801590611ca3575050151592915050565b60405180610140016040528060006001600160a01b0316815260200160006001600160a01b031681526020016000815260200160008152602001600081526020016000815260200160006001600160a01b0316815260200160006001600160a01b0316815260200160608152602001612385612431565b905290565b6040805160e081018252606080825260208201819052600092820183905281018290526080810182905260a0810182905260c081019190915290565b604080516101a08101909152806000815260200160008152602001600081526020016000815260006020820181905260408201819052606082018190526080820181905260a0820181905260c0820181905260e0820181905261010082018190526101209091015290565b6040518060c0016040528060006001600160a01b0316815260200160006001600160a01b0316815260200160006001600160a01b031681526020016000815260200160008152602001606081525090565b803561069181612f79565b805161069181612f79565b8051801515811461069157600080fd5b600082601f8301126124b8578081fd5b81356124cb6124c682612f29565b612f02565b91508082528360208285010111156124e257600080fd5b8060208401602084013760009082016020015292915050565b600082601f83011261250b578081fd5b81516125196124c682612f29565b915080825283602082850101111561253057600080fd5b612541816020840160208601612f4d565b5092915050565b80516002811061069157600080fd5b80516003811061069157600080fd5b80516004811061069157600080fd5b6000610140808385031215612588578182fd5b61259181612f02565b91505061259e838361248d565b81526125ad836020840161248d565b602082015260408201516040820152606082015160608201526080820151608082015260a082015160a08201526125e78360c0840161248d565b60c08201526125f98360e0840161248d565b60e08201526101008083015167ffffffffffffffff8082111561261b57600080fd5b612627868387016124fb565b8385015261012092508285015191508082111561264357600080fd5b50612650858286016126f4565b82840152505092915050565b600060c0828403121561266d578081fd5b61267760c0612f02565b9050813561268481612f79565b8152602082013561269481612f79565b602082015260408201356126a781612f79565b80604083015250606082013560608201526080820135608082015260a082013567ffffffffffffffff8111156126dc57600080fd5b6126e8848285016124a8565b60a08301525092915050565b600060c08284031215612705578081fd5b61270f60c0612f02565b9050815161271c81612f79565b8152602082015161272c81612f79565b6020820152604082015161273f81612f79565b80604083015250606082015160608201526080820151608082015260a082015167ffffffffffffffff81111561277457600080fd5b6126e8848285016124fb565b600060208284031215612791578081fd5b81356118e681612f79565b6000602082840312156127ad578081fd5b81516118e681612f79565b600080604083850312156127ca578081fd5b82356127d581612f79565b946020939093013593505050565b6000806000806000608086880312156127fa578081fd5b853561280581612f79565b94506020860135935060408601359250606086013567ffffffffffffffff8082111561282f578283fd5b818801915088601f830112612842578283fd5b813581811115612850578384fd5b896020828501011115612861578384fd5b9699959850939650602001949392505050565b600060208284031215612885578081fd5b815180151581146118e6578182fd5b600080604083850312156128a6578182fd5b6128b08484612498565b9150602083015190509250929050565b6000602082840312156128d1578081fd5b5051919050565b6000602082840312156128e9578081fd5b813567ffffffffffffffff8111156128ff578182fd5b611ca3848285016124a8565b60006020828403121561291c578081fd5b813567ffffffffffffffff80821115612933578283fd5b8184019150610140808387031215612949578384fd5b61295281612f02565b905061295e8684612482565b815261296d8660208501612482565b602082015260408301356040820152606083013560608201526080830135608082015260a083013560a08201526129a78660c08501612482565b60c08201526129b98660e08501612482565b60e082015261010080840135838111156129d1578586fd5b6129dd888287016124a8565b82840152505061012080840135838111156129f6578586fd5b612a028882870161265c565b918301919091525095945050505050565b600060208284031215612a24578081fd5b815167ffffffffffffffff811115612a3a578182fd5b611ca384828501612575565b60008060008385036101e0811215612a5c578182fd5b6101a080821215612a6b578283fd5b612a7481612f02565b9150612a808787612548565b8252612a8f8760208801612548565b6020830152612aa18760408801612557565b6040830152612ab38760608801612566565b6060830152612ac58760808801612498565b608083015260a086015160a083015260c086015160c0830152612aeb8760e0880161248d565b60e0830152610100612aff8882890161248d565b90830152610120612b128888830161248d565b90830152610140612b258888830161248d565b908301526101608681015190830152610180808701519083015285015190935067ffffffffffffffff811115612b59578182fd5b612b6586828701612575565b925050612b76856101c0860161248d565b90509250925092565b6001600160a01b03169052565b60008151808452612ba4816020860160208601612f4d565b601f01601f19169290920160200192915050565b6000610140612bc8848451612b7f565b6020830151612bda6020860182612b7f565b5060408301516040850152606083015160608501526080830151608085015260a083015160a085015260c0830151612c1560c0860182612b7f565b5060e0830151612c2860e0860182612b7f565b50610100808401518282870152612c4183870182612b8c565b925050506101208084015185830382870152612c5d8382612c67565b9695505050505050565b600060018060a01b0380835116845280602084015116602085015280604084015116604085015250606082015160608401526080820151608084015260a082015160c060a0850152611ca360c0850182612b8c565b60008251612cce818460208701612f4d565b9190910192915050565b90565b6001600160a01b0391909116815260200190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b0385811682528416602082015260408101839052608060608201819052600090612c5d90830184612b8c565b6001600160a01b0394851681529290931660208301526040820152606081019190915260800190565b6001600160a01b0383168152604060208201819052600090611ca390830184612b8c565b6001600160a01b03929092168252602082015260400190565b6000602082526118e66020830184612b8c565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b6020808252601d908201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604082015260600190565b6020808252602a908201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6040820152691bdd081cdd58d8d9595960b21b606082015260800190565b6000602082526118e66020830184612bb8565b600060408252612e9d6040830185612bb8565b905060028310612ea957fe5b8260208301529392505050565b90815260200190565b9283526001600160a01b03919091166020830152604082015260600190565b9384526001600160a01b039290921660208401526040830152606082015260800190565b60405181810167ffffffffffffffff81118282101715612f2157600080fd5b604052919050565b600067ffffffffffffffff821115612f3f578081fd5b50601f01601f191660200190565b60005b83811015612f68578181015183820152602001612f50565b838111156111055750506000910152565b6001600160a01b0381168114612f8e57600080fd5b5056fea2646970667358221220f46cd6e2d90a15203d39e69eb0be6e334469231c1ac66b7a111a655ad6cb98ca64736f6c634300060c0033
Deployed Bytecode
0x6080604052600436106101a05760003560e01c806398ab3d49116100ec578063c50ebaf81161008a578063deca5f8811610064578063deca5f88146103db578063e6a12ca9146103fb578063ee87255814610410578063f851a44014610430576101a7565b8063c50ebaf81461039c578063c72c4d10146103b1578063cc694d48146103c6576101a7565b8063a46a66c9116100c6578063a46a66c9146102bc578063a7304bf714610367578063a734f06e14610219578063ae08fd1014610387576101a7565b806398ab3d4914610310578063a342f23814610325578063a3b8e5d11461033a576101a7565b806341c0e1b5116101595780637b925ab1116101335780637b925ab1146102bc57806381b94280146102d15780638c8a7958146102e65780638da5cb5b146102fb576101a7565b806341c0e1b514610270578063449b9ffa1461028557806350c86de51461029a576101a7565b806308d4f52a146101ac5780631e48907b146101e2578063278d58311461020457806329f7fc9e14610219578063314b63321461023b5780633a12832214610250576101a7565b366101a757005b600080fd5b3480156101b857600080fd5b506101cc6101c736600461290b565b610445565b6040516101d99190612dac565b60405180910390f35b3480156101ee57600080fd5b506102026101fd366004612780565b61046f565b005b34801561021057600080fd5b506101cc6104a8565b34801561022557600080fd5b5061022e6104d0565b6040516101d99190612cdb565b34801561024757600080fd5b5061022e6104e8565b34801561025c57600080fd5b5061020261026b3660046127b8565b610500565b34801561027c57600080fd5b5061020261059a565b34801561029157600080fd5b506101cc6105bf565b3480156102a657600080fd5b506102af6105f1565b6040516101d99190612eb6565b3480156102c857600080fd5b5061022e6105f7565b3480156102dd57600080fd5b5061022e61060f565b3480156102f257600080fd5b5061022e610627565b34801561030757600080fd5b5061022e61063f565b34801561031c57600080fd5b5061022e61064e565b34801561033157600080fd5b5061022e61065d565b34801561034657600080fd5b5061035a6103553660046128d8565b610675565b6040516101d99190612e77565b34801561037357600080fd5b50610202610382366004612780565b610697565b34801561039357600080fd5b506101cc6106d0565b3480156103a857600080fd5b506101cc610701565b3480156103bd57600080fd5b5061022e610730565b3480156103d257600080fd5b506101cc61073f565b3480156103e757600080fd5b506102026103f6366004612780565b61076a565b34801561040757600080fd5b5061022e610797565b34801561041c57600080fd5b5061020261042b3660046127e3565b6107af565b34801561043c57600080fd5b5061022e610c8c565b6060816040516020016104589190612e77565b60405160208183030381529060405290505b919050565b6002546001600160a01b0316331461048657600080fd5b600180546001600160a01b0319166001600160a01b0392909216919091179055565b6040518060400160405280600c81526020016b14db1a5c1c1859d9481a1a5d60a21b81525081565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee81565b7325dd3f51e0c3c3ff164ddc02a8e4d65bb9cbb12d81565b6001546001600160a01b0316331461051757600080fd5b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b038316141561057c576001546040516001600160a01b039091169082156108fc029083906000818181858888f19350505050158015610576573d6000803e3d6000fd5b50610596565b600154610596906001600160a01b03848116911683610c9b565b5050565b6001546001600160a01b031633146105b157600080fd5b6001546001600160a01b0316ff5b6040518060400160405280601681526020017516995c9bde08195e18da185b99d9481a5b9d985b1a5960521b81525081565b61019081565b731b14e8d511c9a4395425314f849bd737baf8208f81565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b734ba1f38427b33b8ab7bb0490200dae1f1c36823f81565b6001546001600160a01b031681565b6003546001600160a01b031681565b7339c4a92dc506300c3ea4c67ca4ca611102ee6f2a81565b61067d61230e565b818060200190518101906106919190612a13565b92915050565b6002546001600160a01b031633146106ae57600080fd5b600280546001600160a01b0319166001600160a01b0392909216919091179055565b6040518060400160405280601581526020017413d99998da185a5b8819185d18481a5b9d985b1a59605a1b81525081565b604051806040016040528060138152602001724465737420616d6f756e74206d697373696e6760681b81525081565b6000546001600160a01b031681565b6040518060400160405280600f81526020016e15dc985c1c195c881a5b9d985b1a59608a1b81525081565b6001546001600160a01b0316331461078157600080fd5b6002546001600160a01b0316156106ae57600080fd5b73597c52281b31b9d949a9d8feba08f7a2530a965e81565b6107b761238a565b6107bf61230e565b610800868686868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610cf692505050565b91509150600073597c52281b31b9d949a9d8feba08f7a2530a965e6001600160a01b031663d502db97610836856080015161102c565b6040518263ffffffff1660e01b81526004016108529190612dac565b60206040518083038186803b15801561086a57600080fd5b505afa15801561087e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108a2919061279c565b9050600073597c52281b31b9d949a9d8feba08f7a2530a965e6001600160a01b031663d502db976108d68660a0015161102c565b6040518263ffffffff1660e01b81526004016108f29190612dac565b60206040518083038186803b15801561090a57600080fd5b505afa15801561091e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610942919061279c565b905061095384604001518a8a611093565b60408085015185519151631cff79cd60e01b81526001600160a01b0390911691631cff79cd91610987918691600401612d6f565b602060405180830381600087803b1580156109a157600080fd5b505af11580156109b5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109d991906128c0565b506101908360a001818152505083604001516001600160a01b0316638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381600087803b158015610a2557600080fd5b505af1158015610a39573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a5d919061279c565b6001600160a01b031660c08481019190915284015160ff1660011415610ac1576040830151602084015184516001600160a01b03908116911614610aa857610aa48461110b565b9150505b610abb856040015185602001518361145b565b50610bb1565b8360c0015160ff1660021415610b975782602001516001600160a01b031683600001516001600160a01b031614610b92578787016060840152610b0383611493565b5050604080850151845191516370a0823160e01b8152610b9292906001600160a01b038216906370a0823190610b3d903090600401612cdb565b60206040518083038186803b158015610b5557600080fd5b505afa158015610b69573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b8d91906128c0565b611093565b610bb1565b60408401518351610bb19190610bac81611817565b61145b565b83604001516001600160a01b0316631cff79cd8286602001516040518363ffffffff1660e01b8152600401610be7929190612d6f565b602060405180830381600087803b158015610c0157600080fd5b505af1158015610c15573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c3991906128c0565b50610c4d89610c488a8a6118c1565b6118ed565b4715610c815760405132904780156108fc02916000818181858888f19350505050158015610c7f573d6000803e3d6000fd5b505b505050505050505050565b6002546001600160a01b031681565b610cf18363a9059cbb60e01b8484604051602401610cba929190612d93565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152611981565b505050565b610cfe61238a565b610d0661230e565b610d0e6123c6565b600084806020019051810190610d249190612a46565b9094509092509050606080888801600085516001811115610d4157fe5b1415610d9c5761016085015161012086015160a0870151604051610d6b9392918e91602401612ede565b60408051601f198184030181529190526020810180516001600160e01b0316638dff4f7760e01b1790529250610e73565b600185516001811115610dab57fe5b1415610e7357600285604001516002811115610dc357fe5b1415610e1e578460e001518561010001518b8860400151604051602401610ded9493929190612d46565b60408051601f198184030181529190526020810180516001600160e01b031663b7c2940360e01b1790529250610e73565b8461012001518560e001518660a001518760c00151604051602401610e469493929190612d46565b60408051601f198184030181529190526020810180516001600160e01b0316630784262160e31b17905292505b600085602001516001811115610e8557fe5b1415610edb5784610180015185610140015182604051602401610eaa93929190612ebf565b60408051601f198184030181529190526020810180516001600160e01b031663b6147ed560e01b1790529150610f9e565b600185602001516001811115610eed57fe5b1415610f9e57600285604001516002811115610f0557fe5b1415610f5257846101000151604051602401610f219190612cdb565b60408051601f198184030181529190526020810180516001600160e01b0316637c27383b60e01b1790529150610f9e565b84610140015185610100015182604051602401610f7193929190612cef565b60408051601f198184030181529190526020810180516001600160e01b0316637a79382160e11b17905291505b6040518060e00160405280848152602001838152602001856001600160a01b031681526020018660e001516001600160a01b0316815260200186600001516001811115610fe757fe5b60ff16815260200186602001516001811115610fff57fe5b60ff1681526020018660400151600281111561101757fe5b60ff1681525096505050505050935093915050565b606060ff821661105e575060408051808201909152600b81526a26a1a22fa9a424a32a22a960a91b602082015261046a565b8160ff166001141561046a575060408051808201909152600c81526b21a7a6a82fa9a424a32a22a960a11b602082015261046a565b6001600160a01b03821673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee146110d0576110cb6001600160a01b0383168483610c9b565b610cf1565b6040516001600160a01b038416904780156108fc02916000818181858888f19350505050158015611105573d6000803e3d6000fd5b50505050565b600080600080600073eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b031686600001516001600160a01b031614156111c657855161115090611a10565b6001600160a01b031686526040808701518151630d0e30db60e41b8152915173c02aaa39b223fe8d0a0e5c4f27ead9083c756cc29263d0e30db09291600480830192600092919082900301818588803b1580156111ac57600080fd5b505af11580156111c0573d6000803e3d6000fd5b50505050505b6111e286604001518760c0015188600001518960a00151611a57565b604087018051919091039052610120860151606001511561121f57611208866000611cab565b92509050801561121f578561012001516020015192505b806112395761122f866000611ea9565b91508560e0015192505b600061125873c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2611817565b111561133e576040516370a0823160e01b815273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc290632e1a7d4d9082906370a082319061129d903090600401612cdb565b602060405180830381600087803b1580156112b757600080fd5b505af11580156112cb573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112ef91906128c0565b6040518263ffffffff1660e01b815260040161130b9190612eb6565b600060405180830381600087803b15801561132557600080fd5b505af1158015611339573d6000803e3d6000fd5b505050505b60208601516001600160a01b031673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc214156113ea57611379866080015187604001516120eb565b61139673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee611817565b10156040518060400160405280600c81526020016b14db1a5c1c1859d9481a1a5d60a21b815250906113e45760405162461bcd60e51b81526004016113db9190612dac565b60405180910390fd5b50611450565b6113fc866080015187604001516120eb565b6114098760200151611817565b10156040518060400160405280600c81526020016b14db1a5c1c1859d9481a1a5d60a21b8152509061144e5760405162461bcd60e51b81526004016113db9190612dac565b505b509092509050915091565b6001600160a01b03821673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee146110d0576110d06001600160a01b0383168483610c9b565b6000806000806000856060015160001415604051806040016040528060138152602001724465737420616d6f756e74206d697373696e6760681b815250906114ee5760405162461bcd60e51b81526004016113db9190612dac565b5061150b86604001518760c0015188600001518960a00151611a57565b60408701805191909103905285516001600160a01b031673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee14156115be57855161154890611a10565b6001600160a01b031686526040808701518151630d0e30db60e41b8152915173c02aaa39b223fe8d0a0e5c4f27ead9083c756cc29263d0e30db09291600480830192600092919082900301818588803b1580156115a457600080fd5b505af11580156115b8573d6000803e3d6000fd5b50505050505b61012086015160600151156115ef576115d8866001611cab565b9250905080156115ef578561012001516020015192505b80611609576115ff866001611ea9565b91508560e0015192505b600061162873c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2611817565b111561170e576040516370a0823160e01b815273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc290632e1a7d4d9082906370a082319061166d903090600401612cdb565b602060405180830381600087803b15801561168757600080fd5b505af115801561169b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116bf91906128c0565b6040518263ffffffff1660e01b81526004016116db9190612eb6565b600060405180830381600087803b1580156116f557600080fd5b505af1158015611709573d6000803e3d6000fd5b505050505b60208601516001600160a01b031673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc214156117a457856060015161175973eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee611817565b10156040518060400160405280600c81526020016b14db1a5c1c1859d9481a1a5d60a21b8152509061179e5760405162461bcd60e51b81526004016113db9190612dac565b506117fd565b85606001516117b68760200151611817565b10156040518060400160405280600c81526020016b14db1a5c1c1859d9481a1a5d60a21b815250906117fb5760405162461bcd60e51b81526004016113db9190612dac565b505b8261180b8760200151611817565b94509450505050915091565b60006001600160a01b03821673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee141561184557504761046a565b6040516370a0823160e01b81526001600160a01b038316906370a0823190611871903090600401612cdb565b60206040518083038186803b15801561188957600080fd5b505afa15801561189d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061069191906128c0565b6000828201838110156118e65760405162461bcd60e51b81526004016113db90612dbf565b9392505050565b60008060009054906101000a90046001600160a01b03166001600160a01b031663ed6ff7606040518163ffffffff1660e01b815260040160206040518083038186803b15801561193c57600080fd5b505afa158015611950573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611974919061279c565b9050610cf1818484612120565b60606119d6826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166121b69092919063ffffffff16565b805190915015610cf157808060200190518101906119f49190612874565b610cf15760405162461bcd60e51b81526004016113db90612e2d565b60006001600160a01b03821673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee14611a3c5781610691565b5073c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2919050565b60008115801590611aea5750604051632cdc77ab60e21b8152731b14e8d511c9a4395425314f849bd737baf8208f9063b371deac90611a9a908790600401612cdb565b60206040518083038186803b158015611ab257600080fd5b505afa158015611ac6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611aea9190612874565b15611b7957604051636eeb543160e01b8152731b14e8d511c9a4395425314f849bd737baf8208f90636eeb543190611b26908790600401612cdb565b60206040518083038186803b158015611b3e57600080fd5b505afa158015611b52573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b7691906128c0565b91505b81611b8657506000611ca3565b818581611b8f57fe5b049050600a8504811115611ba35750600a84045b60007339c4a92dc506300c3ea4c67ca4ca611102ee6f2a6001600160a01b031663b38779eb6040518163ffffffff1660e01b815260040160206040518083038186803b158015611bf257600080fd5b505afa158015611c06573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c2a919061279c565b90506001600160a01b03841673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1415611c8d576040516001600160a01b0382169083156108fc029084906000818181858888f19350505050158015611c87573d6000803e3d6000fd5b50611ca1565b611ca16001600160a01b0385168284610c9b565b505b949350505050565b610120820151602001516040516302f5cc7960e11b81526000918291734ba1f38427b33b8ab7bb0490200dae1f1c36823f916305eb98f291611cf09190600401612cdb565b60206040518083038186803b158015611d0857600080fd5b505afa158015611d1c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d409190612874565b611d4f57506000905080611ea2565b6101208401515160405163e0aa279760e01b81527325dd3f51e0c3c3ff164ddc02a8e4d65bb9cbb12d9163e0aa279791611d8c9190600401612cdb565b60206040518083038186803b158015611da457600080fd5b505afa158015611db8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ddc9190612874565b611deb57506000905080611ea2565b6101208401515160408501518551611e0e926001600160a01b0390911691610c9b565b610120840151805160809091015160405163097396a160e31b81526001600160a01b0390921691634b9cb5089190611e4c9088908890600401612e8a565b60408051808303818588803b158015611e6457600080fd5b505af1158015611e78573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190611e9d9190612894565b915091505b9250929050565b60e082015160405163e0aa279760e01b81526000917325dd3f51e0c3c3ff164ddc02a8e4d65bb9cbb12d9163e0aa279791611ee691600401612cdb565b60206040518083038186803b158015611efe57600080fd5b505afa158015611f12573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f369190612874565b6040518060400160405280600f81526020016e15dc985c1c195c881a5b9d985b1a59608a1b81525090611f7c5760405162461bcd60e51b81526004016113db9190612dac565b5060e083015160408401518451611f9e926001600160a01b0390911691610c9b565b6000826001811115611fac57fe5b1415612052578260e001516001600160a01b0316635b6f36fc8460000151856020015186604001518761010001516040518563ffffffff1660e01b8152600401611ff99493929190612d13565b602060405180830381600087803b15801561201357600080fd5b505af1158015612027573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061204b91906128c0565b9050610691565b8260e001516001600160a01b0316633924db668460000151856020015186606001518761010001516040518563ffffffff1660e01b81526004016120999493929190612d13565b602060405180830381600087803b1580156120b357600080fd5b505af11580156120c7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118e691906128c0565b6000670de0b6b3a764000061211161210385856121c5565b6706f05b59d3b200006121e9565b8161211857fe5b049392505050565b6121286121f9565b6001600160a01b0316826001600160a01b031614156121a257826001600160a01b03168160405161215890612cd8565b60006040518083038185875af1925050503d8060008114612195576040519150601f19603f3d011682016040523d82523d6000602084013e61219a565b606091505b505050610cf1565b610cf16001600160a01b0383168483610c9b565b6060611ca38484600085612211565b60008115806121e0575050808202828282816121dd57fe5b04145b61069157600080fd5b8082018281101561069157600080fd5b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee90565b606061221c856122d5565b6122385760405162461bcd60e51b81526004016113db90612df6565b60006060866001600160a01b031685876040516122559190612cbc565b60006040518083038185875af1925050503d8060008114612292576040519150601f19603f3d011682016040523d82523d6000602084013e612297565b606091505b509150915081156122ab579150611ca39050565b8051156122bb5780518082602001fd5b8360405162461bcd60e51b81526004016113db9190612dac565b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470818114801590611ca3575050151592915050565b60405180610140016040528060006001600160a01b0316815260200160006001600160a01b031681526020016000815260200160008152602001600081526020016000815260200160006001600160a01b0316815260200160006001600160a01b0316815260200160608152602001612385612431565b905290565b6040805160e081018252606080825260208201819052600092820183905281018290526080810182905260a0810182905260c081019190915290565b604080516101a08101909152806000815260200160008152602001600081526020016000815260006020820181905260408201819052606082018190526080820181905260a0820181905260c0820181905260e0820181905261010082018190526101209091015290565b6040518060c0016040528060006001600160a01b0316815260200160006001600160a01b0316815260200160006001600160a01b031681526020016000815260200160008152602001606081525090565b803561069181612f79565b805161069181612f79565b8051801515811461069157600080fd5b600082601f8301126124b8578081fd5b81356124cb6124c682612f29565b612f02565b91508082528360208285010111156124e257600080fd5b8060208401602084013760009082016020015292915050565b600082601f83011261250b578081fd5b81516125196124c682612f29565b915080825283602082850101111561253057600080fd5b612541816020840160208601612f4d565b5092915050565b80516002811061069157600080fd5b80516003811061069157600080fd5b80516004811061069157600080fd5b6000610140808385031215612588578182fd5b61259181612f02565b91505061259e838361248d565b81526125ad836020840161248d565b602082015260408201516040820152606082015160608201526080820151608082015260a082015160a08201526125e78360c0840161248d565b60c08201526125f98360e0840161248d565b60e08201526101008083015167ffffffffffffffff8082111561261b57600080fd5b612627868387016124fb565b8385015261012092508285015191508082111561264357600080fd5b50612650858286016126f4565b82840152505092915050565b600060c0828403121561266d578081fd5b61267760c0612f02565b9050813561268481612f79565b8152602082013561269481612f79565b602082015260408201356126a781612f79565b80604083015250606082013560608201526080820135608082015260a082013567ffffffffffffffff8111156126dc57600080fd5b6126e8848285016124a8565b60a08301525092915050565b600060c08284031215612705578081fd5b61270f60c0612f02565b9050815161271c81612f79565b8152602082015161272c81612f79565b6020820152604082015161273f81612f79565b80604083015250606082015160608201526080820151608082015260a082015167ffffffffffffffff81111561277457600080fd5b6126e8848285016124fb565b600060208284031215612791578081fd5b81356118e681612f79565b6000602082840312156127ad578081fd5b81516118e681612f79565b600080604083850312156127ca578081fd5b82356127d581612f79565b946020939093013593505050565b6000806000806000608086880312156127fa578081fd5b853561280581612f79565b94506020860135935060408601359250606086013567ffffffffffffffff8082111561282f578283fd5b818801915088601f830112612842578283fd5b813581811115612850578384fd5b896020828501011115612861578384fd5b9699959850939650602001949392505050565b600060208284031215612885578081fd5b815180151581146118e6578182fd5b600080604083850312156128a6578182fd5b6128b08484612498565b9150602083015190509250929050565b6000602082840312156128d1578081fd5b5051919050565b6000602082840312156128e9578081fd5b813567ffffffffffffffff8111156128ff578182fd5b611ca3848285016124a8565b60006020828403121561291c578081fd5b813567ffffffffffffffff80821115612933578283fd5b8184019150610140808387031215612949578384fd5b61295281612f02565b905061295e8684612482565b815261296d8660208501612482565b602082015260408301356040820152606083013560608201526080830135608082015260a083013560a08201526129a78660c08501612482565b60c08201526129b98660e08501612482565b60e082015261010080840135838111156129d1578586fd5b6129dd888287016124a8565b82840152505061012080840135838111156129f6578586fd5b612a028882870161265c565b918301919091525095945050505050565b600060208284031215612a24578081fd5b815167ffffffffffffffff811115612a3a578182fd5b611ca384828501612575565b60008060008385036101e0811215612a5c578182fd5b6101a080821215612a6b578283fd5b612a7481612f02565b9150612a808787612548565b8252612a8f8760208801612548565b6020830152612aa18760408801612557565b6040830152612ab38760608801612566565b6060830152612ac58760808801612498565b608083015260a086015160a083015260c086015160c0830152612aeb8760e0880161248d565b60e0830152610100612aff8882890161248d565b90830152610120612b128888830161248d565b90830152610140612b258888830161248d565b908301526101608681015190830152610180808701519083015285015190935067ffffffffffffffff811115612b59578182fd5b612b6586828701612575565b925050612b76856101c0860161248d565b90509250925092565b6001600160a01b03169052565b60008151808452612ba4816020860160208601612f4d565b601f01601f19169290920160200192915050565b6000610140612bc8848451612b7f565b6020830151612bda6020860182612b7f565b5060408301516040850152606083015160608501526080830151608085015260a083015160a085015260c0830151612c1560c0860182612b7f565b5060e0830151612c2860e0860182612b7f565b50610100808401518282870152612c4183870182612b8c565b925050506101208084015185830382870152612c5d8382612c67565b9695505050505050565b600060018060a01b0380835116845280602084015116602085015280604084015116604085015250606082015160608401526080820151608084015260a082015160c060a0850152611ca360c0850182612b8c565b60008251612cce818460208701612f4d565b9190910192915050565b90565b6001600160a01b0391909116815260200190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b0385811682528416602082015260408101839052608060608201819052600090612c5d90830184612b8c565b6001600160a01b0394851681529290931660208301526040820152606081019190915260800190565b6001600160a01b0383168152604060208201819052600090611ca390830184612b8c565b6001600160a01b03929092168252602082015260400190565b6000602082526118e66020830184612b8c565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b6020808252601d908201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604082015260600190565b6020808252602a908201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6040820152691bdd081cdd58d8d9595960b21b606082015260800190565b6000602082526118e66020830184612bb8565b600060408252612e9d6040830185612bb8565b905060028310612ea957fe5b8260208301529392505050565b90815260200190565b9283526001600160a01b03919091166020830152604082015260600190565b9384526001600160a01b039290921660208401526040830152606082015260800190565b60405181810167ffffffffffffffff81118282101715612f2157600080fd5b604052919050565b600067ffffffffffffffff821115612f3f578081fd5b50601f01601f191660200190565b60005b83811015612f68578181015183820152602001612f50565b838111156111055750506000910152565b6001600160a01b0381168114612f8e57600080fd5b5056fea2646970667358221220f46cd6e2d90a15203d39e69eb0be6e334469231c1ac66b7a111a655ad6cb98ca64736f6c634300060c0033
Deployed Bytecode Sourcemap
391:7270:175:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;795:132:83;;;;;;;;;;-1:-1:-1;795:132:83;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;1122:118:35;;;;;;;;;;-1:-1:-1;1122:118:35;;;;;:::i;:::-;;:::i;:::-;;459:56:82;;;;;;;;;;;;;:::i;280:86:84:-;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;670:92::-;;;;;;;;;;;;;:::i;1405:279:35:-;;;;;;;;;;-1:-1:-1;1405:279:35;;;;;:::i;:::-;;:::i;1283:78::-;;;;;;;;;;;;;:::i;665:72:82:-;;;;;;;;;;;;;:::i;811:38:175:-;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;479:82::-;;;;;;;;;;;;;:::i;372:90:84:-;;;;;;;;;;;;;:::i;769:87::-;;;;;;;;;;;;;:::i;117:20:35:-;;;;;;;;;;;;;:::i;568:150:175:-;;;;;;;;;;;;;:::i;469:103:84:-;;;;;;;;;;;;;:::i;933:157:83:-;;;;;;;;;;-1:-1:-1;933:157:83;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;909:118:35:-;;;;;;;;;;-1:-1:-1;909:118:35;;;;;:::i;:::-;;:::i;167:74:84:-;;;;;;;;;;;;;:::i;521:70:82:-;;;;;;;;;;;;;:::i;2246:54:186:-;;;;;;;;;;;;;:::i;597:62:82:-;;;;;;;;;;;;;:::i;635:156:35:-;;;;;;;;;;-1:-1:-1;635:156:35;;;;;:::i;:::-;;:::i;869:117:175:-;;;;;;;;;;;;;:::i;1280:2402::-;;;;;;;;;;-1:-1:-1;1280:2402:175;;;;;:::i;:::-;;:::i;143:20:35:-;;;;;;;;;;;;;:::i;795:132:83:-;870:12;912:7;901:19;;;;;;;;:::i;:::-;;;;;;;;;;;;;894:26;;795:132;;;;:::o;1122:118:35:-;1202:5;;-1:-1:-1;;;;;1202:5:35;1188:10;:19;1180:28;;;;;;1219:5;:14;;-1:-1:-1;;;;;;1219:14:35;-1:-1:-1;;;;;1219:14:35;;;;;;;;;;1122:118::o;459:56:82:-;;;;;;;;;;;;;;-1:-1:-1;;;459:56:82;;;;:::o;280:86:84:-;324:42;280:86;:::o;670:92::-;720:42;670:92;:::o;1405:279:35:-;209:5;;-1:-1:-1;;;;;209:5:35;218:10;209:19;201:28;;;;;;1504:42:::1;-1:-1:-1::0;;;;;1494:52:35;::::1;;1490:188;;;1570:5;::::0;1562:32:::1;::::0;-1:-1:-1;;;;;1570:5:35;;::::1;::::0;1562:32;::::1;;;::::0;1586:7;;1570:5:::1;1562:32:::0;1570:5;1562:32;1586:7;1570:5;1562:32;::::1;;;;;;;;;;;;;::::0;::::1;;;;;;1490:188;;;1652:5;::::0;1625:42:::1;::::0;-1:-1:-1;;;;;1625:26:35;;::::1;::::0;1652:5:::1;1659:7:::0;1625:26:::1;:42::i;:::-;1405:279:::0;;:::o;1283:78::-;209:5;;-1:-1:-1;;;;;209:5:35;218:10;209:19;201:28;;;;;;1347:5:::1;::::0;-1:-1:-1;;;;;1347:5:35::1;1326:28;665:72:82::0;;;;;;;;;;;;;;-1:-1:-1;;;665:72:82;;;;:::o;811:38:175:-;846:3;811:38;:::o;479:82::-;519:42;479:82;:::o;372:90:84:-;420:42;372:90;:::o;769:87::-;814:42;769:87;:::o;117:20:35:-;;;-1:-1:-1;;;;;117:20:35;;:::o;568:150:175:-;;;-1:-1:-1;;;;;568:150:175;;:::o;469:103:84:-;529:42;469:103;:::o;933:157:83:-;1001:27;;:::i;:::-;1061:5;1050:33;;;;;;;;;;;;:::i;:::-;1040:43;933:157;-1:-1:-1;;933:157:83:o;909:118:35:-;989:5;;-1:-1:-1;;;;;989:5:35;975:10;:19;967:28;;;;;;1006:5;:14;;-1:-1:-1;;;;;;1006:14:35;-1:-1:-1;;;;;1006:14:35;;;;;;;;;;909:118::o;167:74:84:-;;;;;;;;;;;;;;-1:-1:-1;;;167:74:84;;;;:::o;521:70:82:-;;;;;;;;;;;;;;-1:-1:-1;;;521:70:82;;;;:::o;2246:54:186:-;;;-1:-1:-1;;;;;2246:54:186;;:::o;597:62:82:-;;;;;;;;;;;;;;-1:-1:-1;;;597:62:82;;;;:::o;635:156:35:-;715:5;;-1:-1:-1;;;;;715:5:35;701:10;:19;693:28;;;;;;739:5;;-1:-1:-1;;;;;739:5:35;:19;731:28;;;;;869:117:175;943:42;869:117;:::o;1280:2402::-;1490:26;;:::i;:::-;1518:32;;:::i;:::-;1566:40;1583:7;1592:4;1598:7;;1566:40;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1566:16:175;;-1:-1:-1;;;1566:40:175:i;:::-;1489:117;;;;1617:21;943:42;-1:-1:-1;;;;;1641:23:175;;1665:38;1683:9;:19;;;1665:17;:38::i;:::-;1641:63;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;1617:87;;1714:21;943:42;-1:-1:-1;;;;;1738:23:175;;1762:38;1780:9;:19;;;1762:17;:38::i;:::-;1738:63;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;1714:87;;1857:61;1882:9;:15;;;1900:8;1910:7;1857:16;:61::i;:::-;1997:15;;;;;2037:20;;1980:78;;-1:-1:-1;;;1980:78:175;;-1:-1:-1;;;;;1980:41:175;;;;;;:78;;2022:13;;1980:78;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;846:3;2069:12;:26;;:40;;;;;2156:9;:15;;;-1:-1:-1;;;;;2139:39:175;;:41;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;2119:61:175;:17;;;;:61;;;;2195:18;;;:23;;2217:1;2195:23;2191:1095;;;2251:22;;;;2316:21;;;;2292:20;;-1:-1:-1;;;;;2292:45:175;;;;;;2288:145;;2399:19;2405:12;2399:5;:19::i;:::-;2386:32;-1:-1:-1;;2288:145:175;2447:79;2478:9;:15;;;2496:12;:21;;;2519:6;2447:22;:79::i;:::-;2191:1095;;;;2547:9;:18;;;:23;;2569:1;2547:23;2543:743;;;2640:12;:21;;;-1:-1:-1;;;;;2616:45:175;:12;:20;;;-1:-1:-1;;;;;2616:45:175;;2612:418;;2708:14;;;2681:23;;;:42;2741:18;2681:12;2741:4;:18::i;:::-;-1:-1:-1;;2865:15:175;;;;;2903:20;;2945:52;;-1:-1:-1;;;2945:52:175;;2819:196;;2903:20;-1:-1:-1;;;;;2945:37:175;;;;;:52;;2991:4;;2945:52;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2819:16;:196::i;:::-;2543:743;;;3157:15;;;;3191:20;;3109:166;;3157:15;3229:32;3191:20;3229:10;:32::i;:::-;3109:22;:166::i;:::-;3351:9;:15;;;-1:-1:-1;;;;;3334:41:175;;3376:13;3391:9;:20;;;3334:78;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;3443:60:175;3475:8;3485:17;:7;3497:4;3485:11;:17::i;:::-;3443:31;:60::i;:::-;3583:21;:25;3579:97;;3624:41;;:9;;3643:21;3624:41;;;;;;;;;3643:21;3624:9;:41;;;;;;;;;;;;;;;;;;;;;3579:97;1280:2402;;;;;;;;;:::o;143:20:35:-;;;-1:-1:-1;;;;;143:20:35;;:::o;197:174:189:-;278:86;298:5;328:23;;;353:2;357:5;305:58;;;;;;;;;:::i;:::-;;;;-1:-1:-1;;305:58:189;;;;;;;;;;;;;;-1:-1:-1;;;;;305:58:189;-1:-1:-1;;;;;;305:58:189;;;;;;;;;;278:19;:86::i;:::-;197:174;;;:::o;3688:3041:175:-;3844:26;;:::i;:::-;3872:32;;:::i;:::-;3921:47;;:::i;:::-;3978:13;4061:7;4037:110;;;;;;;;;;;;:::i;:::-;4002:145;;-1:-1:-1;4002:145:175;;-1:-1:-1;4002:145:175;-1:-1:-1;4158:23:175;;4250:14;;;4224:22;4280;;:56;;;;;;;;;4276:1200;;;4490:13;;;;4521:19;;;;4583:20;;;;4391:226;;;;4490:13;4521:19;4558:7;;4391:226;;;:::i;:::-;;;;-1:-1:-1;;4391:226:175;;;;;;;;;;;;;;-1:-1:-1;;;;;4391:226:175;-1:-1:-1;;;4391:226:175;;;;-1:-1:-1;4276:1200:175;;;4664:35;4638:22;;:61;;;;;;;;;4634:842;;;4770:35;4748:9;:18;;;:57;;;;;;;;;4744:722;;;4979:9;:19;;;5020:9;:19;;;5061:7;5090:12;:22;;;4867:263;;;;;;;;;;;:::i;:::-;;;;-1:-1:-1;;4867:263:175;;;;;;;;;;;;;;-1:-1:-1;;;;;4867:263:175;-1:-1:-1;;;4867:263:175;;;;-1:-1:-1;4744:722:175;;;5289:9;:19;;;5330:9;:19;;;5371:9;:20;;;5413:9;:20;;;5182:269;;;;;;;;;;;:::i;:::-;;;;-1:-1:-1;;5182:269:175;;;;;;;;;;;;;;-1:-1:-1;;;;;5182:269:175;-1:-1:-1;;;5182:269:175;;;;-1:-1:-1;4744:722:175;5514:30;5490:9;:20;;;:54;;;;;;;;;5486:897;;;5687:9;:13;;;5718:9;:19;;;5755:14;5597:186;;;;;;;;;;:::i;:::-;;;;-1:-1:-1;;5597:186:175;;;;;;;;;;;;;;-1:-1:-1;;;;;5597:186:175;-1:-1:-1;;;5597:186:175;;;;-1:-1:-1;5486:897:175;;;5828:35;5804:9;:20;;;:59;;;;;;;;;5800:583;;;5932:35;5910:9;:18;;;:57;;;;;;;;;5906:467;;;6074:9;:19;;;6029:65;;;;;;;;:::i;:::-;;;;-1:-1:-1;;6029:65:175;;;;;;;;;;;;;;-1:-1:-1;;;;;6029:65:175;-1:-1:-1;;;6029:65:175;;;;-1:-1:-1;5906:467:175;;;6244:9;:19;;;6285:9;:19;;;6326:14;6146:212;;;;;;;;;;:::i;:::-;;;;-1:-1:-1;;6146:212:175;;;;;;;;;;;;;;-1:-1:-1;;;;;6146:212:175;-1:-1:-1;;;6146:212:175;;;;-1:-1:-1;5906:467:175;6405:317;;;;;;;;6441:10;6405:317;;;;6477:10;6405:317;;;;6508:5;-1:-1:-1;;;;;6405:317:175;;;;;6537:9;:19;;;-1:-1:-1;;;;;6405:317:175;;;;;6587:9;:22;;;6581:29;;;;;;;;6405:317;;;;;;6641:9;:20;;;6635:27;;;;;;;;6405:317;;;;;;6692:9;:18;;;6686:25;;;;;;;;6405:317;;;;;6393:329;;3688:3041;;;;;;;;;;;:::o;7349:226::-;7413:13;7442:11;;;7438:131;;-1:-1:-1;7469:20:175;;;;;;;;;;;;-1:-1:-1;;;7469:20:175;;;;;;7438:131;7510:6;:11;;7520:1;7510:11;7506:63;;;-1:-1:-1;7537:21:175;;;;;;;;;;;;-1:-1:-1;;;7537:21:175;;;;;;7035:308;-1:-1:-1;;;;;7173:23:175;;762:42;7173:23;7169:168;;7212:45;-1:-1:-1;;;;;7212:28:175;;7241:6;7249:7;7212:28;:45::i;:::-;7169:168;;;7288:38;;-1:-1:-1;;;;;7288:15:175;;;7304:21;7288:38;;;;;;;;;7304:21;7288:15;:38;;;;;;;;;;;;;;;;;;;;;7035:308;;;:::o;1002:1627:82:-;1063:7;1072:4;1089:15;1114:17;1141:12;324:42:84;-1:-1:-1;;;;;1211:35:82;:6;:14;;;-1:-1:-1;;;;;1211:35:82;;1207:198;;;1293:14;;1279:29;;:13;:29::i;:::-;-1:-1:-1;;;;;1262:46:82;;;1375:16;;;;;1322:72;;-1:-1:-1;;;1322:72:82;;;;420:42:84;;1322:45:82;;1375:16;1322:72;;;;;1262:14;;1322:72;;;;;;;1375:16;420:42:84;1322:72:82;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1207:198;1435:75;1442:6;:16;;;1460:6;:11;;;1473:6;:14;;;1489:6;:20;;;1435:6;:75::i;:::-;1415:16;;;:95;;;;;;;;1587:19;;;;:25;;;:29;1583:222;;1658:34;1668:6;1676:15;1658:9;:34::i;:::-;1632:60;-1:-1:-1;1632:60:82;-1:-1:-1;1707:88:82;;;;1748:6;:19;;;:32;;;1738:42;;1707:88;1872:7;1867:126;;1910:34;1920:6;1928:15;1910:9;:34::i;:::-;1895:49;;1968:6;:14;;;1958:24;;1867:126;2106:1;2070:33;420:42:84;2070:10:82;:33::i;:::-;:37;2066:208;;;2187:62;;-1:-1:-1;;;2187:62:82;;420:42:84;;2123:46:82;;420:42:84;;2187:47:82;;:62;;2243:4;;2187:62;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2123:140;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2066:208;2288:15;;;;-1:-1:-1;;;;;2288:40:82;420:42:84;2288:40:82;2284:298;;;2385:39;2390:6;:15;;;2407:6;:16;;;2385:4;:39::i;:::-;2352:29;324:42:84;2352:10:82;:29::i;:::-;:72;;2426:16;;;;;;;;;;;;;-1:-1:-1;;;2426:16:82;;;2344:99;;;;;-1:-1:-1;;;2344:99:82;;;;;;;;:::i;:::-;;;;;;;;;;2284:298;;;2513:39;2518:6;:15;;;2535:6;:16;;;2513:4;:39::i;:::-;2482:27;2493:6;:15;;;2482:10;:27::i;:::-;:70;;2554:16;;;;;;;;;;;;;-1:-1:-1;;;2554:16:82;;;2474:97;;;;;-1:-1:-1;;;2474:97:82;;;;;;;;:::i;:::-;;2284:298;-1:-1:-1;2600:7:82;;-1:-1:-1;2609:12:82;-1:-1:-1;1002:1627:82;;;:::o;6735:294:175:-;-1:-1:-1;;;;;6879:23:175;;762:42;6879:23;6875:99;;6918:45;-1:-1:-1;;;;;6918:28:175;;6947:6;6955:7;6918:28;:45::i;2891:1600:82:-;2951:7;2960:4;2977:15;3002:17;3029:12;3060:6;:17;;;3081:1;3060:22;;3084:23;;;;;;;;;;;;;-1:-1:-1;;;3084:23:82;;;3052:56;;;;;-1:-1:-1;;;3052:56:82;;;;;;;;:::i;:::-;;3139:75;3146:6;:16;;;3164:6;:11;;;3177:6;:14;;;3193:6;:20;;;3139:6;:75::i;:::-;3119:16;;;:95;;;;;;;;3272:14;;-1:-1:-1;;;;;3272:35:82;324:42:84;3272:35:82;3268:198;;;3354:14;;3340:29;;:13;:29::i;:::-;-1:-1:-1;;;;;3323:46:82;;;3436:16;;;;;3383:72;;-1:-1:-1;;;3383:72:82;;;;420:42:84;;3383:45:82;;3436:16;3383:72;;;;;3323:14;;3383:72;;;;;;;3436:16;420:42:84;3383:72:82;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3268:198;3480:19;;;;:25;;;:29;3476:221;;3551:33;3561:6;3569:14;3551:9;:33::i;:::-;3525:59;-1:-1:-1;3525:59:82;-1:-1:-1;3599:88:82;;;;3640:6;:19;;;:32;;;3630:42;;3599:88;3764:7;3759:125;;3802:33;3812:6;3820:14;3802:9;:33::i;:::-;3787:48;;3859:6;:14;;;3849:24;;3759:125;3997:1;3961:33;420:42:84;3961:10:82;:33::i;:::-;:37;3957:208;;;4078:62;;-1:-1:-1;;;4078:62:82;;420:42:84;;4014:46:82;;420:42:84;;4078:47:82;;:62;;4134:4;;4078:62;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4014:140;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3957:208;4179:15;;;;-1:-1:-1;;;;;4179:40:82;420:42:84;4179:40:82;4175:254;;;4276:6;:17;;;4243:29;324:42:84;4243:10:82;:29::i;:::-;:50;;4295:16;;;;;;;;;;;;;-1:-1:-1;;;4295:16:82;;;4235:77;;;;;-1:-1:-1;;;4235:77:82;;;;;;;;:::i;:::-;;4175:254;;;4382:6;:17;;;4351:27;4362:6;:15;;;4351:10;:27::i;:::-;:48;;4401:16;;;;;;;;;;;;;-1:-1:-1;;;4401:16:82;;;4343:75;;;;;-1:-1:-1;;;4343:75:82;;;;;;;;:::i;:::-;;4175:254;4447:7;4456:27;4467:6;:15;;;4456:10;:27::i;:::-;4439:45;;;;;;;2891:1600;;;:::o;1040:269:84:-;1103:12;-1:-1:-1;;;;;1131:31:84;;324:42;1131:31;1127:176;;;-1:-1:-1;1188:21:84;1127:176;;;1250:42;;-1:-1:-1;;;1250:42:84;;-1:-1:-1;;;;;1250:27:84;;;;;:42;;1286:4;;1250:42;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;48:176:190:-;106:7;137:5;;;160:6;;;;152:46;;;;-1:-1:-1;;;152:46:190;;;;;;;:::i;:::-;216:1;48:176;-1:-1:-1;;;48:176:190:o;2463:215:186:-;2559:20;2582:17;;;;;;;;;-1:-1:-1;;;;;2582:17:186;-1:-1:-1;;;;;2582:36:186;;:38;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2559:61;;2631:40;2648:4;2653:8;2663:7;2631:16;:40::i;1639:412:189:-;1719:23;1745:69;1773:4;1745:69;;;;;;;;;;;;;;;;;1753:5;-1:-1:-1;;;;;1745:27:189;;;:69;;;;;:::i;:::-;1828:17;;1719:95;;-1:-1:-1;1828:21:189;1824:221;;1968:10;1957:30;;;;;;;;;;;;:::i;:::-;1949:85;;;;-1:-1:-1;;;1949:85:189;;;;;;;:::i;3751:149:84:-;3811:7;-1:-1:-1;;;;;3837:25:84;;324:42;3837:25;:56;;3889:4;3837:56;;;-1:-1:-1;420:42:84;;3751:149;-1:-1:-1;3751:149:84:o;2118:882::-;2224:17;2257:19;;;;;:71;;-1:-1:-1;2280:48:84;;-1:-1:-1;;;2280:48:84;;622:42;;2280:41;;:48;;2322:5;;2280:48;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2253:172;;;2361:53;;-1:-1:-1;;;2361:53:84;;622:42;;2361:46;;:53;;2408:5;;2361:53;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2344:70;;2253:172;2439:19;2435:559;;-1:-1:-1;2486:1:84;2435:559;;;2540:14;2530:7;:24;;;;;;;-1:-1:-1;2653:2:84;2643:7;:12;2630:9;:26;2626:89;;;-1:-1:-1;2698:2:84;2688:12;;2626:89;2729:18;529:42;-1:-1:-1;;;;;2750:24:84;;:26;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2729:47;-1:-1:-1;;;;;;2795:27:84;;324:42;2795:27;2791:193;;;2842:39;;-1:-1:-1;;;;;2842:28:84;;;:39;;;;;2871:9;;2842:39;;;;2871:9;2842:28;:39;;;;;;;;;;;;;;;;;;;;;2791:193;;;2920:49;-1:-1:-1;;;;;2920:26:84;;2947:10;2959:9;2920:26;:49::i;:::-;2435:559;;2118:882;;;;;;:::o;4618:676:82:-;4804:20;;;;:33;;;4761:77;;-1:-1:-1;;;4761:77:82;;4723:12;;;;814:42:84;;4761::82;;:77;;4804:33;4761:77;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4756:126;;-1:-1:-1;4862:5:82;;-1:-1:-1;4862:5:82;4854:17;;4756:126;4954:20;;;;:28;4897:86;;-1:-1:-1;;;4897:86:82;;720:42:84;;4897:56:82;;:86;;4954:28;4897:86;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4892:135;;-1:-1:-1;5007:5:82;;-1:-1:-1;5007:5:82;4999:17;;4892:135;5100:20;;;;:28;5130:17;;;;5070:15;;5064:84;;-1:-1:-1;;;;;5064:35:82;;;;;:84::i;:::-;5191:20;;;;:28;;5238:32;;;;;5166:121;;-1:-1:-1;;;5166:121:82;;-1:-1:-1;;;;;5166:64:82;;;;;;5238:32;5166:121;;5191:7;;5281:5;;5166:121;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;5159:128;;;;4618:676;;;;;;:::o;5554:705::-;5732:15;;;;5675:73;;-1:-1:-1;;;5675:73:82;;5638:17;;720:42:84;;5675:56:82;;:73;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;5750:19;;;;;;;;;;;;;-1:-1:-1;;;5750:19:82;;;5667:103;;;;;-1:-1:-1;;;5667:103:82;;;;;;;;:::i;:::-;-1:-1:-1;5817:15:82;;;;5834:17;;;;5787:15;;5781:71;;-1:-1:-1;;;;;5781:35:82;;;;;:71::i;:::-;5876:15;5867:5;:24;;;;;;;;;5863:390;;;5942:7;:15;;;-1:-1:-1;;;;;5922:62:82;;5985:7;:15;;;6002:7;:16;;;6020:7;:17;;;6039:7;:19;;;5922:137;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;5907:152;;5863:390;;;6125:7;:15;;;-1:-1:-1;;;;;6105:61:82;;6167:7;:15;;;6184:7;:16;;;6202:7;:18;;;6222:7;:19;;;6105:137;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;1036:120:3:-;1095:9;988:6;1120:23;1124:9;1128:1;1131;1124:3;:9::i;:::-;1135:7;1120:3;:23::i;:::-;:29;;;;;;;1036:120;-1:-1:-1;;;1036:120:3:o;2684:345:186:-;2810:26;:24;:26::i;:::-;-1:-1:-1;;;;;2798:38:186;:8;-1:-1:-1;;;;;2798:38:186;;2795:164;;;2891:12;-1:-1:-1;;;;;2891:17:186;2916:7;2891:37;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2942:7;;2795:164;2969:51;-1:-1:-1;;;;;2969:28:186;;2998:12;3012:7;2969:28;:51::i;1237:194:180:-;1340:12;1371:53;1394:6;1402:4;1408:1;1411:12;1371:22;:53::i;282:125:3:-;340:9;369:6;;;:30;;-1:-1:-1;;384:5:3;;;398:1;393;384:5;393:1;379:15;;;;;:20;369:30;361:39;;;;;48:111;140:5;;;135:16;;;;127:25;;;;;1990:119:186;2060:42;1990:119;:::o;1986:958:180:-;2116:12;2148:18;2159:6;2148:10;:18::i;:::-;2140:60;;;;-1:-1:-1;;;2140:60:180;;;;;;;:::i;:::-;2271:12;2285:23;2312:6;-1:-1:-1;;;;;2312:11:180;2332:8;2343:4;2312:36;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2270:78;;;;2362:7;2358:580;;;2392:10;-1:-1:-1;2385:17:180;;-1:-1:-1;2385:17:180;2358:580;2503:17;;:21;2499:429;;2761:10;2755:17;2821:15;2808:10;2804:2;2800:19;2793:44;2710:145;2900:12;2893:20;;-1:-1:-1;;;2893:20:180;;;;;;;;:::i;47:610::-;107:4;568:20;;413:66;607:23;;;;;;:42;;-1:-1:-1;;634:15:180;;;599:51;-1:-1:-1;;47:610:180:o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;5:130::-;72:20;;97:33;72:20;97:33;:::i;142:134::-;220:13;;238:33;220:13;238:33;:::i;440:128::-;515:13;;34510;;34503:21;37451:32;;37441:2;;37497:1;;37487:12;1075:440;;1176:3;1169:4;1161:6;1157:17;1153:27;1143:2;;-1:-1;;1184:12;1143:2;1231:6;1218:20;1253:64;1268:48;1309:6;1268:48;:::i;:::-;1253:64;:::i;:::-;1244:73;;1337:6;1330:5;1323:21;1441:3;1373:4;1432:6;1365;1423:16;;1420:25;1417:2;;;1458:1;;1448:12;1417:2;36563:6;1373:4;1365:6;1361:17;1373:4;1399:5;1395:16;36540:30;36619:1;36601:16;;;1373:4;36601:16;36594:27;1399:5;1136:379;-1:-1;;1136:379::o;1524:442::-;;1636:3;1629:4;1621:6;1617:17;1613:27;1603:2;;-1:-1;;1644:12;1603:2;1684:6;1678:13;1706:64;1721:48;1762:6;1721:48;:::i;1706:64::-;1697:73;;1790:6;1783:5;1776:21;1894:3;1826:4;1885:6;1818;1876:16;;1873:25;1870:2;;;1911:1;;1901:12;1870:2;1921:39;1953:6;1826:4;1852:5;1848:16;1826:4;1818:6;1814:17;1921:39;:::i;:::-;;1596:370;;;;:::o;1974:164::-;2067:13;;37721:1;37711:12;;37701:2;;37737:1;;37727:12;2145:162;2237:13;;37836:1;37826:12;;37816:2;;37852:1;;37842:12;2314:156;2403:13;;37948:1;37938:12;;37928:2;;37964:1;;37954:12;4468:2007;;4599:6;;4587:9;4582:3;4578:19;4574:32;4571:2;;;-1:-1;;4609:12;4571:2;4637:22;4599:6;4637:22;:::i;:::-;4628:31;;;4744:60;4800:3;4776:22;4744:60;:::i;:::-;4726:16;4719:86;4903:60;4959:3;4870:2;4939:9;4935:22;4903:60;:::i;:::-;4870:2;4889:5;4885:16;4878:86;5030:2;5099:9;5095:22;11617:13;5030:2;5049:5;5045:16;5038:86;5191:2;5260:9;5256:22;11617:13;5191:2;5210:5;5206:16;5199:86;5350:3;5420:9;5416:22;11617:13;5350:3;5370:5;5366:16;5359:86;5515:3;5585:9;5581:22;11617:13;5515:3;5535:5;5531:16;5524:86;5705:60;5761:3;5671;5741:9;5737:22;5705:60;:::i;:::-;5671:3;5691:5;5687:16;5680:86;5864:60;5920:3;5830;5900:9;5896:22;5864:60;:::i;:::-;5830:3;5850:5;5846:16;5839:86;6014:3;;6003:9;5999:19;5993:26;6039:18;;6031:6;6028:30;6025:2;;;4712:1;;6061:12;6025:2;6108:69;6173:3;6164:6;6153:9;6149:22;6108:69;:::i;:::-;6014:3;6092:5;6088:18;6081:97;6268:3;;;;6257:9;6253:19;6247:26;6233:40;;6039:18;6285:6;6282:30;6279:2;;;4712:1;;6315:12;6279:2;;6362:91;6449:3;6440:6;6429:9;6425:22;6362:91;:::i;:::-;6268:3;6346:5;6342:18;6335:119;;;4565:1910;;;;:::o;8916:1180::-;;9036:4;9024:9;9019:3;9015:19;9011:30;9008:2;;;-1:-1;;9044:12;9008:2;9072:20;9036:4;9072:20;:::i;:::-;9063:29;;85:6;72:20;97:33;124:5;97:33;:::i;:::-;9152:75;;9296:2;9350:22;;72:20;97:33;72:20;97:33;:::i;:::-;9296:2;9311:16;;9304:75;9451:2;9505:22;;72:20;97:33;72:20;97:33;:::i;:::-;9484:49;9451:2;9470:5;9466:16;9459:75;;9596:2;9654:9;9650:22;11469:20;9596:2;9615:5;9611:16;9604:75;9747:3;9806:9;9802:22;11469:20;9747:3;9767:5;9763:16;9756:75;9924:3;9913:9;9909:19;9896:33;9949:18;9941:6;9938:30;9935:2;;;-1:-1;;9971:12;9935:2;10016:58;10070:3;10061:6;10050:9;10046:22;10016:58;:::i;:::-;9924:3;10002:5;9998:16;9991:84;;9002:1094;;;;:::o;10145:1250::-;;10276:4;10264:9;10259:3;10255:19;10251:30;10248:2;;;-1:-1;;10284:12;10248:2;10312:20;10276:4;10312:20;:::i;:::-;10303:29;;226:6;220:13;238:33;265:5;238:33;:::i;:::-;10392:86;;10547:2;10612:22;;220:13;238:33;220:13;238:33;:::i;:::-;10547:2;10562:16;;10555:86;10713:2;10778:22;;220:13;238:33;220:13;238:33;:::i;:::-;10746:60;10713:2;10732:5;10728:16;10721:86;;10869:2;10938:9;10934:22;11617:13;10869:2;10888:5;10884:16;10877:86;11031:3;11101:9;11097:22;11617:13;11031:3;11051:5;11047:16;11040:86;11212:3;11201:9;11197:19;11191:26;11237:18;11229:6;11226:30;11223:2;;;-1:-1;;11259:12;11223:2;11304:69;11369:3;11360:6;11349:9;11345:22;11304:69;:::i;11680:241::-;;11784:2;11772:9;11763:7;11759:23;11755:32;11752:2;;;-1:-1;;11790:12;11752:2;85:6;72:20;97:33;124:5;97:33;:::i;11928:263::-;;12043:2;12031:9;12022:7;12018:23;12014:32;12011:2;;;-1:-1;;12049:12;12011:2;226:6;220:13;238:33;265:5;238:33;:::i;12484:366::-;;;12605:2;12593:9;12584:7;12580:23;12576:32;12573:2;;;-1:-1;;12611:12;12573:2;85:6;72:20;97:33;124:5;97:33;:::i;:::-;12663:63;12763:2;12802:22;;;;11469:20;;-1:-1;;;12567:283::o;12857:741::-;;;;;;13031:3;13019:9;13010:7;13006:23;13002:33;12999:2;;;-1:-1;;13038:12;12999:2;85:6;72:20;97:33;124:5;97:33;:::i;:::-;13090:63;-1:-1;13190:2;13229:22;;11469:20;;-1:-1;13298:2;13337:22;;11469:20;;-1:-1;13434:2;13419:18;;13406:32;13458:18;13447:30;;;13444:2;;;-1:-1;;13480:12;13444:2;13565:6;13554:9;13550:22;;;844:3;837:4;829:6;825:17;821:27;811:2;;-1:-1;;852:12;811:2;895:6;882:20;13458:18;914:6;911:30;908:2;;;-1:-1;;944:12;908:2;1039:3;13190:2;1019:17;980:6;1005:32;;1002:41;999:2;;;-1:-1;;1046:12;999:2;12993:605;;;;-1:-1;12993:605;;-1:-1;13190:2;976:17;;13500:82;12993:605;-1:-1;;;12993:605::o;13605:257::-;;13717:2;13705:9;13696:7;13692:23;13688:32;13685:2;;;-1:-1;;13723:12;13685:2;521:6;515:13;37476:5;34510:13;34503:21;37454:5;37451:32;37441:2;;-1:-1;;37487:12;13869:393;;;13998:2;13986:9;13977:7;13973:23;13969:32;13966:2;;;-1:-1;;14004:12;13966:2;14066:61;14119:7;14095:22;14066:61;:::i;:::-;14056:71;;14164:2;14218:9;14214:22;11617:13;14172:74;;13960:302;;;;;:::o;14269:263::-;;14384:2;14372:9;14363:7;14359:23;14355:32;14352:2;;;-1:-1;;14390:12;14352:2;-1:-1;653:13;;14346:186;-1:-1;14346:186::o;14539:345::-;;14652:2;14640:9;14631:7;14627:23;14623:32;14620:2;;;-1:-1;;14658:12;14620:2;14716:17;14703:31;14754:18;14746:6;14743:30;14740:2;;;-1:-1;;14776:12;14740:2;14806:62;14860:7;14851:6;14840:9;14836:22;14806:62;:::i;14891:389::-;;15026:2;15014:9;15005:7;15001:23;14997:32;14994:2;;;-1:-1;;15032:12;14994:2;15090:17;15077:31;15128:18;;15120:6;15117:30;15114:2;;;-1:-1;;15150:12;15114:2;15247:6;15236:9;15232:22;;;2639:6;;2627:9;2622:3;2618:19;2614:32;2611:2;;;-1:-1;;2649:12;2611:2;2677:22;2639:6;2677:22;:::i;:::-;2668:31;;2784:49;2829:3;2805:22;2784:49;:::i;:::-;2766:16;2759:75;2932:49;2977:3;15026:2;2957:9;2953:22;2932:49;:::i;:::-;15026:2;2918:5;2914:16;2907:75;3048:2;3106:9;3102:22;11469:20;3048:2;3067:5;3063:16;3056:75;3198:2;3256:9;3252:22;11469:20;3198:2;3217:5;3213:16;3206:75;3346:3;3405:9;3401:22;11469:20;3346:3;3366:5;3362:16;3355:75;3500:3;3559:9;3555:22;11469:20;3500:3;3520:5;3516:16;3509:75;3679:49;3724:3;3645;3704:9;3700:22;3679:49;:::i;:::-;3645:3;3665:5;3661:16;3654:75;3827:49;3872:3;3793;3852:9;3848:22;3827:49;:::i;:::-;3793:3;3813:5;3809:16;3802:75;3973:3;;3962:9;3958:19;3945:33;15128:18;3990:6;3987:30;3984:2;;;-1:-1;;4020:12;3984:2;4067:58;4121:3;4112:6;4101:9;4097:22;4067:58;:::i;:::-;3973:3;4051:5;4047:18;4040:86;;;4223:3;;4212:9;4208:19;4195:33;15128:18;4240:6;4237:30;4234:2;;;-1:-1;;4270:12;4234:2;4317:80;4393:3;4384:6;4373:9;4369:22;4317:80;:::i;:::-;4297:18;;;4290:108;;;;-1:-1;4301:5;14988:292;-1:-1;;;;;14988:292::o;15287:404::-;;15433:2;15421:9;15412:7;15408:23;15404:32;15401:2;;;-1:-1;;15439:12;15401:2;15490:17;15484:24;15528:18;15520:6;15517:30;15514:2;;;-1:-1;;15550:12;15514:2;15580:95;15667:7;15658:6;15647:9;15643:22;15580:95;:::i;15698:759::-;;;;15906:9;15897:7;15893:23;15918:3;15893:23;15889:33;15886:2;;;-1:-1;;15925:12;15886:2;6658:6;;6637:19;6633:32;6630:2;;;-1:-1;;6668:12;6630:2;6696:22;6658:6;6696:22;:::i;:::-;6687:31;;6808:75;6879:3;6855:22;6808:75;:::i;:::-;6790:16;6783:101;6984:75;7055:3;6951:2;7035:9;7031:22;6984:75;:::i;:::-;6951:2;6970:5;6966:16;6959:101;7158:74;7228:3;7125:2;7208:9;7204:22;7158:74;:::i;:::-;7125:2;7144:5;7140:16;7133:100;7328:71;7395:3;7295:2;7375:9;7371:22;7328:71;:::i;:::-;7295:2;7314:5;7310:16;7303:97;7500:57;7553:3;7466;7533:9;7529:22;7500:57;:::i;:::-;7466:3;7486:5;7482:16;7475:83;7625:3;7695:9;7691:22;11617:13;7625:3;7645:5;7641:16;7634:86;7787:3;7857:9;7853:22;11617:13;7787:3;7807:5;7803:16;7796:86;7982:60;8038:3;7948;8018:9;8014:22;7982:60;:::i;:::-;7948:3;7968:5;7964:16;7957:86;8109:3;8145:60;8201:3;8109;8181:9;8177:22;8145:60;:::i;:::-;8125:18;;;8118:88;8272:3;8308:60;8364:3;8340:22;;;8308:60;:::i;:::-;8288:18;;;8281:88;8435:3;8471:60;8527:3;8503:22;;;8471:60;:::i;:::-;8451:18;;;8444:88;8592:3;8660:22;;;11617:13;8608:18;;;8601:88;8749:3;8817:22;;;11617:13;8765:18;;;8758:88;16126:19;;16120:26;8455:5;;-1:-1;16166:18;16155:30;;16152:2;;;-1:-1;;16188:12;16152:2;16218:95;16305:7;16296:6;16285:9;16281:22;16218:95;:::i;:::-;16208:105;;;16369:72;16433:7;16350:3;16413:9;16409:22;16369:72;:::i;:::-;16359:82;;15880:577;;;;;:::o;16883:103::-;-1:-1;;;;;34822:54;16944:37;;16938:48::o;17113:323::-;;17245:5;33413:12;33688:6;33683:3;33676:19;17328:52;17373:6;33725:4;33720:3;33716:14;33725:4;17354:5;17350:16;17328:52;:::i;:::-;36996:7;36980:14;-1:-1;;36976:28;17392:39;;;;33725:4;17392:39;;17193:243;-1:-1;;17193:243::o;20692:1990::-;;20855:6;20955:63;21003:14;20932:16;20926:23;20955:63;:::i;:::-;21101:4;21094:5;21090:16;21084:23;21113:63;21101:4;21165:3;21161:14;21147:12;21113:63;:::i;:::-;;21260:4;21253:5;21249:16;21243:23;21260:4;21324:3;21320:14;24059:37;21420:4;21413:5;21409:16;21403:23;21420:4;21484:3;21480:14;24059:37;21578:4;21571:5;21567:16;21561:23;21578:4;21642:3;21638:14;24059:37;21741:4;21734:5;21730:16;21724:23;21741:4;21805:3;21801:14;24059:37;21895:4;21888:5;21884:16;21878:23;21907:63;21895:4;21959:3;21955:14;21941:12;21907:63;:::i;:::-;;22052:4;22045:5;22041:16;22035:23;22064:63;22052:4;22116:3;22112:14;22098:12;22064:63;:::i;:::-;;22213:6;;22206:5;22202:18;22196:25;20855:6;22213;22245:3;22241:16;22234:40;22289:71;20855:6;20850:3;20846:16;22341:12;22289:71;:::i;:::-;22281:79;;;;22453:6;;22446:5;22442:18;22436:25;22509:3;22503:4;22499:14;22453:6;22485:3;22481:16;22474:40;22529:115;22639:4;22625:12;22529:115;:::i;:::-;22666:11;20828:1854;-1:-1;;;;;;20828:1854::o;22770:1221::-;;6039:18;;34833:42;;;;22998:16;22992:23;34822:54;16951:3;16944:37;34833:42;23171:4;23164:5;23160:16;23154:23;34822:54;23171:4;23235:3;23231:14;16944:37;34833:42;23336:4;23329:5;23325:16;23319:23;34822:54;23336:4;23400:3;23396:14;16944:37;;23491:4;23484:5;23480:16;23474:23;23491:4;23555:3;23551:14;24059:37;23652:4;23645:5;23641:16;23635:23;23652:4;23716:3;23712:14;24059:37;23810:4;23803:5;23799:16;23793:23;22923:4;23810;23840:3;23836:14;23829:38;23882:71;22923:4;22918:3;22914:14;23934:12;23882:71;:::i;24228:271::-;;17953:5;33413:12;18064:52;18109:6;18104:3;18097:4;18090:5;18086:16;18064:52;:::i;:::-;18128:16;;;;;24362:137;-1:-1;;24362:137::o;24506:379::-;24870:10;24694:191::o;24892:222::-;-1:-1;;;;;34822:54;;;;16944:37;;25019:2;25004:18;;24990:124::o;25366:444::-;-1:-1;;;;;34822:54;;;16944:37;;34822:54;;;;25713:2;25698:18;;16944:37;25796:2;25781:18;;24059:37;;;;25549:2;25534:18;;25520:290::o;25817:640::-;-1:-1;;;;;34822:54;;;16944:37;;34822:54;;26211:2;26196:18;;16944:37;26294:2;26279:18;;24059:37;;;26046:3;26331:2;26316:18;;26309:48;;;25817:640;;26371:76;;26031:19;;26433:6;26371:76;:::i;26464:556::-;-1:-1;;;;;34822:54;;;16944:37;;34822:54;;;;26840:2;26825:18;;16944:37;26923:2;26908:18;;24059:37;27006:2;26991:18;;24059:37;;;;26675:3;26660:19;;26646:374::o;27027:417::-;-1:-1;;;;;34822:54;;16944:37;;27200:2;27318;27303:18;;27296:48;;;27027:417;;27358:76;;27185:18;;27420:6;27358:76;:::i;27451:333::-;-1:-1;;;;;34822:54;;;;16944:37;;27770:2;27755:18;;24059:37;27606:2;27591:18;;27577:207::o;27791:306::-;;27936:2;27957:17;27950:47;28011:76;27936:2;27925:9;27921:18;28073:6;28011:76;:::i;29282:416::-;29482:2;29496:47;;;19469:2;29467:18;;;33676:19;19505:29;33716:14;;;19485:50;19554:12;;;29453:245::o;29705:416::-;29905:2;29919:47;;;20110:2;29890:18;;;33676:19;20146:31;33716:14;;;20126:52;20197:12;;;29876:245::o;30128:416::-;30328:2;30342:47;;;20448:2;30313:18;;;33676:19;20484:34;33716:14;;;20464:55;-1:-1;;;20539:12;;;20532:34;20585:12;;;30299:245::o;30551:394::-;;30740:2;30761:17;30754:47;30815:120;30740:2;30729:9;30725:18;30921:6;30815:120;:::i;30952:533::-;;31183:2;31204:17;31197:47;31258:120;31183:2;31172:9;31168:18;31364:6;31258:120;:::i;:::-;31250:128;;37102:1;37095:5;37092:12;37082:2;;37108:9;37082:2;36186:40;31471:2;31460:9;31456:18;18814:64;31154:331;;;;;:::o;31492:222::-;24059:37;;;31619:2;31604:18;;31590:124::o;31721:444::-;24059:37;;;-1:-1;;;;;34822:54;;;;32068:2;32053:18;;16944:37;32151:2;32136:18;;24059:37;31904:2;31889:18;;31875:290::o;32172:556::-;24059:37;;;-1:-1;;;;;34822:54;;;;32548:2;32533:18;;16944:37;32631:2;32616:18;;24059:37;32714:2;32699:18;;24059:37;32383:3;32368:19;;32354:374::o;32735:256::-;32797:2;32791:9;32823:17;;;32898:18;32883:34;;32919:22;;;32880:62;32877:2;;;32955:1;;32945:12;32877:2;32797;32964:22;32775:216;;-1:-1;32775:216::o;32998:321::-;;33141:18;33133:6;33130:30;33127:2;;;-1:-1;;33163:12;33127:2;-1:-1;36996:7;33217:17;-1:-1;;33213:33;33304:4;33294:15;;33064:255::o;36636:268::-;36701:1;36708:101;36722:6;36719:1;36716:13;36708:101;;;36789:11;;;36783:18;36770:11;;;36763:39;36744:2;36737:10;36708:101;;;36824:6;36821:1;36818:13;36815:2;;;-1:-1;;36701:1;36871:16;;36864:27;36685:219::o;37131:117::-;-1:-1;;;;;34822:54;;37190:35;;37180:2;;37239:1;;37229:12;37180:2;37174:74;:::o
Swarm Source
ipfs://f46cd6e2d90a15203d39e69eb0be6e334469231c1ac66b7a111a655ad6cb98ca
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.