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
|
|||
---|---|---|---|---|---|---|
11700072 | 1434 days ago | 61.64305952 ETH | ||||
11700072 | 1434 days ago | 61.64305952 ETH | ||||
11699452 | 1434 days ago | 11.23276805 ETH | ||||
11699452 | 1434 days ago | 11.23276805 ETH | ||||
11699452 | 1434 days ago | 23.40569065 ETH | ||||
11699452 | 1434 days ago | 0.05866087 ETH | ||||
11699452 | 1434 days ago | 23.46435153 ETH | ||||
11699362 | 1434 days ago | 17.64390589 ETH | ||||
11699362 | 1434 days ago | 17.64390589 ETH | ||||
11699362 | 1434 days ago | 33.13125177 ETH | ||||
11699362 | 1434 days ago | 0.08303571 ETH | ||||
11699362 | 1434 days ago | 33.21428749 ETH | ||||
11699036 | 1434 days ago | 89.57161258 ETH | ||||
11699036 | 1434 days ago | 89.57161258 ETH | ||||
11698962 | 1434 days ago | 14.36249717 ETH | ||||
11698962 | 1434 days ago | 14.36249717 ETH | ||||
11698962 | 1434 days ago | 37.83867246 ETH | ||||
11698962 | 1434 days ago | 0.09483376 ETH | ||||
11698962 | 1434 days ago | 37.93350623 ETH | ||||
11698716 | 1434 days ago | 0.0103852 ETH | ||||
11698716 | 1434 days ago | 0.0103852 ETH | ||||
11698716 | 1434 days ago | 0.028224 ETH | ||||
11698716 | 1434 days ago | 0.028224 ETH | ||||
11698710 | 1434 days ago | 0.01092 ETH | ||||
11698710 | 1434 days ago | 0.01092 ETH |
Loading...
Loading
Contract Name:
MCDCloseFlashLoan
Compiler Version
v0.6.12+commit.27d51765
Contract Source Code (Solidity Standard Json-Input format)
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; Manager public constant manager = Manager(0x5ef30b9986345249bc32d8928B7ee64DE9435E39); 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; closeCDP(closeData, exchangeData, user); } function closeCDP( CloseData memory _closeData, ExchangeData memory _exchangeData, address _user ) internal { paybackDebt(_closeData.cdpId, manager.ilks(_closeData.cdpId), _closeData.daiAmount); // payback whole debt uint drawnAmount = drawMaxCollateral(_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(uint _cdpId, address _joinAddr, uint _amount) internal returns (uint) { manager.frob(_cdpId, -toPositiveInt(_amount), 0); manager.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(uint _cdpId, bytes32 _ilk, uint _daiAmount) internal { address urn = manager.urns(_cdpId); daiJoin.dai().approve(DAI_JOIN_ADDRESS, _daiAmount); daiJoin.join(urn, _daiAmount); manager.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; 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); // ---------------- 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; pragma experimental ABIEncoderV2; import "../../mcd/saver/MCDSaverProxy.sol"; import "../../loggers/DefisaverLogger.sol"; import "../../interfaces/ILendingPool.sol"; import "../../exchangeV3/DFSExchangeData.sol"; import "../../utils/SafeERC20.sol"; contract MCDCreateTaker { using SafeERC20 for ERC20; address payable public constant MCD_CREATE_FLASH_LOAN = 0xF7d60f5F8783279D33552Cf789F0bbE8336e2e2d; 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 { 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 "../../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 MANAGER_ADDRESS = 0x5ef30b9986345249bc32d8928B7ee64DE9435E39; 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; Manager public constant manager = Manager(MANAGER_ADDRESS); 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 ) public payable { address user = getOwner(manager, _cdpId); bytes32 ilk = manager.ilks(_cdpId); drawCollateral(_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(_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 ) public payable { address user = getOwner(manager, _cdpId); bytes32 ilk = manager.ilks(_cdpId); uint daiDrawn = drawDai(_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(_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 _cdpId Id of the CDP /// @param _ilk Ilk of the CDP /// @param _daiAmount Amount of Dai to draw function drawDai(uint _cdpId, bytes32 _ilk, uint _daiAmount) internal returns (uint) { uint rate = Jug(JUG_ADDRESS).drip(_ilk); uint daiVatBalance = vat.dai(manager.urns(_cdpId)); uint maxAmount = getMaxDebt(_cdpId, _ilk); if (_daiAmount >= maxAmount) { _daiAmount = sub(maxAmount, 1); } manager.frob(_cdpId, int(0), normalizeDrawAmount(_daiAmount, rate, daiVatBalance)); manager.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 _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(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.ilks(_cdpId), manager.urns(_cdpId), address(this), address(this), convertAmount, 0 ); } /// @notice Draws collateral and returns it to DSProxy /// @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(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.frob(_cdpId, -toPositiveInt(frobAmount), 0); manager.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 /// @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(uint _cdpId, bytes32 _ilk, uint _daiAmount, address _owner) internal { address urn = manager.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.frob(_cdpId, 0, normalizePaybackAmount(VAT_ADDRESS, urn, _ilk)); } /// @notice Gets the maximum amount of collateral available to draw /// @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(uint _cdpId, bytes32 _ilk, address _joinAddr) public view returns (uint) { uint price = getPrice(_ilk); (uint collateral, uint debt) = getCdpInfo(manager, _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 _cdpId Id of the CDP /// @param _ilk Ilk of the CDP /// @dev Substracts 10 wei to aviod rounding error later on function getMaxDebt(uint _cdpId, bytes32 _ilk) public virtual view returns (uint) { uint price = getPrice(_ilk); (, uint mat) = spotter.ilks(_ilk); (uint collateral, uint debt) = getCdpInfo(manager, _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); } /// @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 getCdpDetailedInfo(uint _cdpId) public view returns (uint collateral, uint debt, uint price, bytes32 ilk) { address urn = manager.urns(_cdpId); ilk = manager.ilks(_cdpId); (collateral, debt) = vat.urns(ilk, urn); (,uint rate,,,) = vat.ilks(ilk); debt = rmul(debt, rate); price = getPrice(ilk); } 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); uint balance = ERC20(DAI_ADDRESS).balanceOf(address(this)); if (feeAmount > _amount / 10) { feeAmount = _amount / 10; } ERC20(DAI_ADDRESS).transfer(WALLET_ID, feeAmount); return feeAmount; } return 0; } }
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; 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; 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; 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 "./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; 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 { /// @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(); } }
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) { // calculate src amount based on price and add 5% /// @dev this value in exData object will be changed in parent method as well exData.srcAmount = div(mul(wdiv(exData.destAmount, exData.minPrice), 105), 100); 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; abstract contract PipInterface { function read() public virtual returns (bytes32); }
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 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; 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 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; 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; 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 DSAuthority { function canCall(address src, address dst, bytes4 sig) public virtual view returns (bool); }
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; 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"; 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; address payable public constant WALLET_ID = 0x322d58b9E75a6918f7e7849AEe0fF09369977e08; 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; } if (_token == KYBER_ETH_ADDRESS) { WALLET_ID.transfer(feeAmount); } else { ERC20(_token).safeTransfer(WALLET_ID, 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); }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../saver/MCDSaverProxy.sol"; import "../../exchangeV3/DFSExchangeData.sol"; import "../../utils/GasBurner.sol"; import "../../interfaces/ILendingPool.sol"; // abstract contract ILendingPool { // function flashLoan( address payable _receiver, address _reserve, uint _amount, bytes calldata _params) external virtual; // } contract MCDSaverTaker is MCDSaverProxy, GasBurner { address payable public constant MCD_SAVER_FLASH_LOAN = 0xD0eB57ff3eA4Def2b74dc29681fd529D1611880f; address public constant AAVE_POOL_CORE = 0x3dfd23A6c5E8BbcFc9581d2E864a68feb6a076d3; ILendingPool public constant lendingPool = ILendingPool(0x398eC7346DcD622eDc5ae82352F02bE94C62d119); function boostWithLoan( ExchangeData memory _exchangeData, uint _cdpId, uint _gasCost, address _joinAddr ) public payable burnGas(25) { uint256 maxDebt = getMaxDebt(_cdpId, manager.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); return; } uint256 loanAmount = sub(_exchangeData.srcAmount, maxDebt); loanAmount = loanAmount > maxLiq ? maxLiq : loanAmount; MCD_SAVER_FLASH_LOAN.transfer(msg.value); // 0x fee manager.cdpAllow(_cdpId, MCD_SAVER_FLASH_LOAN, 1); bytes memory paramsData = abi.encode(packExchangeData(_exchangeData), _cdpId, _gasCost, _joinAddr, false); lendingPool.flashLoan(MCD_SAVER_FLASH_LOAN, DAI_ADDRESS, loanAmount, paramsData); manager.cdpAllow(_cdpId, MCD_SAVER_FLASH_LOAN, 0); } function repayWithLoan( ExchangeData memory _exchangeData, uint _cdpId, uint _gasCost, address _joinAddr ) public payable burnGas(25) { uint256 maxColl = getMaxCollateral(_cdpId, manager.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); return; } uint256 loanAmount = sub(_exchangeData.srcAmount, maxColl); loanAmount = loanAmount > maxLiq ? maxLiq : loanAmount; MCD_SAVER_FLASH_LOAN.transfer(msg.value); // 0x fee manager.cdpAllow(_cdpId, MCD_SAVER_FLASH_LOAN, 1); bytes memory paramsData = abi.encode(packExchangeData(_exchangeData), _cdpId, _gasCost, _joinAddr, true); lendingPool.flashLoan(MCD_SAVER_FLASH_LOAN, getAaveCollAddr(_joinAddr), loanAmount, paramsData); manager.cdpAllow(_cdpId, MCD_SAVER_FLASH_LOAN, 0); } /// @notice Gets the maximum amount of debt available to generate /// @param _cdpId Id of the CDP /// @param _ilk Ilk of the CDP function getMaxDebt(uint256 _cdpId, bytes32 _ilk) public override view returns (uint256) { uint256 price = getPrice(_ilk); (, uint256 mat) = spotter.ilks(_ilk); (uint256 collateral, uint256 debt) = getCdpInfo(manager, _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 "../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 "../exchange/SaverExchangeCore.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 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( SaverExchangeCore.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( SaverExchangeCore.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); } ( uint[8] memory numData, address[8] memory addrData, uint8[3] memory enumData, bytes memory callData ) = _packData(_loanShift, _exchangeData); // encode data bytes memory paramsData = abi.encode(numData, addrData, enumData, callData, 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 CTokenInterface(_address).underlying(); } else if (_fromProtocol == Protocols.MCD) { return DAI_ADDRESS; } else { return address(0); } } function logEvent( SaverExchangeCore.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(); } } function _packData( LoanShiftData memory _loanShift, SaverExchangeCore.ExchangeData memory exchangeData ) internal pure returns (uint[8] memory numData, address[8] memory addrData, uint8[3] memory enumData, bytes memory callData) { numData = [ _loanShift.collAmount, _loanShift.debtAmount, _loanShift.id1, _loanShift.id2, exchangeData.srcAmount, exchangeData.destAmount, exchangeData.minPrice, exchangeData.price0x ]; addrData = [ _loanShift.addrLoan1, _loanShift.addrLoan2, _loanShift.debtAddr1, _loanShift.debtAddr2, exchangeData.srcAddr, exchangeData.destAddr, exchangeData.exchangeAddr, exchangeData.wrapper ]; enumData = [ uint8(_loanShift.fromProtocol), uint8(_loanShift.toProtocol), uint8(_loanShift.swapType) ]; callData = exchangeData.callData; } }
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 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; 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; 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; 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; 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; 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 payable public constant WALLET_ID = 0x322d58b9E75a6918f7e7849AEe0fF09369977e08; 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 "../../mcd/saver/MCDSaverProxy.sol"; import "../../loggers/DefisaverLogger.sol"; import "../../interfaces/ILendingPool.sol"; import "../../exchangeV3/DFSExchangeData.sol"; abstract contract IMCDSubscriptions { function unsubscribe(uint256 _cdpId) external virtual ; function subscribersPos(uint256 _cdpId) external virtual returns (uint256, bool); } contract MCDCloseTaker is MCDSaverProxyHelper { address public constant SUBSCRIPTION_ADDRESS_NEW = 0xC45d4f6B6bf41b6EdAA58B01c4298B8d9078269a; address public constant DEFISAVER_LOGGER = 0x5c55B921f590a89C1Ebe84dF170E655a82b62126; ILendingPool public constant lendingPool = ILendingPool(0x398eC7346DcD622eDc5ae82352F02bE94C62d119); // solhint-disable-next-line const-name-snakecase Manager public constant manager = Manager(0x5ef30b9986345249bc32d8928B7ee64DE9435E39); 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; } 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 { mcdCloseFlashLoan.transfer(msg.value); // 0x fee if (_closeData.wholeDebt) { _closeData.daiAmount = getAllDebt( VAT_ADDRESS, manager.urns(_closeData.cdpId), manager.urns(_closeData.cdpId), manager.ilks(_closeData.cdpId) ); (_closeData.collAmount, ) = getCdpInfo(manager, _closeData.cdpId, manager.ilks(_closeData.cdpId)); } manager.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.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 _cdpId Id of the CDP /// @param _ilk Ilk of the CDP function getMaxDebt(uint256 _cdpId, bytes32 _ilk) public view returns (uint256) { uint256 price = getPrice(_ilk); (, uint256 mat) = spotter.ilks(_ilk); (uint256 collateral, uint256 debt) = getCdpInfo(manager, _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 "../../interfaces/ILoanShifter.sol"; import "../../mcd/saver/MCDSaverProxy.sol"; import "../../mcd/create/MCDCreateProxyActions.sol"; contract McdShifter is MCDSaverProxy { using SafeERC20 for ERC20; 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(_cdpId, ilk, _loanAmount, owner); maxColl = _collateral > maxColl ? maxColl : _collateral; // withdraw collateral from cdp drawCollateral(_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(_cdpId, _joinAddr, collAmount); // draw debt drawDai(_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, uint256(-1)); 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; 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 "../../mcd/saver/MCDSaverProxy.sol"; import "../../utils/FlashLoanReceiverBase.sol"; import "../../exchangeV3/DFSExchangeCore.sol"; 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; } 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 ) = abi.decode(_params, (bytes,uint256,uint256,address,bool)); ExchangeData memory exchangeData = unpackExchangeData(exDataBytes); SaverData memory saverData = SaverData({ cdpId: cdpId, gasCost: gasCost, loanAmount: _amount, fee: _fee, joinAddr: joinAddr }); 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 user = getOwner(manager, _saverData.cdpId); // Draw users Dai uint maxDebt = getMaxDebt(_saverData.cdpId, manager.ilks(_saverData.cdpId)); uint daiDrawn = drawDai(_saverData.cdpId, manager.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(_saverData.cdpId, _saverData.joinAddr, swapedAmount); // Draw Dai to repay the flash loan drawDai(_saverData.cdpId, manager.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 user = getOwner(manager, _saverData.cdpId); bytes32 ilk = manager.ilks(_saverData.cdpId); // Draw collateral uint maxColl = getMaxCollateral(_saverData.cdpId, ilk, _saverData.joinAddr); uint collDrawn = drawCollateral(_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(_saverData.cdpId, ilk, paybackAmount, user); // Payback the debt paybackDebt(_saverData.cdpId, ilk, paybackAmount, user); // Draw collateral to repay the flash loan drawCollateral(_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(uint _cdpId, bytes32 _ilk, uint _paybackAmount, address _owner) internal returns (uint256) { uint debt = getAllDebt(address(vat), manager.urns(_cdpId), manager.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 "../auth/AdminAuth.sol"; import "../utils/FlashLoanReceiverBase.sol"; import "../interfaces/DSProxyInterface.sol"; import "../exchange/SaverExchangeCore.sol"; import "./ShifterRegistry.sol"; /// @title LoanShifterReceiver Recevies the Aave flash loan and calls actions through users DSProxy contract LoanShifterReceiver is SaverExchangeCore, FlashLoanReceiverBase, AdminAuth { address payable public constant WALLET_ADDR = 0x322d58b9E75a6918f7e7849AEe0fF09369977e08; address public constant DISCOUNT_ADDR = 0x1b14E8D511c9A4395425314f849bD737BAF8208F; ILendingPoolAddressesProvider public LENDING_POOL_ADDRESS_PROVIDER = ILendingPoolAddressesProvider(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8); address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; ShifterRegistry public constant shifterRegistry = ShifterRegistry(0x597C52281b31B9d949a9D8fEbA08F7A2530a965e); struct ParamData { bytes proxyData1; bytes proxyData2; address proxy; address debtAddr; uint8 protocol1; uint8 protocol2; uint8 swapType; } constructor() FlashLoanReceiverBase(LENDING_POOL_ADDRESS_PROVIDER) public {} 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); if (paramData.swapType == 1) { // COLL_SWAP exchangeData.srcAmount -= getFee(getBalance(exchangeData.srcAddr), exchangeData.srcAddr, paramData.proxy); (, uint amount) = _sell(exchangeData); sendTokenAndEthToProxy(payable(paramData.proxy), exchangeData.destAddr, amount); } else if (paramData.swapType == 2) { // DEBT_SWAP exchangeData.srcAmount -= getFee(exchangeData.srcAmount, exchangeData.srcAddr, paramData.proxy); exchangeData.destAmount = (_amount + _fee); _buy(exchangeData); // Send extra to DSProxy sendTokenAndEthToProxy(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(uint _amount, uint _fee, bytes memory _params) internal pure returns (ParamData memory paramData, ExchangeData memory exchangeData) { ( uint[8] memory numData, // collAmount, debtAmount, id1, id2, srcAmount, destAmount, minPrice, price0x address[8] memory addrData, // addrLoan1, addrLoan2, debtAddr1, debtAddr2, srcAddr, destAddr, exchangeAddr, wrapper uint8[3] memory enumData, // fromProtocol, toProtocol, swapType bytes memory callData, address proxy ) = abi.decode(_params, (uint256[8],address[8],uint8[3],bytes,address)); bytes memory proxyData1; bytes memory proxyData2; uint openDebtAmount = (_amount + _fee); if (enumData[0] == 0) { // MAKER FROM proxyData1 = abi.encodeWithSignature("close(uint256,address,uint256,uint256)", numData[2], addrData[0], _amount, numData[0]); } else if(enumData[0] == 1) { // COMPOUND FROM if (enumData[2] == 2) { // DEBT_SWAP proxyData1 = abi.encodeWithSignature("changeDebt(address,address,uint256,uint256)", addrData[2], addrData[3], _amount, numData[4]); } else { proxyData1 = abi.encodeWithSignature("close(address,address,uint256,uint256)", addrData[0], addrData[2], numData[0], numData[1]); } } if (enumData[1] == 0) { // MAKER TO proxyData2 = abi.encodeWithSignature("open(uint256,address,uint256)", numData[3], addrData[1], openDebtAmount); } else if(enumData[1] == 1) { // COMPOUND TO if (enumData[2] == 2) { // DEBT_SWAP proxyData2 = abi.encodeWithSignature("repayAll(address)", addrData[3]); } else { proxyData2 = abi.encodeWithSignature("open(address,address,uint256)", addrData[1], addrData[3], openDebtAmount); } } paramData = ParamData({ proxyData1: proxyData1, proxyData2: proxyData2, proxy: proxy, debtAddr: addrData[2], protocol1: enumData[0], protocol2: enumData[1], swapType: enumData[2] }); exchangeData = SaverExchangeCore.ExchangeData({ srcAddr: addrData[4], destAddr: addrData[5], srcAmount: numData[4], destAmount: numData[5], minPrice: numData[6], wrapper: addrData[7], exchangeAddr: addrData[6], callData: callData, price0x: numData[7] }); } function sendTokenAndEthToProxy(address payable _proxy, address _reserve, uint _amount) internal { if (_reserve != ETH_ADDRESS) { ERC20(_reserve).safeTransfer(_proxy, _amount); } _proxy.transfer(address(this).balance); } function sendTokenToProxy(address payable _proxy, address _reserve, uint _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"; } } function getFee(uint _amount, address _tokenAddr, address _proxy) internal returns (uint feeAmount) { uint fee = 400; DSProxyInterface proxy = DSProxyInterface(payable(_proxy)); address user = proxy.owner(); if (Discount(DISCOUNT_ADDR).isCustomFeeSet(user)) { fee = Discount(DISCOUNT_ADDR).getCustomServiceFee(user); } feeAmount = (fee == 0) ? 0 : (_amount / fee); // fee can't go over 20% of the whole amount if (feeAmount > (_amount / 5)) { feeAmount = _amount / 5; } if (_tokenAddr == ETH_ADDRESS) { WALLET_ADDR.transfer(feeAmount); } else { ERC20(_tokenAddr).safeTransfer(WALLET_ADDR, feeAmount); } } receive() external override(FlashLoanReceiverBase, SaverExchangeCore) payable {} }
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 "../../exchange/SaverExchangeCore.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 = 500000000000; // 500 gwei uint public REPAY_GAS_COST = 1500000; uint public BOOST_GAS_COST = 1500000; MCDMonitorProxyV2 public monitorProxyContract; ISubscriptionsV2 public subscriptionsContract; address public mcdSaverTakerAddress; address public constant BOT_REGISTRY_ADDRESS = 0x637726f8b08a7ABE3aE3aCaB01A80E2d8ddeF77B; address public constant PROXY_PERMISSION_ADDR = 0x5a4f877CA808Cca3cB7c2A194F80Ab8588FAE26B; address public constant NEW_MONITOR_PROXY_ADDR = 0x1816A86C4DA59395522a42b871bf11A4E96A1C7a; 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( SaverExchangeCore.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.encodeWithSignature( "repayWithLoan((address,address,uint256,uint256,uint256,address,address,bytes,uint256),uint256,uint256,address)", _exchangeData, _cdpId, gasCost, _joinAddr)); (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( SaverExchangeCore.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.encodeWithSignature( "boostWithLoan((address,address,uint256,uint256,uint256,address,address,bytes,uint256),uint256,uint256,address)", _exchangeData, _cdpId, gasCost, _joinAddr)); (bool isGoodRatio, uint ratioAfter) = ratioGoodAfter(Method.Boost, _cdpId, _nextPrice); require(isGoodRatio); returnEth(); logger.Log(address(this), owner, "AutomaticMCDBoost", abi.encode(ratioBefore, ratioAfter)); } /// @dev One time function used to remove and give new permission function monitorProxyUpdate(address[] memory _proxies) public onlyApproved burnGas(30) { for(uint i = 0; i < _proxies.length; ++i) { monitorProxyContract.callExecute( _proxies[i], PROXY_PERMISSION_ADDR, abi.encodeWithSignature( "givePermission(address)", NEW_MONITOR_PROXY_ADDR )); } } /******************* 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 < 500000000000); 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; 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); }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "./lib/Actions.sol"; import "./lib/Account.sol"; import "./lib/Types.sol"; abstract contract ISoloMargin { struct OperatorArg { address operator; bool trusted; } function operate( Account.Info[] memory accounts, Actions.ActionArgs[] memory actions ) public virtual; function getAccountBalances( Account.Info memory account ) public view virtual returns ( address[] memory, Types.Par[] memory, Types.Wei[] memory ); function setOperators( OperatorArg[] memory args ) public virtual; function getNumMarkets() public view virtual returns (uint256); function getMarketTokenAddress(uint256 marketId) public view virtual returns (address); }
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); } }
/* Copyright 2019 dYdX Trading Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import { Account } from "./Account.sol"; import { Types } from "./Types.sol"; /** * @title Actions * @author dYdX * * Library that defines and parses valid Actions */ library Actions { // ============ Constants ============ bytes32 constant FILE = "Actions"; // ============ Enums ============ enum ActionType { Deposit, // supply tokens Withdraw, // borrow tokens Transfer, // transfer balance between accounts Buy, // buy an amount of some token (externally) Sell, // sell an amount of some token (externally) 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 } // ============ Structs ============ /* * Arguments that are passed to Solo in an ordered list as part of a single operation. * Each ActionArgs has an actionType which specifies which action struct that this data will be * parsed into before being processed. */ struct ActionArgs { ActionType actionType; uint256 accountId; Types.AssetAmount amount; uint256 primaryMarketId; uint256 secondaryMarketId; address otherAddress; uint256 otherAccountId; bytes data; } // ============ Action Types ============ /* * Moves tokens from an address to Solo. Can either repay a borrow or provide additional supply. */ struct DepositArgs { Types.AssetAmount amount; Account.Info account; uint256 market; address from; } /* * Moves tokens from Solo to another address. Can either borrow tokens or reduce the amount * previously supplied. */ struct WithdrawArgs { Types.AssetAmount amount; Account.Info account; uint256 market; address to; } /* * Transfers balance between two accounts. The msg.sender must be an operator for both accounts. * The amount field applies to accountOne. * This action does not require any token movement since the trade is done internally to Solo. */ struct TransferArgs { Types.AssetAmount amount; Account.Info accountOne; Account.Info accountTwo; uint256 market; } /* * Acquires a certain amount of tokens by spending other tokens. Sends takerMarket tokens to the * specified exchangeWrapper contract and expects makerMarket tokens in return. The amount field * applies to the makerMarket. */ struct BuyArgs { Types.AssetAmount amount; Account.Info account; uint256 makerMarket; uint256 takerMarket; address exchangeWrapper; bytes orderData; } /* * Spends a certain amount of tokens to acquire other tokens. Sends takerMarket tokens to the * specified exchangeWrapper and expects makerMarket tokens in return. The amount field applies * to the takerMarket. */ struct SellArgs { Types.AssetAmount amount; Account.Info account; uint256 takerMarket; uint256 makerMarket; address exchangeWrapper; bytes orderData; } /* * Trades balances between two accounts using any external contract that implements the * AutoTrader interface. The AutoTrader contract must be an operator for the makerAccount (for * which it is trading on-behalf-of). The amount field applies to the makerAccount and the * inputMarket. This proposed change to the makerAccount is passed to the AutoTrader which will * quote a change for the makerAccount in the outputMarket (or will disallow the trade). * This action does not require any token movement since the trade is done internally to Solo. */ struct TradeArgs { Types.AssetAmount amount; Account.Info takerAccount; Account.Info makerAccount; uint256 inputMarket; uint256 outputMarket; address autoTrader; bytes tradeData; } /* * Each account must maintain a certain margin-ratio (specified globally). If the account falls * below this margin-ratio, it can be liquidated by any other account. This allows anyone else * (arbitrageurs) to repay any borrowed asset (owedMarket) of the liquidating account in * exchange for any collateral asset (heldMarket) of the liquidAccount. The ratio is determined * by the price ratio (given by the oracles) plus a spread (specified globally). Liquidating an * account also sets a flag on the account that the account is being liquidated. This allows * anyone to continue liquidating the account until there are no more borrows being taken by the * liquidating account. Liquidators do not have to liquidate the entire account all at once but * can liquidate as much as they choose. The liquidating flag allows liquidators to continue * liquidating the account even if it becomes collateralized through partial liquidation or * price movement. */ struct LiquidateArgs { Types.AssetAmount amount; Account.Info solidAccount; Account.Info liquidAccount; uint256 owedMarket; uint256 heldMarket; } /* * Similar to liquidate, but vaporAccounts are accounts that have only negative balances * remaining. The arbitrageur pays back the negative asset (owedMarket) of the vaporAccount in * exchange for a collateral asset (heldMarket) at a favorable spread. However, since the * liquidAccount has no collateral assets, the collateral must come from Solo's excess tokens. */ struct VaporizeArgs { Types.AssetAmount amount; Account.Info solidAccount; Account.Info vaporAccount; uint256 owedMarket; uint256 heldMarket; } /* * Passes arbitrary bytes of data to an external contract that implements the Callee interface. * Does not change any asset amounts. This function may be useful for setting certain variables * on layer-two contracts for certain accounts without having to make a separate Ethereum * transaction for doing so. Also, the second-layer contracts can ensure that the call is coming * from an operator of the particular account. */ struct CallArgs { Account.Info account; address callee; bytes data; } // ============ Helper Functions ============ function getMarketLayout( ActionType actionType ) internal pure returns (MarketLayout) { if ( actionType == Actions.ActionType.Deposit || actionType == Actions.ActionType.Withdraw || actionType == Actions.ActionType.Transfer ) { return MarketLayout.OneMarket; } else if (actionType == Actions.ActionType.Call) { return MarketLayout.ZeroMarkets; } return MarketLayout.TwoMarkets; } function getAccountLayout( ActionType actionType ) internal pure returns (AccountLayout) { if ( actionType == Actions.ActionType.Transfer || actionType == Actions.ActionType.Trade ) { return AccountLayout.TwoPrimary; } else if ( actionType == Actions.ActionType.Liquidate || actionType == Actions.ActionType.Vaporize ) { return AccountLayout.PrimaryAndSecondary; } return AccountLayout.OnePrimary; } // ============ Parsing Functions ============ function parseDepositArgs( Account.Info[] memory accounts, ActionArgs memory args ) internal pure returns (DepositArgs memory) { assert(args.actionType == ActionType.Deposit); return DepositArgs({ amount: args.amount, account: accounts[args.accountId], market: args.primaryMarketId, from: args.otherAddress }); } function parseWithdrawArgs( Account.Info[] memory accounts, ActionArgs memory args ) internal pure returns (WithdrawArgs memory) { assert(args.actionType == ActionType.Withdraw); return WithdrawArgs({ amount: args.amount, account: accounts[args.accountId], market: args.primaryMarketId, to: args.otherAddress }); } function parseTransferArgs( Account.Info[] memory accounts, ActionArgs memory args ) internal pure returns (TransferArgs memory) { assert(args.actionType == ActionType.Transfer); return TransferArgs({ amount: args.amount, accountOne: accounts[args.accountId], accountTwo: accounts[args.otherAccountId], market: args.primaryMarketId }); } function parseBuyArgs( Account.Info[] memory accounts, ActionArgs memory args ) internal pure returns (BuyArgs memory) { assert(args.actionType == ActionType.Buy); return BuyArgs({ amount: args.amount, account: accounts[args.accountId], makerMarket: args.primaryMarketId, takerMarket: args.secondaryMarketId, exchangeWrapper: args.otherAddress, orderData: args.data }); } function parseSellArgs( Account.Info[] memory accounts, ActionArgs memory args ) internal pure returns (SellArgs memory) { assert(args.actionType == ActionType.Sell); return SellArgs({ amount: args.amount, account: accounts[args.accountId], takerMarket: args.primaryMarketId, makerMarket: args.secondaryMarketId, exchangeWrapper: args.otherAddress, orderData: args.data }); } function parseTradeArgs( Account.Info[] memory accounts, ActionArgs memory args ) internal pure returns (TradeArgs memory) { assert(args.actionType == ActionType.Trade); return TradeArgs({ amount: args.amount, takerAccount: accounts[args.accountId], makerAccount: accounts[args.otherAccountId], inputMarket: args.primaryMarketId, outputMarket: args.secondaryMarketId, autoTrader: args.otherAddress, tradeData: args.data }); } function parseLiquidateArgs( Account.Info[] memory accounts, ActionArgs memory args ) internal pure returns (LiquidateArgs memory) { assert(args.actionType == ActionType.Liquidate); return LiquidateArgs({ amount: args.amount, solidAccount: accounts[args.accountId], liquidAccount: accounts[args.otherAccountId], owedMarket: args.primaryMarketId, heldMarket: args.secondaryMarketId }); } function parseVaporizeArgs( Account.Info[] memory accounts, ActionArgs memory args ) internal pure returns (VaporizeArgs memory) { assert(args.actionType == ActionType.Vaporize); return VaporizeArgs({ amount: args.amount, solidAccount: accounts[args.accountId], vaporAccount: accounts[args.otherAccountId], owedMarket: args.primaryMarketId, heldMarket: args.secondaryMarketId }); } function parseCallArgs( Account.Info[] memory accounts, ActionArgs memory args ) internal pure returns (CallArgs memory) { assert(args.actionType == ActionType.Call); return CallArgs({ account: accounts[args.accountId], callee: args.otherAddress, data: args.data }); } }
/* Copyright 2019 dYdX Trading Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import { Types } from "./Types.sol"; /** * @title Account * @author dYdX * * Library of structs and functions that represent an account */ library Account { // ============ Enums ============ /* * Most-recently-cached account status. * * Normal: Can only be liquidated if the account values are violating the global margin-ratio. * Liquid: Can be liquidated no matter the account values. * Can be vaporized if there are no more positive account values. * Vapor: Has only negative (or zeroed) account values. Can be vaporized. * */ enum Status { Normal, Liquid, Vapor } // ============ Structs ============ // Represents the unique key that specifies an account struct Info { address owner; // The address that owns the account uint256 number; // A nonce that allows a single address to control many accounts } // The complete storage for any account struct Storage { mapping (uint256 => Types.Par) balances; // Mapping from marketId to principal Status status; } // ============ Library Functions ============ function equals( Info memory a, Info memory b ) internal pure returns (bool) { return a.owner == b.owner && a.number == b.number; } }
/* Copyright 2019 dYdX Trading Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import { SafeMath } from "../../../utils/SafeMath.sol"; import { Math } from "./Math.sol"; /** * @title Types * @author dYdX * * Library for interacting with the basic structs used in Solo */ library Types { using Math for uint256; // ============ AssetAmount ============ 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; } // ============ Par (Principal Amount) ============ // Total borrow and supply values for a market struct TotalPar { uint128 borrow; uint128 supply; } // Individual principal amount for an account struct Par { bool sign; // true if positive uint128 value; } function zeroPar() internal pure returns (Par memory) { return Par({ sign: false, value: 0 }); } function sub( Par memory a, Par memory b ) internal pure returns (Par memory) { return add(a, negative(b)); } function add( Par memory a, Par memory b ) internal pure returns (Par memory) { Par memory result; if (a.sign == b.sign) { result.sign = a.sign; result.value = SafeMath.add(a.value, b.value).to128(); } else { if (a.value >= b.value) { result.sign = a.sign; result.value = SafeMath.sub(a.value, b.value).to128(); } else { result.sign = b.sign; result.value = SafeMath.sub(b.value, a.value).to128(); } } return result; } function equals( Par memory a, Par memory b ) internal pure returns (bool) { if (a.value == b.value) { if (a.value == 0) { return true; } return a.sign == b.sign; } return false; } function negative( Par memory a ) internal pure returns (Par memory) { return Par({ sign: !a.sign, value: a.value }); } function isNegative( Par memory a ) internal pure returns (bool) { return !a.sign && a.value > 0; } function isPositive( Par memory a ) internal pure returns (bool) { return a.sign && a.value > 0; } function isZero( Par memory a ) internal pure returns (bool) { return a.value == 0; } // ============ Wei (Token Amount) ============ // Individual token amount for an account struct Wei { bool sign; // true if positive uint256 value; } function zeroWei() internal pure returns (Wei memory) { return Wei({ sign: false, value: 0 }); } function sub( Wei memory a, Wei memory b ) internal pure returns (Wei memory) { return add(a, negative(b)); } function add( Wei memory a, Wei memory b ) internal pure returns (Wei memory) { Wei memory result; if (a.sign == b.sign) { result.sign = a.sign; result.value = SafeMath.add(a.value, b.value); } else { if (a.value >= b.value) { result.sign = a.sign; result.value = SafeMath.sub(a.value, b.value); } else { result.sign = b.sign; result.value = SafeMath.sub(b.value, a.value); } } return result; } function equals( Wei memory a, Wei memory b ) internal pure returns (bool) { if (a.value == b.value) { if (a.value == 0) { return true; } return a.sign == b.sign; } return false; } function negative( Wei memory a ) internal pure returns (Wei memory) { return Wei({ sign: !a.sign, value: a.value }); } function isNegative( Wei memory a ) internal pure returns (bool) { return !a.sign && a.value > 0; } function isPositive( Wei memory a ) internal pure returns (bool) { return a.sign && a.value > 0; } function isZero( Wei memory a ) internal pure returns (bool) { return a.value == 0; } }
/* Copyright 2019 dYdX Trading Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import { SafeMath } from "../../../utils/SafeMath.sol"; import { Require } from "./Require.sol"; /** * @title Math * @author dYdX * * Library for non-standard Math functions */ library Math { using SafeMath for uint256; // ============ Constants ============ bytes32 constant FILE = "Math"; // ============ Library Functions ============ /* * Return target * (numerator / denominator). */ function getPartial( uint256 target, uint256 numerator, uint256 denominator ) internal pure returns (uint256) { return target.mul(numerator).div(denominator); } /* * Return target * (numerator / denominator), but rounded up. */ function getPartialRoundUp( uint256 target, uint256 numerator, uint256 denominator ) internal pure returns (uint256) { if (target == 0 || numerator == 0) { // SafeMath will check for zero denominator return SafeMath.div(0, denominator); } return target.mul(numerator).sub(1).div(denominator).add(1); } function to128( uint256 number ) internal pure returns (uint128) { uint128 result = uint128(number); Require.that( result == number, FILE, "Unsafe cast to uint128" ); return result; } function to96( uint256 number ) internal pure returns (uint96) { uint96 result = uint96(number); Require.that( result == number, FILE, "Unsafe cast to uint96" ); return result; } function to32( uint256 number ) internal pure returns (uint32) { uint32 result = uint32(number); Require.that( result == number, FILE, "Unsafe cast to uint32" ); return result; } function min( uint256 a, uint256 b ) internal pure returns (uint256) { return a < b ? a : b; } function max( uint256 a, uint256 b ) internal pure returns (uint256) { return a > b ? a : b; } }
/* Copyright 2019 dYdX Trading Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; /** * @title Require * @author dYdX * * Stringifies parameters to pretty-print revert messages. Costs more gas than regular require() */ library Require { // ============ Constants ============ uint256 constant ASCII_ZERO = 48; // '0' uint256 constant ASCII_RELATIVE_ZERO = 87; // 'a' - 10 uint256 constant ASCII_LOWER_EX = 120; // 'x' bytes2 constant COLON = 0x3a20; // ': ' bytes2 constant COMMA = 0x2c20; // ', ' bytes2 constant LPAREN = 0x203c; // ' <' byte constant RPAREN = 0x3e; // '>' uint256 constant FOUR_BIT_MASK = 0xf; // ============ Library Functions ============ function that( bool must, bytes32 file, bytes32 reason ) internal pure { if (!must) { revert( string( abi.encodePacked( stringifyTruncated(file), COLON, stringifyTruncated(reason) ) ) ); } } function that( bool must, bytes32 file, bytes32 reason, uint256 payloadA ) internal pure { if (!must) { revert( string( abi.encodePacked( stringifyTruncated(file), COLON, stringifyTruncated(reason), LPAREN, stringify(payloadA), RPAREN ) ) ); } } function that( bool must, bytes32 file, bytes32 reason, uint256 payloadA, uint256 payloadB ) internal pure { if (!must) { revert( string( abi.encodePacked( stringifyTruncated(file), COLON, stringifyTruncated(reason), LPAREN, stringify(payloadA), COMMA, stringify(payloadB), RPAREN ) ) ); } } function that( bool must, bytes32 file, bytes32 reason, address payloadA ) internal pure { if (!must) { revert( string( abi.encodePacked( stringifyTruncated(file), COLON, stringifyTruncated(reason), LPAREN, stringify(payloadA), RPAREN ) ) ); } } function that( bool must, bytes32 file, bytes32 reason, address payloadA, uint256 payloadB ) internal pure { if (!must) { revert( string( abi.encodePacked( stringifyTruncated(file), COLON, stringifyTruncated(reason), LPAREN, stringify(payloadA), COMMA, stringify(payloadB), RPAREN ) ) ); } } function that( bool must, bytes32 file, bytes32 reason, address payloadA, uint256 payloadB, uint256 payloadC ) internal pure { if (!must) { revert( string( abi.encodePacked( stringifyTruncated(file), COLON, stringifyTruncated(reason), LPAREN, stringify(payloadA), COMMA, stringify(payloadB), COMMA, stringify(payloadC), RPAREN ) ) ); } } function that( bool must, bytes32 file, bytes32 reason, bytes32 payloadA ) internal pure { if (!must) { revert( string( abi.encodePacked( stringifyTruncated(file), COLON, stringifyTruncated(reason), LPAREN, stringify(payloadA), RPAREN ) ) ); } } function that( bool must, bytes32 file, bytes32 reason, bytes32 payloadA, uint256 payloadB, uint256 payloadC ) internal pure { if (!must) { revert( string( abi.encodePacked( stringifyTruncated(file), COLON, stringifyTruncated(reason), LPAREN, stringify(payloadA), COMMA, stringify(payloadB), COMMA, stringify(payloadC), RPAREN ) ) ); } } // ============ Private Functions ============ function stringifyTruncated( bytes32 input ) private pure returns (bytes memory) { // put the input bytes into the result bytes memory result = abi.encodePacked(input); // determine the length of the input by finding the location of the last non-zero byte for (uint256 i = 32; i > 0; ) { // reverse-for-loops with unsigned integer /* solium-disable-next-line security/no-modify-for-iter-var */ i--; // find the last non-zero byte in order to determine the length if (result[i] != 0) { uint256 length = i + 1; /* solium-disable-next-line security/no-inline-assembly */ assembly { mstore(result, length) // r.length = length; } return result; } } // all bytes are zero return new bytes(0); } function stringify( uint256 input ) private pure returns (bytes memory) { if (input == 0) { return "0"; } // get the final string length uint256 j = input; uint256 length; while (j != 0) { length++; j /= 10; } // allocate the string bytes memory bstr = new bytes(length); // populate the string starting with the least-significant character j = input; for (uint256 i = length; i > 0; ) { // reverse-for-loops with unsigned integer /* solium-disable-next-line security/no-modify-for-iter-var */ i--; // take last decimal digit bstr[i] = byte(uint8(ASCII_ZERO + (j % 10))); // remove the last decimal digit j /= 10; } return bstr; } function stringify( address input ) private pure returns (bytes memory) { uint256 z = uint256(input); // addresses are "0x" followed by 20 bytes of data which take up 2 characters each bytes memory result = new bytes(42); // populate the result with "0x" result[0] = byte(uint8(ASCII_ZERO)); result[1] = byte(uint8(ASCII_LOWER_EX)); // for each byte (starting from the lowest byte), populate the result with two characters for (uint256 i = 0; i < 20; i++) { // each byte takes two characters uint256 shift = i * 2; // populate the least-significant character result[41 - shift] = char(z & FOUR_BIT_MASK); z = z >> 4; // populate the most-significant character result[40 - shift] = char(z & FOUR_BIT_MASK); z = z >> 4; } return result; } function stringify( bytes32 input ) private pure returns (bytes memory) { uint256 z = uint256(input); // bytes32 are "0x" followed by 32 bytes of data which take up 2 characters each bytes memory result = new bytes(66); // populate the result with "0x" result[0] = byte(uint8(ASCII_ZERO)); result[1] = byte(uint8(ASCII_LOWER_EX)); // for each byte (starting from the lowest byte), populate the result with two characters for (uint256 i = 0; i < 32; i++) { // each byte takes two characters uint256 shift = i * 2; // populate the least-significant character result[65 - shift] = char(z & FOUR_BIT_MASK); z = z >> 4; // populate the most-significant character result[64 - shift] = char(z & FOUR_BIT_MASK); z = z >> 4; } return result; } function char( uint256 input ) private pure returns (byte) { // return ASCII digit (0-9) if (input < 10) { return byte(uint8(input + ASCII_ZERO)); } // return ASCII letter (a-f) return byte(uint8(input + ASCII_RELATIVE_ZERO)); } }
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 "../../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; address payable public constant WALLET_ADDR = 0x322d58b9E75a6918f7e7849AEe0fF09369977e08; 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; } if (tokenAddr == ETH_ADDRESS) { WALLET_ADDR.transfer(feeAmount); } else { ERC20(tokenAddr).safeTransfer(WALLET_ADDR, 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; } if (tokenAddr == ETH_ADDRESS) { WALLET_ADDR.transfer(feeAmount); } else { ERC20(tokenAddr).safeTransfer(WALLET_ADDR, 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 "../../utils/SafeERC20.sol"; import "../../exchange/SaverExchangeCore.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 SaverExchangeCore, 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]; (,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, "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 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, "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 "../../interfaces/CEtherInterface.sol"; import "../../interfaces/CompoundOracleInterface.sol"; import "../../interfaces/CTokenInterface.sol"; import "../../interfaces/ComptrollerInterface.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; address payable public constant WALLET_ADDR = 0x322d58b9E75a6918f7e7849AEe0fF09369977e08; 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; } if (tokenAddr == ETH_ADDRESS) { WALLET_ADDR.transfer(feeAmount); } else { ERC20(tokenAddr).safeTransfer(WALLET_ADDR, 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; } if (tokenAddr == ETH_ADDRESS) { WALLET_ADDR.transfer(feeAmount); } else { ERC20(tokenAddr).safeTransfer(WALLET_ADDR, 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 } }
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 "../../exchange/SaverExchangeCore.sol"; import "../../loggers/DefisaverLogger.sol"; import "../helpers/CompoundSaverHelper.sol"; /// @title Contract that implements repay/boost functionality contract CompoundSaverProxy is CompoundSaverHelper, 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) { _exData.srcAmount = collAmount; (, 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, "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) { 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, "CompoundBoost", abi.encode(_exData.srcAmount, swapAmount, collToken, borrowToken)); } }
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 "./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 // 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 { feeAmount = _amount / fee; if (_token == KYBER_ETH_ADDRESS) { WALLET_ID.transfer(feeAmount); } else { ERC20(_token).safeTransfer(WALLET_ID, 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; 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; 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)); 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; 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; 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; 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/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; address payable public constant WALLET_ID = 0x322d58b9E75a6918f7e7849AEe0fF09369977e08; 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); 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, WALLET_ID ); 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); 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, WALLET_ID ); 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 "../../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; 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; 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 "../../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 = 0xff909120396247A7e3a7EB3823f2B5FbE583Bfbc; address public constant DEFISAVER_LOGGER = 0x5c55B921f590a89C1Ebe84dF170E655a82b62126; address public constant PROXY_REGISTRY_ADDRESS = 0x4678f0a6958e4D2Bc4F1BAF7Bc52E8F3564f3fE4; function repay(address _market, ExchangeData memory _data, uint _rateMode, uint256 _gasCost) public payable { _flashLoan(_market, _data, _rateMode,_gasCost, true); } function boost(address _market, ExchangeData memory _data, uint _rateMode, uint256 _gasCost) public payable { _flashLoan(_market, _data, _rateMode, _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(address _market, ExchangeData memory _data, uint _rateMode, 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, _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 "../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: "" }); } }
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 "../../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; 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; 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; } 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]) }); } } /// @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; 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 = 2000000; uint public BOOST_GAS_COST = 2000000; 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 "../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; 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 "../../utils/FlashLoanReceiverBase.sol"; import "../../interfaces/DSProxyInterface.sol"; import "../../exchange/SaverExchangeCore.sol"; /// @title Contract that receives the FL from Aave for Repays/Boost contract CompoundSaverFlashLoan is FlashLoanReceiverBase, SaverExchangeCore { 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(SaverExchangeCore, FlashLoanReceiverBase) payable {} }
pragma solidity ^0.6.0; pragma experimental ABIEncoderV2; import "../../DS/DSProxy.sol"; import "../../utils/FlashLoanReceiverBase.sol"; import "../../interfaces/DSProxyInterface.sol"; import "../../exchange/SaverExchangeCore.sol"; import "../../shifter/ShifterRegistry.sol"; /// @title Contract that receives the FL from Aave for Creating loans contract CompoundCreateReceiver is FlashLoanReceiverBase, SaverExchangeCore { ILendingPoolAddressesProvider public LENDING_POOL_ADDRESS_PROVIDER = ILendingPoolAddressesProvider(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8); ShifterRegistry public constant shifterRegistry = ShifterRegistry(0x2E82103bD91053C781aaF39da17aE58ceE39d0ab); address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address payable public constant WALLET_ADDR = 0x322d58b9E75a6918f7e7849AEe0fF09369977e08; address public constant DISCOUNT_ADDR = 0x1b14E8D511c9A4395425314f849bD737BAF8208F; // 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) { (, uint sellAmount) = _sell(exchangeData); getFee(sellAmount, exchangeData.destAddr, compCreate.proxyAddr); 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) { ( uint[4] memory numData, // srcAmount, destAmount, minPrice, price0x address[6] memory cAddresses, // cCollAddr, cDebtAddr, srcAddr, destAddr, exchangeAddr, wrapper bytes memory callData, address proxy ) = abi.decode(_params, (uint256[4],address[6],bytes,address)); bytes memory proxyData = abi.encodeWithSignature( "open(address,address,uint256)", cAddresses[0], cAddresses[1], (_amount + _fee)); exchangeData = SaverExchangeCore.ExchangeData({ srcAddr: cAddresses[2], destAddr: cAddresses[3], srcAmount: numData[0], destAmount: numData[1], minPrice: numData[2], wrapper: cAddresses[5], exchangeAddr: cAddresses[4], callData: callData, price0x: numData[3] }); compCreate = CompCreateData({ proxyAddr: payable(proxy), proxyData: proxyData, cCollAddr: cAddresses[0], cDebtAddr: cAddresses[1] }); 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); } } function getFee(uint _amount, address _tokenAddr, address _proxy) internal returns (uint feeAmount) { uint fee = 400; DSProxy proxy = DSProxy(payable(_proxy)); address user = proxy.owner(); if (Discount(DISCOUNT_ADDR).isCustomFeeSet(user)) { fee = Discount(DISCOUNT_ADDR).getCustomServiceFee(user); } feeAmount = (fee == 0) ? 0 : (_amount / fee); // fee can't go over 20% of the whole amount if (feeAmount > (_amount / 5)) { feeAmount = _amount / 5; } if (_tokenAddr == ETH_ADDRESS) { WALLET_ADDR.transfer(feeAmount); } else { ERC20(_tokenAddr).safeTransfer(WALLET_ADDR, feeAmount); } } // solhint-disable-next-line no-empty-blocks receive() external override(FlashLoanReceiverBase, SaverExchangeCore) payable {} }
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 payable public constant WALLET_ADDR = 0x322d58b9E75a6918f7e7849AEe0fF09369977e08; 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: msg.value}(); // 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 += 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 = add_(comp.compAccrued(_supplier), 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 = add_(comp.compAccrued(_borrower), borrowerDelta); } } }
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; 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 "../../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 "../../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 = 2500000; uint public BOOST_GAS_COST = 2500000; 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 ) 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)", AAVE_MARKET_ADDRESS, _exData, _rateMode, 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, "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 ) 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)", AAVE_MARKET_ADDRESS, _exData, _rateMode, 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, "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; 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; 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; import "../DS/DSMath.sol"; import "../DS/DSProxy.sol"; import "../utils/Discount.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; address payable public constant WALLET_ADDR = 0x322d58b9E75a6918f7e7849AEe0fF09369977e08; 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; } if (_tokenAddr == ETH_ADDR) { WALLET_ADDR.transfer(gasCost); } else { ERC20(_tokenAddr).safeTransfer(WALLET_ADDR, 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 "../../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 = 0x152e85c85CbD72D3949C42527E4073586E56CC1a; 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; 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 "../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 "../../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 "../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) { // 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 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; pragma experimental ABIEncoderV2; import "../AaveHelper.sol"; import "../../exchange/SaverExchangeCore.sol"; import "../../interfaces/IAToken.sol"; import "../../interfaces/ILendingPool.sol"; import "../../loggers/DefisaverLogger.sol"; import "../../utils/GasBurner.sol"; contract AaveSaverProxy is GasBurner, SaverExchangeCore, 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) { // swap (, destAmount) = _sell(_data); destAmount -= getFee(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.srcAmount -= getFee(_data.srcAmount, user, _gasCost, _data.srcAddr); // swap (, destAmount) = _sell(_data); } else { _data.srcAmount -= getGasCost(_data.srcAmount, user, _gasCost, _data.srcAddr); destAmount = _data.srcAmount; } 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; import "../DS/DSMath.sol"; import "../DS/DSProxy.sol"; import "../utils/Discount.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; address payable public constant WALLET_ADDR = 0x322d58b9E75a6918f7e7849AEe0fF09369977e08; 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; } if (_tokenAddr == ETH_ADDR) { WALLET_ADDR.transfer(feeAmount); } else { ERC20(_tokenAddr).safeTransfer(WALLET_ADDR, 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; } if (_tokenAddr == ETH_ADDR) { WALLET_ADDR.transfer(gasCost); } else { ERC20(_tokenAddr).safeTransfer(WALLET_ADDR, 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(); } }
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 "../../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; function callFunction( address sender, Account.Info memory account, bytes memory data ) public { ( address collateralToken, address borrowToken, uint256 ethAmount, address user, address proxy ) = abi.decode(data, (address,address,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(); 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 tokens from user to proxy ERC20(aCollateralToken).safeTransferFrom(user, proxy, 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; 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 = 2500000; uint public BOOST_GAS_COST = 2500000; 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; 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].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]), 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; 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 "../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 "../../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"; // take weth // send weth to AaveImport // approve AaveImport to manage proxy position // call flashloan // remove AaveImport // log /// @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 = 0xA60aA272650CA12A5B3a46e212aE86Ba689F9b4f; 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 AaveImport 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, msg.sender, 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; 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"; // take weth // send weth to AaveImport // approve AaveImport to manage proxy position // call flashloan // remove AaveImport // log /// @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 = 0x751961666D8605af1C5Ee549205f822F47b04168; 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 AaveImport 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, msg.sender, 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; 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 "../../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 "./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; 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 = 0xaf9f8781A4c39Ce2122019fC05F22e3a662B0A32; 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 COMPOUND_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(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)); } /// @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; pragma experimental ABIEncoderV2; import "../../interfaces/ILendingPool.sol"; import "../../loggers/DefisaverLogger.sol"; import "../../auth/ProxyPermission.sol"; import "../../exchange/SaverExchangeCore.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, SaverExchangeCore.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); // Pack the struct data (uint[4] memory numData, address[6] memory cAddresses, bytes memory callData) = _packData(_createInfo, _exchangeData); bytes memory paramsData = abi.encode(numData, cAddresses, callData, 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); } function _packData( CreateInfo memory _createInfo, SaverExchangeCore.ExchangeData memory exchangeData ) internal pure returns (uint[4] memory numData, address[6] memory cAddresses, bytes memory callData) { numData = [ exchangeData.srcAmount, exchangeData.destAmount, exchangeData.minPrice, exchangeData.price0x ]; cAddresses = [ _createInfo.cCollAddress, _createInfo.cBorrowAddress, exchangeData.srcAddr, exchangeData.destAddr, exchangeData.exchangeAddr, exchangeData.wrapper ]; callData = exchangeData.callData; } }
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; 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 = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address public constant AAVE_MONITOR_PROXY = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; /// @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 "../../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; function callFunction( address sender, Account.Info memory account, bytes memory data ) public { ( address market, address collateralToken, address borrowToken, uint256 ethAmount, address user, address proxy ) = abi.decode(data, (address,address,address,uint256,address,address)); // withdraw eth TokenInterface(WETH_ADDRESS).withdraw(ethAmount); address lendingPool = ILendingPoolAddressesProviderV2(market).getLendingPool(); 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 tokens from user to proxy ERC20(aToken).safeTransferFrom(user, proxy, 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 "./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; 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].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 borrowStablePos = 0; uint64 borrowVariablePos = 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[borrowStablePos] = reserve; data.borrowStableAmounts[borrowStablePos] = userBorrowBalanceEth; borrowStablePos++; } // Sum up debt in Eth if (borrowsVariable > 0) { uint256 userBorrowBalanceEth = wmul(borrowsVariable, price) * (10 ** (18 - _getDecimals(reserve))); data.borrowAddr[borrowVariablePos] = reserve; data.borrowVariableAmounts[borrowVariablePos] = userBorrowBalanceEth; borrowVariablePos++; } } 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; 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 "../../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; 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 { using SafeERC20 for ERC20; ILendingPoolAddressesProvider public LENDING_POOL_ADDRESS_PROVIDER = ILendingPoolAddressesProvider(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8); address public constant COMPOUND_BORROW_PROXY = 0xb7EDC39bE76107e2Cc645f0f6a3D164f5e173Ee2; 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, 0); ERC20(_reserve).safeApprove(cBorrowToken, uint(-1)); // repay compound 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(COMPOUND_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; import "../../utils/SafeERC20.sol"; import "../../interfaces/KyberNetworkProxyInterface.sol"; import "../../interfaces/ExchangeInterfaceV2.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; address payable public constant WALLET_ID = 0x322d58b9E75a6918f7e7849AEe0fF09369977e08; 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); 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, WALLET_ID ); 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); 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, WALLET_ID ); 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; pragma experimental ABIEncoderV2; import "../../utils/SafeERC20.sol"; import "../../DS/DSMath.sol"; import "../../auth/AdminAuth.sol"; import "../DFSExchangeHelper.sol"; import "../../interfaces/OffchainWrapperInterface.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"; 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 { ERC20(_exData.srcAddr).safeApprove(_exData.offchainData.allowanceTarget, wdiv(_exData.destAmount, _exData.offchainData.price)); } uint256 tokensBefore = getBalance(_exData.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(_exData.destAddr) - tokensBefore; } // returns all funds from src addr, dest addr and eth funds (protocol fee leftovers) sendLeftover(_exData.srcAddr, _exData.destAddr, msg.sender); return (success, tokensSwaped); } }
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"; 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"; 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 { writeUint256(_exData.offchainData.callData, 36, wdiv(_exData.destAmount, _exData.offchainData.price)); } uint256 tokensBefore = getBalance(_exData.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(_exData.destAddr) - tokensBefore; } // returns all funds from src addr, dest addr and eth funds (protocol fee leftovers) sendLeftover(_exData.srcAddr, _exData.destAddr, msg.sender); return (success, tokensSwaped); } }
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; 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); }
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 "./DSProxy.sol"; abstract contract DSProxyFactoryInterface { function build(address owner) public virtual returns (DSProxy proxy); }
{ "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":"DAI_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DAI_JOIN_ADDRESS","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":"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":"SPOTTER_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VAT_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WALLET_ID","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ZRX_ALLOWLIST_ADDR","outputs":[{"internalType":"address","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":[],"name":"daiJoin","outputs":[{"internalType":"contract DaiJoin","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":[{"internalType":"contract Manager","name":"_manager","type":"address"},{"internalType":"uint256","name":"_cdpId","type":"uint256"},{"internalType":"bytes32","name":"_ilk","type":"bytes32"}],"name":"getCdpInfo","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract Manager","name":"_manager","type":"address"},{"internalType":"uint256","name":"_cdpId","type":"uint256"}],"name":"getOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_ilk","type":"bytes32"}],"name":"getPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"kill","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"manager","outputs":[{"internalType":"contract Manager","name":"","type":"address"}],"stateMutability":"view","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":"spotter","outputs":[{"internalType":"contract Spotter","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":[],"name":"vat","outputs":[{"internalType":"contract Vat","name":"","type":"address"}],"stateMutability":"view","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
6080604052600380546001600160a01b0319167324a42fd28c976a61df5d00d0599c34c4f90748c817905534801561003657600080fd5b50600354600080546001600160a01b03199081166001600160a01b0390931692909217905560018054821633179055600280549091167325efa336886c74ea8e282ac466bdcd0199f85bb9179055613a7f806100936000396000f3fe6080604052600436106102085760003560e01c806381b9428011610118578063c50ebaf8116100a0578063cfac57c71161006f578063cfac57c7146104b1578063d3661fa514610505578063deca5f881461051a578063ee8725581461053a578063f851a4401461055a5761020f565b8063c50ebaf8146104c6578063c72c4d10146104db578063cc694d48146104f0578063cf786f8f1461032f5761020f565b8063a3b8e5d1116100e7578063a3b8e5d11461043a578063a46a66c914610467578063a7304bf71461047c578063ae08fd101461049c578063c11645bc146104b15761020f565b806381b94280146103e65780638c8a7958146103fb5780638da5cb5b1461041057806398ab3d49146104255761020f565b806331d98b3f1161019b57806341c0e1b51161016a57806341c0e1b514610392578063449b9ffa146103a7578063481c6a75146103bc57806350c86de5146103d15780636738929f146102b85761020f565b806331d98b3f1461030257806336569e771461032f5780633a128322146103445780634115fe6b146103645761020f565b80632a4c0a1a116101d75780632a4c0a1a146102a35780632e77468d146102b85780632f634a90146102cd578063314b6332146102ed5761020f565b806308d4f52a146102145780631e48907b1461024a578063278d58311461026c57806329f7fc9e146102815761020f565b3661020f57005b600080fd5b34801561022057600080fd5b5061023461022f366004613488565b61056f565b6040516102419190613801565b60405180910390f35b34801561025657600080fd5b5061026a610265366004613187565b610599565b005b34801561027857600080fd5b506102346105d2565b34801561028d57600080fd5b506102966105fa565b6040516102419190613781565b3480156102af57600080fd5b50610296610612565b3480156102c457600080fd5b5061029661062a565b3480156102d957600080fd5b506102966102e836600461320d565b610642565b3480156102f957600080fd5b5061029661073e565b34801561030e57600080fd5b5061032261031d366004613315565b610756565b60405161024191906137e1565b34801561033b57600080fd5b50610296610914565b34801561035057600080fd5b5061026a61035f36600461320d565b61092c565b34801561037057600080fd5b5061038461037f366004613378565b6109c6565b6040516102419291906139a8565b34801561039e57600080fd5b5061026a610be2565b3480156103b357600080fd5b50610234610c07565b3480156103c857600080fd5b50610296610c39565b3480156103dd57600080fd5b50610322610c51565b3480156103f257600080fd5b50610296610c57565b34801561040757600080fd5b50610296610c6f565b34801561041c57600080fd5b50610296610c87565b34801561043157600080fd5b50610296610c96565b34801561044657600080fd5b5061045a610455366004613345565b610ca5565b6040516102419190613934565b34801561047357600080fd5b50610296610cc1565b34801561048857600080fd5b5061026a610497366004613187565b610cd9565b3480156104a857600080fd5b50610234610d12565b3480156104bd57600080fd5b50610296610d43565b3480156104d257600080fd5b50610234610d5b565b3480156104e757600080fd5b50610296610d8a565b3480156104fc57600080fd5b50610234610d99565b34801561051157600080fd5b50610296610dc4565b34801561052657600080fd5b5061026a610535366004613187565b610ddc565b34801561054657600080fd5b5061026a610555366004613238565b610e09565b34801561056657600080fd5b50610296610f72565b6060816040516020016105829190613934565b60405160208183030381529060405290505b919050565b6002546001600160a01b031633146105b057600080fd5b600180546001600160a01b0319166001600160a01b0392909216919091179055565b6040518060400160405280600c81526020016b14db1a5c1c1859d9481a1a5d60a21b81525081565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee81565b736b175474e89094c44da98b954eedeac495271d0f81565b7365c79fcb50ca1594b025960e539ed7a9a6d434a381565b600080836001600160a01b0316638161b120846040518263ffffffff1660e01b815260040161067191906137e1565b60206040518083038186803b15801561068957600080fd5b505afa15801561069d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106c191906131a3565b9050806001600160a01b0316638da5cb5b6040518163ffffffff1660e01b815260040160206040518083038186803b1580156106fc57600080fd5b505afa158015610710573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061073491906131a3565b9150505b92915050565b7325dd3f51e0c3c3ff164ddc02a8e4d65bb9cbb12d81565b604051636cb1c69b60e11b815260009081907365c79fcb50ca1594b025960e539ed7a9a6d434a39063d9638d36906107929086906004016137e1565b604080518083038186803b1580156107a957600080fd5b505afa1580156107bd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107e191906133ac565b604051636cb1c69b60e11b8152909250600091507335d1b3f3d7966a1dfe207aa4514c12a259a0492b9063d9638d369061081f9087906004016137e1565b60a06040518083038186803b15801561083757600080fd5b505afa15801561084b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061086f91906135e6565b50509250505061090c610906827365c79fcb50ca1594b025960e539ed7a9a6d434a36001600160a01b031663495d32cb6040518163ffffffff1660e01b815260040160206040518083038186803b1580156108c957600080fd5b505afa1580156108dd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610901919061332d565b610f81565b83610f81565b949350505050565b7335d1b3f3d7966a1dfe207aa4514c12a259a0492b81565b6001546001600160a01b0316331461094357600080fd5b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b03831614156109a8576001546040516001600160a01b039091169082156108fc029083906000818181858888f193505050501580156109a2573d6000803e3d6000fd5b506109c2565b6001546109c2906001600160a01b03848116911683610fc2565b5050565b6000806000856001600160a01b03166336569e776040518163ffffffff1660e01b815260040160206040518083038186803b158015610a0457600080fd5b505afa158015610a18573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a3c91906131a3565b90506000866001600160a01b0316632726b073876040518263ffffffff1660e01b8152600401610a6c91906137e1565b60206040518083038186803b158015610a8457600080fd5b505afa158015610a98573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610abc91906131a3565b9050600080836001600160a01b0316632424be5c88856040518363ffffffff1660e01b8152600401610aef9291906137ea565b604080518083038186803b158015610b0657600080fd5b505afa158015610b1a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b3e91906135c3565b915091506000846001600160a01b031663d9638d36896040518263ffffffff1660e01b8152600401610b7091906137e1565b60a06040518083038186803b158015610b8857600080fd5b505afa158015610b9c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bc091906135e6565b50505091505082610bd18383610f81565b965096505050505050935093915050565b6001546001600160a01b03163314610bf957600080fd5b6001546001600160a01b0316ff5b6040518060400160405280601681526020017516995c9bde08195e18da185b99d9481a5b9d985b1a5960521b81525081565b735ef30b9986345249bc32d8928b7ee64de9435e3981565b61019081565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b734ba1f38427b33b8ab7bb0490200dae1f1c36823f81565b6001546001600160a01b031681565b6003546001600160a01b031681565b610cad612d2f565b818060200190518101906107389190613590565b731b14e8d511c9a4395425314f849bd737baf8208f81565b6002546001600160a01b03163314610cf057600080fd5b600280546001600160a01b0319166001600160a01b0392909216919091179055565b6040518060400160405280601581526020017413d99998da185a5b8819185d18481a5b9d985b1a59605a1b81525081565b739759a6ac90977b93b58547b4a71c78317f391a2881565b604051806040016040528060138152602001724465737420616d6f756e74206d697373696e6760681b81525081565b6000546001600160a01b031681565b6040518060400160405280600f81526020016e15dc985c1c195c881a5b9d985b1a59608a1b81525081565b73322d58b9e75a6918f7e7849aee0ff09369977e0881565b6001546001600160a01b03163314610df357600080fd5b6002546001600160a01b031615610cf057600080fd5b60006060610e19838501856131bf565b91509150610e25612dab565b610e2d612d2f565b82806020019051810190610e4191906133d9565b91509150610e4d612df5565b6040518061014001604052808460000151815260200184604001518152602001846060015181526020018460800151815260200184602001516001600160a01b03168152602001866001600160a01b031681526020018981526020018460c00151151581526020018b6001600160a01b031681526020018a815250905060008160a001516001600160a01b0316638da5cb5b6040518163ffffffff1660e01b815260040160206040518083038186803b158015610f0957600080fd5b505afa158015610f1d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f4191906131a3565b61019060a08501526001600160a01b03811660c08501529050610f6582848361101d565b5050505050505050505050565b6002546001600160a01b031681565b60006b033b2e3c9fd0803ce8000000610fb3610f9d85856111ca565b60026b033b2e3c9fd0803ce80000005b046111ee565b81610fba57fe5b049392505050565b6110188363a9059cbb60e01b8484604051602401610fe1929190613795565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b0319909316929092179091526111fe565b505050565b8251604051632c2cb9fd60e01b81526110b59190735ef30b9986345249bc32d8928b7ee64de9435e3990632c2cb9fd9061105b9084906004016137e1565b60206040518083038186803b15801561107357600080fd5b505afa158015611087573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110ab919061332d565b856040015161128d565b60006110ce846000015185608001518660200151611519565b905060008460e00151156110f557604084018290526110ec84611845565b91506111129050565b60c0850151604086015101606085015261110e84611b8c565b9150505b60006111218660800151611f3b565b90508560e00151156111445750736b175474e89094c44da98b954eedeac495271d0f5b856060015161115282611ff3565b10156111795760405162461bcd60e51b815260040161117090613814565b60405180910390fd5b6111a386610100015161119e8860c0015189610120015161209d90919063ffffffff16565b6120c9565b6111c281736b175474e89094c44da98b954eedeac495271d0f8661215d565b505050505050565b60008115806111e5575050808202828282816111e257fe5b04145b61073857600080fd5b8082018281101561073857600080fd5b6060611253826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166121f89092919063ffffffff16565b805190915015611018578080602001905181019061127191906132c9565b6110185760405162461bcd60e51b8152600401611170906138c4565b604051632726b07360e01b8152600090735ef30b9986345249bc32d8928b7ee64de9435e3990632726b073906112c79087906004016137e1565b60206040518083038186803b1580156112df57600080fd5b505afa1580156112f3573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061131791906131a3565b9050739759a6ac90977b93b58547b4a71c78317f391a286001600160a01b031663f4b9fa756040518163ffffffff1660e01b8152600401602060405180830381600087803b15801561136857600080fd5b505af115801561137c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113a091906131a3565b6001600160a01b031663095ea7b3739759a6ac90977b93b58547b4a71c78317f391a28846040518363ffffffff1660e01b81526004016113e1929190613795565b600060405180830381600087803b1580156113fb57600080fd5b505af115801561140f573d6000803e3d6000fd5b5050604051633b4da69f60e01b8152739759a6ac90977b93b58547b4a71c78317f391a289250633b4da69f915061144c9084908690600401613795565b600060405180830381600087803b15801561146657600080fd5b505af115801561147a573d6000803e3d6000fd5b50505050735ef30b9986345249bc32d8928b7ee64de9435e396001600160a01b03166345e6bdcd8560006114c37335d1b3f3d7966a1dfe207aa4514c12a259a0492b8689612207565b6040518463ffffffff1660e01b81526004016114e193929190613992565b600060405180830381600087803b1580156114fb57600080fd5b505af115801561150f573d6000803e3d6000fd5b5050505050505050565b6000735ef30b9986345249bc32d8928b7ee64de9435e396345e6bdcd8561153f856123cb565b60000360006040518463ffffffff1660e01b815260040161156293929190613992565b600060405180830381600087803b15801561157c57600080fd5b505af1158015611590573d6000803e3d6000fd5b50506040516313771f0760e31b8152735ef30b9986345249bc32d8928b7ee64de9435e399250639bb8f83891506115cf90879030908790600401613973565b600060405180830381600087803b1580156115e957600080fd5b505af11580156115fd573d6000803e3d6000fd5b505050506000829050836001600160a01b031663b3bcfa826040518163ffffffff1660e01b815260040160206040518083038186803b15801561163f57600080fd5b505afa158015611653573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611677919061332d565b60121461170157836001600160a01b031663b3bcfa826040518163ffffffff1660e01b815260040160206040518083038186803b1580156116b757600080fd5b505afa1580156116cb573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116ef919061332d565b601203600a0a83816116fd57fe5b0490505b60405163ef693bed60e01b81526001600160a01b0385169063ef693bed9061172f9030908590600401613795565b600060405180830381600087803b15801561174957600080fd5b505af115801561175d573d6000803e3d6000fd5b5050505061176a846123ed565b1561090c57836001600160a01b0316637bd2bea76040518163ffffffff1660e01b815260040160206040518083038186803b1580156117a857600080fd5b505afa1580156117bc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117e091906131a3565b6001600160a01b0316632e1a7d4d826040518263ffffffff1660e01b815260040161180b91906137e1565b600060405180830381600087803b15801561182557600080fd5b505af1158015611839573d6000803e3d6000fd5b50505050949350505050565b600080600080600073eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b031686600001516001600160a01b0316141561190057855161188a906124ca565b6001600160a01b031686526040808701518151630d0e30db60e41b8152915173c02aaa39b223fe8d0a0e5c4f27ead9083c756cc29263d0e30db09291600480830192600092919082900301818588803b1580156118e657600080fd5b505af11580156118fa573d6000803e3d6000fd5b50505050505b61191c86604001518760c0015188600001518960a00151612511565b6040870180519190910390526101208601516060015115611959576119428660006126f1565b925090508015611959578561012001516020015192505b80611973576119698660006128ef565b91508560e0015192505b600061199273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2611ff3565b1115611a78576040516370a0823160e01b815273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc290632e1a7d4d9082906370a08231906119d7903090600401613781565b602060405180830381600087803b1580156119f157600080fd5b505af1158015611a05573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a29919061332d565b6040518263ffffffff1660e01b8152600401611a4591906137e1565b600060405180830381600087803b158015611a5f57600080fd5b505af1158015611a73573d6000803e3d6000fd5b505050505b60208601516001600160a01b031673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc21415611b1b57611ab386608001518760400151612b31565b611ad073eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee611ff3565b10156040518060400160405280600c81526020016b14db1a5c1c1859d9481a1a5d60a21b81525090611b155760405162461bcd60e51b81526004016111709190613801565b50611b81565b611b2d86608001518760400151612b31565b611b3a8760200151611ff3565b10156040518060400160405280600c81526020016b14db1a5c1c1859d9481a1a5d60a21b81525090611b7f5760405162461bcd60e51b81526004016111709190613801565b505b509092509050915091565b6000806000806000856060015160001415604051806040016040528060138152602001724465737420616d6f756e74206d697373696e6760681b81525090611be75760405162461bcd60e51b81526004016111709190613801565b50611c0486604001518760c0015188600001518960a00151612511565b60408701805191909103905285516001600160a01b031673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1415611cb7578551611c41906124ca565b6001600160a01b031686526040808701518151630d0e30db60e41b8152915173c02aaa39b223fe8d0a0e5c4f27ead9083c756cc29263d0e30db09291600480830192600092919082900301818588803b158015611c9d57600080fd5b505af1158015611cb1573d6000803e3d6000fd5b50505050505b6101208601516060015115611ce857611cd18660016126f1565b925090508015611ce8578561012001516020015192505b80611d2d57611d13611d0c611d0588606001518960800151612b59565b60696111ca565b6064612b79565b6040870152611d238660016128ef565b91508560e0015192505b6000611d4c73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2611ff3565b1115611e32576040516370a0823160e01b815273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc290632e1a7d4d9082906370a0823190611d91903090600401613781565b602060405180830381600087803b158015611dab57600080fd5b505af1158015611dbf573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611de3919061332d565b6040518263ffffffff1660e01b8152600401611dff91906137e1565b600060405180830381600087803b158015611e1957600080fd5b505af1158015611e2d573d6000803e3d6000fd5b505050505b60208601516001600160a01b031673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc21415611ec8578560600151611e7d73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee611ff3565b10156040518060400160405280600c81526020016b14db1a5c1c1859d9481a1a5d60a21b81525090611ec25760405162461bcd60e51b81526004016111709190613801565b50611f21565b8560600151611eda8760200151611ff3565b10156040518060400160405280600c81526020016b14db1a5c1c1859d9481a1a5d60a21b81525090611f1f5760405162461bcd60e51b81526004016111709190613801565b505b82611f2f8760200151611ff3565b94509450505050915091565b600080826001600160a01b0316637bd2bea76040518163ffffffff1660e01b815260040160206040518083038186803b158015611f7757600080fd5b505afa158015611f8b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611faf91906131a3565b90506001600160a01b03811673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc214156107385773eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee915050610594565b60006001600160a01b03821673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1415612021575047610594565b6040516370a0823160e01b81526001600160a01b038316906370a082319061204d903090600401613781565b60206040518083038186803b15801561206557600080fd5b505afa158015612079573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610738919061332d565b6000828201838110156120c25760405162461bcd60e51b815260040161117090613856565b9392505050565b60008060009054906101000a90046001600160a01b03166001600160a01b031663ed6ff7606040518163ffffffff1660e01b815260040160206040518083038186803b15801561211857600080fd5b505afa15801561212c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061215091906131a3565b9050611018818484612b84565b471561219a576040516001600160a01b038216904780156108fc02916000818181858888f19350505050158015612198573d6000803e3d6000fd5b505b60006121a584611ff3565b11156121c9576121c9816121b885611ff3565b6001600160a01b0386169190610fc2565b60006121d483611ff3565b111561101857611018816121e784611ff3565b6001600160a01b0385169190610fc2565b606061090c8484600085612c1a565b600080846001600160a01b0316636c25b346856040518263ffffffff1660e01b81526004016122369190613781565b60206040518083038186803b15801561224e57600080fd5b505afa158015612262573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612286919061332d565b90506000856001600160a01b031663d9638d36856040518263ffffffff1660e01b81526004016122b691906137e1565b60a06040518083038186803b1580156122ce57600080fd5b505afa1580156122e2573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061230691906135e6565b5050509150506000866001600160a01b0316632424be5c86886040518363ffffffff1660e01b815260040161233c9291906137ea565b604080518083038186803b15801561235357600080fd5b505afa158015612367573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061238b91906135c3565b9150506123a082848161239a57fe5b046123cb565b9350808411156123bb576123b3816123cb565b6000036123c0565b836000035b979650505050505050565b8060008112156105945760405162461bcd60e51b81526004016111709061390e565b6000739759a6ac90977b93b58547b4a71c78317f391a286001600160a01b038316141561241c57506000610594565b816001600160a01b0316637bd2bea76040518163ffffffff1660e01b815260040160206040518083038186803b15801561245557600080fd5b505afa158015612469573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061248d91906131a3565b6001600160a01b031673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031614156124c257506001610594565b506000919050565b60006001600160a01b03821673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee146124f65781610738565b5073c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2919050565b600081158015906125a45750604051632cdc77ab60e21b8152731b14e8d511c9a4395425314f849bd737baf8208f9063b371deac90612554908790600401613781565b60206040518083038186803b15801561256c57600080fd5b505afa158015612580573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125a491906132c9565b1561263357604051636eeb543160e01b8152731b14e8d511c9a4395425314f849bd737baf8208f90636eeb5431906125e0908790600401613781565b60206040518083038186803b1580156125f857600080fd5b505afa15801561260c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612630919061332d565b91505b816126405750600061090c565b81858161264957fe5b049050600a850481111561265d5750600a84045b6001600160a01b03831673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee14156126c95760405173322d58b9e75a6918f7e7849aee0ff09369977e089082156108fc029083906000818181858888f193505050501580156126c3573d6000803e3d6000fd5b5061090c565b61090c6001600160a01b03841673322d58b9e75a6918f7e7849aee0ff09369977e0883610fc2565b610120820151602001516040516302f5cc7960e11b81526000918291734ba1f38427b33b8ab7bb0490200dae1f1c36823f916305eb98f2916127369190600401613781565b60206040518083038186803b15801561274e57600080fd5b505afa158015612762573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061278691906132c9565b612795575060009050806128e8565b6101208401515160405163e0aa279760e01b81527325dd3f51e0c3c3ff164ddc02a8e4d65bb9cbb12d9163e0aa2797916127d29190600401613781565b60206040518083038186803b1580156127ea57600080fd5b505afa1580156127fe573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061282291906132c9565b612831575060009050806128e8565b6101208401515160408501518551612854926001600160a01b0390911691610fc2565b610120840151805160809091015160405163097396a160e31b81526001600160a01b0390921691634b9cb50891906128929088908890600401613947565b60408051808303818588803b1580156128aa57600080fd5b505af11580156128be573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906128e391906132e9565b915091505b9250929050565b60e082015160405163e0aa279760e01b81526000917325dd3f51e0c3c3ff164ddc02a8e4d65bb9cbb12d9163e0aa27979161292c91600401613781565b60206040518083038186803b15801561294457600080fd5b505afa158015612958573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061297c91906132c9565b6040518060400160405280600f81526020016e15dc985c1c195c881a5b9d985b1a59608a1b815250906129c25760405162461bcd60e51b81526004016111709190613801565b5060e0830151604084015184516129e4926001600160a01b0390911691610fc2565b60008260018111156129f257fe5b1415612a98578260e001516001600160a01b0316635b6f36fc8460000151856020015186604001518761010001516040518563ffffffff1660e01b8152600401612a3f94939291906137ae565b602060405180830381600087803b158015612a5957600080fd5b505af1158015612a6d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a91919061332d565b9050610738565b8260e001516001600160a01b0316633924db668460000151856020015186606001518761010001516040518563ffffffff1660e01b8152600401612adf94939291906137ae565b602060405180830381600087803b158015612af957600080fd5b505af1158015612b0d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120c2919061332d565b6000670de0b6b3a7640000610fb3612b4985856111ca565b6002670de0b6b3a7640000610fad565b600081610fb3612b7185670de0b6b3a76400006111ca565b600285610fad565b6000818381610fba57fe5b612b8c612cde565b6001600160a01b0316826001600160a01b03161415612c0657826001600160a01b031681604051612bbc9061377e565b60006040518083038185875af1925050503d8060008114612bf9576040519150601f19603f3d011682016040523d82523d6000602084013e612bfe565b606091505b505050611018565b6110186001600160a01b0383168483610fc2565b6060612c2585612cf6565b612c415760405162461bcd60e51b81526004016111709061388d565b60006060866001600160a01b03168587604051612c5e9190613762565b60006040518083038185875af1925050503d8060008114612c9b576040519150601f19603f3d011682016040523d82523d6000602084013e612ca0565b606091505b50915091508115612cb457915061090c9050565b805115612cc45780518082602001fd5b8360405162461bcd60e51b81526004016111709190613801565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee90565b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47081811480159061090c575050151592915050565b60405180610140016040528060006001600160a01b0316815260200160006001600160a01b031681526020016000815260200160008152602001600081526020016000815260200160006001600160a01b0316815260200160006001600160a01b0316815260200160608152602001612da6612e65565b905290565b6040518060e001604052806000815260200160006001600160a01b031681526020016000815260200160008152602001600081526020016000151581526020016000151581525090565b6040518061014001604052806000815260200160008152602001600081526020016000815260200160006001600160a01b0316815260200160006001600160a01b031681526020016000815260200160001515815260200160006001600160a01b03168152602001600081525090565b6040518060c0016040528060006001600160a01b0316815260200160006001600160a01b0316815260200160006001600160a01b031681526020016000815260200160008152602001606081525090565b803561073881613a31565b805161073881613a31565b8051801515811461073857600080fd5b600082601f830112612eec578081fd5b8135612eff612efa826139dd565b6139b6565b9150808252836020828501011115612f1657600080fd5b8060208401602084013760009082016020015292915050565b600082601f830112612f3f578081fd5b8151612f4d612efa826139dd565b9150808252836020828501011115612f6457600080fd5b612f75816020840160208601613a01565b5092915050565b6000610140808385031215612f8f578182fd5b612f98816139b6565b915050612fa58383612ec1565b8152612fb48360208401612ec1565b602082015260408201516040820152606082015160608201526080820151608082015260a082015160a0820152612fee8360c08401612ec1565b60c08201526130008360e08401612ec1565b60e08201526101008083015167ffffffffffffffff8082111561302257600080fd5b61302e86838701612f2f565b8385015261012092508285015191508082111561304a57600080fd5b50613057858286016130fb565b82840152505092915050565b600060c08284031215613074578081fd5b61307e60c06139b6565b9050813561308b81613a31565b8152602082013561309b81613a31565b602082015260408201356130ae81613a31565b80604083015250606082013560608201526080820135608082015260a082013567ffffffffffffffff8111156130e357600080fd5b6130ef84828501612edc565b60a08301525092915050565b600060c0828403121561310c578081fd5b61311660c06139b6565b9050815161312381613a31565b8152602082015161313381613a31565b6020820152604082015161314681613a31565b80604083015250606082015160608201526080820151608082015260a082015167ffffffffffffffff81111561317b57600080fd5b6130ef84828501612f2f565b600060208284031215613198578081fd5b81356120c281613a31565b6000602082840312156131b4578081fd5b81516120c281613a31565b600080604083850312156131d1578081fd5b82356131dc81613a31565b9150602083013567ffffffffffffffff8111156131f7578182fd5b61320385828601612edc565b9150509250929050565b6000806040838503121561321f578182fd5b823561322a81613a31565b946020939093013593505050565b60008060008060006080868803121561324f578081fd5b853561325a81613a31565b94506020860135935060408601359250606086013567ffffffffffffffff80821115613284578283fd5b818801915088601f830112613297578283fd5b8135818111156132a5578384fd5b8960208285010111156132b6578384fd5b9699959850939650602001949392505050565b6000602082840312156132da578081fd5b815180151581146120c2578182fd5b600080604083850312156132fb578182fd5b6133058484612ecc565b9150602083015190509250929050565b600060208284031215613326578081fd5b5035919050565b60006020828403121561333e578081fd5b5051919050565b600060208284031215613356578081fd5b813567ffffffffffffffff81111561336c578182fd5b61073484828501612edc565b60008060006060848603121561338c578081fd5b833561339781613a31565b95602085013595506040909401359392505050565b600080604083850312156133be578182fd5b82516133c981613a31565b6020939093015192949293505050565b6000808284036101008112156133ed578283fd5b60e08112156133fa578283fd5b5061340560e06139b6565b83518152602084015161341781613a31565b806020830152506040840151604082015260608401516060820152608084015160808201526134498560a08601612ecc565b60a082015261345b8560c08601612ecc565b60c082015260e084015190925067ffffffffffffffff81111561347c578182fd5b61320385828601612f7c565b600060208284031215613499578081fd5b813567ffffffffffffffff808211156134b0578283fd5b81840191506101408083870312156134c6578384fd5b6134cf816139b6565b90506134db8684612eb6565b81526134ea8660208501612eb6565b602082015260408301356040820152606083013560608201526080830135608082015260a083013560a08201526135248660c08501612eb6565b60c08201526135368660e08501612eb6565b60e0820152610100808401358381111561354e578586fd5b61355a88828701612edc565b8284015250506101208084013583811115613573578586fd5b61357f88828701613063565b918301919091525095945050505050565b6000602082840312156135a1578081fd5b815167ffffffffffffffff8111156135b7578182fd5b61073484828501612f7c565b600080604083850312156135d5578182fd5b505080516020909101519092909150565b600080600080600060a086880312156135fd578283fd5b5050835160208501516040860151606087015160809097015192989197509594509092509050565b6001600160a01b03169052565b6000815180845261364a816020860160208601613a01565b601f01601f19169290920160200192915050565b600061014061366e848451613625565b60208301516136806020860182613625565b5060408301516040850152606083015160608501526080830151608085015260a083015160a085015260c08301516136bb60c0860182613625565b5060e08301516136ce60e0860182613625565b506101008084015182828701526136e783870182613632565b925050506101208084015185830382870152613703838261370d565b9695505050505050565b600060018060a01b0380835116845280602084015116602085015280604084015116604085015250606082015160608401526080820151608084015260a082015160c060a085015261073460c0850182613632565b60008251613774818460208701613a01565b9190910192915050565b90565b6001600160a01b0391909116815260200190565b6001600160a01b03929092168252602082015260400190565b6001600160a01b038581168252841660208201526040810183905260806060820181905260009061370390830184613632565b90815260200190565b9182526001600160a01b0316602082015260400190565b6000602082526120c26020830184613632565b60208082526022908201527f42656c6f77206d696e2e206e756d626572206f66206574682073706563696669604082015261195960f21b606082015260800190565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b6020808252601d908201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604082015260600190565b6020808252602a908201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6040820152691bdd081cdd58d8d9595960b21b606082015260800190565b6020808252600c908201526b696e742d6f766572666c6f7760a01b604082015260600190565b6000602082526120c2602083018461365e565b60006040825261395a604083018561365e565b90506002831061396657fe5b8260208301529392505050565b9283526001600160a01b03919091166020830152604082015260600190565b9283526020830191909152604082015260600190565b918252602082015260400190565b60405181810167ffffffffffffffff811182821017156139d557600080fd5b604052919050565b600067ffffffffffffffff8211156139f3578081fd5b50601f01601f191660200190565b60005b83811015613a1c578181015183820152602001613a04565b83811115613a2b576000848401525b50505050565b6001600160a01b0381168114613a4657600080fd5b5056fea2646970667358221220ffb74415982a5c0be4f613108a63c9911dd6693f1b74368ab13ec6a7c2966c2664736f6c634300060c0033
Deployed Bytecode
0x6080604052600436106102085760003560e01c806381b9428011610118578063c50ebaf8116100a0578063cfac57c71161006f578063cfac57c7146104b1578063d3661fa514610505578063deca5f881461051a578063ee8725581461053a578063f851a4401461055a5761020f565b8063c50ebaf8146104c6578063c72c4d10146104db578063cc694d48146104f0578063cf786f8f1461032f5761020f565b8063a3b8e5d1116100e7578063a3b8e5d11461043a578063a46a66c914610467578063a7304bf71461047c578063ae08fd101461049c578063c11645bc146104b15761020f565b806381b94280146103e65780638c8a7958146103fb5780638da5cb5b1461041057806398ab3d49146104255761020f565b806331d98b3f1161019b57806341c0e1b51161016a57806341c0e1b514610392578063449b9ffa146103a7578063481c6a75146103bc57806350c86de5146103d15780636738929f146102b85761020f565b806331d98b3f1461030257806336569e771461032f5780633a128322146103445780634115fe6b146103645761020f565b80632a4c0a1a116101d75780632a4c0a1a146102a35780632e77468d146102b85780632f634a90146102cd578063314b6332146102ed5761020f565b806308d4f52a146102145780631e48907b1461024a578063278d58311461026c57806329f7fc9e146102815761020f565b3661020f57005b600080fd5b34801561022057600080fd5b5061023461022f366004613488565b61056f565b6040516102419190613801565b60405180910390f35b34801561025657600080fd5b5061026a610265366004613187565b610599565b005b34801561027857600080fd5b506102346105d2565b34801561028d57600080fd5b506102966105fa565b6040516102419190613781565b3480156102af57600080fd5b50610296610612565b3480156102c457600080fd5b5061029661062a565b3480156102d957600080fd5b506102966102e836600461320d565b610642565b3480156102f957600080fd5b5061029661073e565b34801561030e57600080fd5b5061032261031d366004613315565b610756565b60405161024191906137e1565b34801561033b57600080fd5b50610296610914565b34801561035057600080fd5b5061026a61035f36600461320d565b61092c565b34801561037057600080fd5b5061038461037f366004613378565b6109c6565b6040516102419291906139a8565b34801561039e57600080fd5b5061026a610be2565b3480156103b357600080fd5b50610234610c07565b3480156103c857600080fd5b50610296610c39565b3480156103dd57600080fd5b50610322610c51565b3480156103f257600080fd5b50610296610c57565b34801561040757600080fd5b50610296610c6f565b34801561041c57600080fd5b50610296610c87565b34801561043157600080fd5b50610296610c96565b34801561044657600080fd5b5061045a610455366004613345565b610ca5565b6040516102419190613934565b34801561047357600080fd5b50610296610cc1565b34801561048857600080fd5b5061026a610497366004613187565b610cd9565b3480156104a857600080fd5b50610234610d12565b3480156104bd57600080fd5b50610296610d43565b3480156104d257600080fd5b50610234610d5b565b3480156104e757600080fd5b50610296610d8a565b3480156104fc57600080fd5b50610234610d99565b34801561051157600080fd5b50610296610dc4565b34801561052657600080fd5b5061026a610535366004613187565b610ddc565b34801561054657600080fd5b5061026a610555366004613238565b610e09565b34801561056657600080fd5b50610296610f72565b6060816040516020016105829190613934565b60405160208183030381529060405290505b919050565b6002546001600160a01b031633146105b057600080fd5b600180546001600160a01b0319166001600160a01b0392909216919091179055565b6040518060400160405280600c81526020016b14db1a5c1c1859d9481a1a5d60a21b81525081565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee81565b736b175474e89094c44da98b954eedeac495271d0f81565b7365c79fcb50ca1594b025960e539ed7a9a6d434a381565b600080836001600160a01b0316638161b120846040518263ffffffff1660e01b815260040161067191906137e1565b60206040518083038186803b15801561068957600080fd5b505afa15801561069d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106c191906131a3565b9050806001600160a01b0316638da5cb5b6040518163ffffffff1660e01b815260040160206040518083038186803b1580156106fc57600080fd5b505afa158015610710573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061073491906131a3565b9150505b92915050565b7325dd3f51e0c3c3ff164ddc02a8e4d65bb9cbb12d81565b604051636cb1c69b60e11b815260009081907365c79fcb50ca1594b025960e539ed7a9a6d434a39063d9638d36906107929086906004016137e1565b604080518083038186803b1580156107a957600080fd5b505afa1580156107bd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107e191906133ac565b604051636cb1c69b60e11b8152909250600091507335d1b3f3d7966a1dfe207aa4514c12a259a0492b9063d9638d369061081f9087906004016137e1565b60a06040518083038186803b15801561083757600080fd5b505afa15801561084b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061086f91906135e6565b50509250505061090c610906827365c79fcb50ca1594b025960e539ed7a9a6d434a36001600160a01b031663495d32cb6040518163ffffffff1660e01b815260040160206040518083038186803b1580156108c957600080fd5b505afa1580156108dd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610901919061332d565b610f81565b83610f81565b949350505050565b7335d1b3f3d7966a1dfe207aa4514c12a259a0492b81565b6001546001600160a01b0316331461094357600080fd5b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b03831614156109a8576001546040516001600160a01b039091169082156108fc029083906000818181858888f193505050501580156109a2573d6000803e3d6000fd5b506109c2565b6001546109c2906001600160a01b03848116911683610fc2565b5050565b6000806000856001600160a01b03166336569e776040518163ffffffff1660e01b815260040160206040518083038186803b158015610a0457600080fd5b505afa158015610a18573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a3c91906131a3565b90506000866001600160a01b0316632726b073876040518263ffffffff1660e01b8152600401610a6c91906137e1565b60206040518083038186803b158015610a8457600080fd5b505afa158015610a98573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610abc91906131a3565b9050600080836001600160a01b0316632424be5c88856040518363ffffffff1660e01b8152600401610aef9291906137ea565b604080518083038186803b158015610b0657600080fd5b505afa158015610b1a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b3e91906135c3565b915091506000846001600160a01b031663d9638d36896040518263ffffffff1660e01b8152600401610b7091906137e1565b60a06040518083038186803b158015610b8857600080fd5b505afa158015610b9c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bc091906135e6565b50505091505082610bd18383610f81565b965096505050505050935093915050565b6001546001600160a01b03163314610bf957600080fd5b6001546001600160a01b0316ff5b6040518060400160405280601681526020017516995c9bde08195e18da185b99d9481a5b9d985b1a5960521b81525081565b735ef30b9986345249bc32d8928b7ee64de9435e3981565b61019081565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b734ba1f38427b33b8ab7bb0490200dae1f1c36823f81565b6001546001600160a01b031681565b6003546001600160a01b031681565b610cad612d2f565b818060200190518101906107389190613590565b731b14e8d511c9a4395425314f849bd737baf8208f81565b6002546001600160a01b03163314610cf057600080fd5b600280546001600160a01b0319166001600160a01b0392909216919091179055565b6040518060400160405280601581526020017413d99998da185a5b8819185d18481a5b9d985b1a59605a1b81525081565b739759a6ac90977b93b58547b4a71c78317f391a2881565b604051806040016040528060138152602001724465737420616d6f756e74206d697373696e6760681b81525081565b6000546001600160a01b031681565b6040518060400160405280600f81526020016e15dc985c1c195c881a5b9d985b1a59608a1b81525081565b73322d58b9e75a6918f7e7849aee0ff09369977e0881565b6001546001600160a01b03163314610df357600080fd5b6002546001600160a01b031615610cf057600080fd5b60006060610e19838501856131bf565b91509150610e25612dab565b610e2d612d2f565b82806020019051810190610e4191906133d9565b91509150610e4d612df5565b6040518061014001604052808460000151815260200184604001518152602001846060015181526020018460800151815260200184602001516001600160a01b03168152602001866001600160a01b031681526020018981526020018460c00151151581526020018b6001600160a01b031681526020018a815250905060008160a001516001600160a01b0316638da5cb5b6040518163ffffffff1660e01b815260040160206040518083038186803b158015610f0957600080fd5b505afa158015610f1d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f4191906131a3565b61019060a08501526001600160a01b03811660c08501529050610f6582848361101d565b5050505050505050505050565b6002546001600160a01b031681565b60006b033b2e3c9fd0803ce8000000610fb3610f9d85856111ca565b60026b033b2e3c9fd0803ce80000005b046111ee565b81610fba57fe5b049392505050565b6110188363a9059cbb60e01b8484604051602401610fe1929190613795565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b0319909316929092179091526111fe565b505050565b8251604051632c2cb9fd60e01b81526110b59190735ef30b9986345249bc32d8928b7ee64de9435e3990632c2cb9fd9061105b9084906004016137e1565b60206040518083038186803b15801561107357600080fd5b505afa158015611087573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110ab919061332d565b856040015161128d565b60006110ce846000015185608001518660200151611519565b905060008460e00151156110f557604084018290526110ec84611845565b91506111129050565b60c0850151604086015101606085015261110e84611b8c565b9150505b60006111218660800151611f3b565b90508560e00151156111445750736b175474e89094c44da98b954eedeac495271d0f5b856060015161115282611ff3565b10156111795760405162461bcd60e51b815260040161117090613814565b60405180910390fd5b6111a386610100015161119e8860c0015189610120015161209d90919063ffffffff16565b6120c9565b6111c281736b175474e89094c44da98b954eedeac495271d0f8661215d565b505050505050565b60008115806111e5575050808202828282816111e257fe5b04145b61073857600080fd5b8082018281101561073857600080fd5b6060611253826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166121f89092919063ffffffff16565b805190915015611018578080602001905181019061127191906132c9565b6110185760405162461bcd60e51b8152600401611170906138c4565b604051632726b07360e01b8152600090735ef30b9986345249bc32d8928b7ee64de9435e3990632726b073906112c79087906004016137e1565b60206040518083038186803b1580156112df57600080fd5b505afa1580156112f3573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061131791906131a3565b9050739759a6ac90977b93b58547b4a71c78317f391a286001600160a01b031663f4b9fa756040518163ffffffff1660e01b8152600401602060405180830381600087803b15801561136857600080fd5b505af115801561137c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113a091906131a3565b6001600160a01b031663095ea7b3739759a6ac90977b93b58547b4a71c78317f391a28846040518363ffffffff1660e01b81526004016113e1929190613795565b600060405180830381600087803b1580156113fb57600080fd5b505af115801561140f573d6000803e3d6000fd5b5050604051633b4da69f60e01b8152739759a6ac90977b93b58547b4a71c78317f391a289250633b4da69f915061144c9084908690600401613795565b600060405180830381600087803b15801561146657600080fd5b505af115801561147a573d6000803e3d6000fd5b50505050735ef30b9986345249bc32d8928b7ee64de9435e396001600160a01b03166345e6bdcd8560006114c37335d1b3f3d7966a1dfe207aa4514c12a259a0492b8689612207565b6040518463ffffffff1660e01b81526004016114e193929190613992565b600060405180830381600087803b1580156114fb57600080fd5b505af115801561150f573d6000803e3d6000fd5b5050505050505050565b6000735ef30b9986345249bc32d8928b7ee64de9435e396345e6bdcd8561153f856123cb565b60000360006040518463ffffffff1660e01b815260040161156293929190613992565b600060405180830381600087803b15801561157c57600080fd5b505af1158015611590573d6000803e3d6000fd5b50506040516313771f0760e31b8152735ef30b9986345249bc32d8928b7ee64de9435e399250639bb8f83891506115cf90879030908790600401613973565b600060405180830381600087803b1580156115e957600080fd5b505af11580156115fd573d6000803e3d6000fd5b505050506000829050836001600160a01b031663b3bcfa826040518163ffffffff1660e01b815260040160206040518083038186803b15801561163f57600080fd5b505afa158015611653573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611677919061332d565b60121461170157836001600160a01b031663b3bcfa826040518163ffffffff1660e01b815260040160206040518083038186803b1580156116b757600080fd5b505afa1580156116cb573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116ef919061332d565b601203600a0a83816116fd57fe5b0490505b60405163ef693bed60e01b81526001600160a01b0385169063ef693bed9061172f9030908590600401613795565b600060405180830381600087803b15801561174957600080fd5b505af115801561175d573d6000803e3d6000fd5b5050505061176a846123ed565b1561090c57836001600160a01b0316637bd2bea76040518163ffffffff1660e01b815260040160206040518083038186803b1580156117a857600080fd5b505afa1580156117bc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117e091906131a3565b6001600160a01b0316632e1a7d4d826040518263ffffffff1660e01b815260040161180b91906137e1565b600060405180830381600087803b15801561182557600080fd5b505af1158015611839573d6000803e3d6000fd5b50505050949350505050565b600080600080600073eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b031686600001516001600160a01b0316141561190057855161188a906124ca565b6001600160a01b031686526040808701518151630d0e30db60e41b8152915173c02aaa39b223fe8d0a0e5c4f27ead9083c756cc29263d0e30db09291600480830192600092919082900301818588803b1580156118e657600080fd5b505af11580156118fa573d6000803e3d6000fd5b50505050505b61191c86604001518760c0015188600001518960a00151612511565b6040870180519190910390526101208601516060015115611959576119428660006126f1565b925090508015611959578561012001516020015192505b80611973576119698660006128ef565b91508560e0015192505b600061199273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2611ff3565b1115611a78576040516370a0823160e01b815273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc290632e1a7d4d9082906370a08231906119d7903090600401613781565b602060405180830381600087803b1580156119f157600080fd5b505af1158015611a05573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a29919061332d565b6040518263ffffffff1660e01b8152600401611a4591906137e1565b600060405180830381600087803b158015611a5f57600080fd5b505af1158015611a73573d6000803e3d6000fd5b505050505b60208601516001600160a01b031673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc21415611b1b57611ab386608001518760400151612b31565b611ad073eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee611ff3565b10156040518060400160405280600c81526020016b14db1a5c1c1859d9481a1a5d60a21b81525090611b155760405162461bcd60e51b81526004016111709190613801565b50611b81565b611b2d86608001518760400151612b31565b611b3a8760200151611ff3565b10156040518060400160405280600c81526020016b14db1a5c1c1859d9481a1a5d60a21b81525090611b7f5760405162461bcd60e51b81526004016111709190613801565b505b509092509050915091565b6000806000806000856060015160001415604051806040016040528060138152602001724465737420616d6f756e74206d697373696e6760681b81525090611be75760405162461bcd60e51b81526004016111709190613801565b50611c0486604001518760c0015188600001518960a00151612511565b60408701805191909103905285516001600160a01b031673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1415611cb7578551611c41906124ca565b6001600160a01b031686526040808701518151630d0e30db60e41b8152915173c02aaa39b223fe8d0a0e5c4f27ead9083c756cc29263d0e30db09291600480830192600092919082900301818588803b158015611c9d57600080fd5b505af1158015611cb1573d6000803e3d6000fd5b50505050505b6101208601516060015115611ce857611cd18660016126f1565b925090508015611ce8578561012001516020015192505b80611d2d57611d13611d0c611d0588606001518960800151612b59565b60696111ca565b6064612b79565b6040870152611d238660016128ef565b91508560e0015192505b6000611d4c73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2611ff3565b1115611e32576040516370a0823160e01b815273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc290632e1a7d4d9082906370a0823190611d91903090600401613781565b602060405180830381600087803b158015611dab57600080fd5b505af1158015611dbf573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611de3919061332d565b6040518263ffffffff1660e01b8152600401611dff91906137e1565b600060405180830381600087803b158015611e1957600080fd5b505af1158015611e2d573d6000803e3d6000fd5b505050505b60208601516001600160a01b031673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc21415611ec8578560600151611e7d73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee611ff3565b10156040518060400160405280600c81526020016b14db1a5c1c1859d9481a1a5d60a21b81525090611ec25760405162461bcd60e51b81526004016111709190613801565b50611f21565b8560600151611eda8760200151611ff3565b10156040518060400160405280600c81526020016b14db1a5c1c1859d9481a1a5d60a21b81525090611f1f5760405162461bcd60e51b81526004016111709190613801565b505b82611f2f8760200151611ff3565b94509450505050915091565b600080826001600160a01b0316637bd2bea76040518163ffffffff1660e01b815260040160206040518083038186803b158015611f7757600080fd5b505afa158015611f8b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611faf91906131a3565b90506001600160a01b03811673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc214156107385773eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee915050610594565b60006001600160a01b03821673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1415612021575047610594565b6040516370a0823160e01b81526001600160a01b038316906370a082319061204d903090600401613781565b60206040518083038186803b15801561206557600080fd5b505afa158015612079573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610738919061332d565b6000828201838110156120c25760405162461bcd60e51b815260040161117090613856565b9392505050565b60008060009054906101000a90046001600160a01b03166001600160a01b031663ed6ff7606040518163ffffffff1660e01b815260040160206040518083038186803b15801561211857600080fd5b505afa15801561212c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061215091906131a3565b9050611018818484612b84565b471561219a576040516001600160a01b038216904780156108fc02916000818181858888f19350505050158015612198573d6000803e3d6000fd5b505b60006121a584611ff3565b11156121c9576121c9816121b885611ff3565b6001600160a01b0386169190610fc2565b60006121d483611ff3565b111561101857611018816121e784611ff3565b6001600160a01b0385169190610fc2565b606061090c8484600085612c1a565b600080846001600160a01b0316636c25b346856040518263ffffffff1660e01b81526004016122369190613781565b60206040518083038186803b15801561224e57600080fd5b505afa158015612262573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612286919061332d565b90506000856001600160a01b031663d9638d36856040518263ffffffff1660e01b81526004016122b691906137e1565b60a06040518083038186803b1580156122ce57600080fd5b505afa1580156122e2573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061230691906135e6565b5050509150506000866001600160a01b0316632424be5c86886040518363ffffffff1660e01b815260040161233c9291906137ea565b604080518083038186803b15801561235357600080fd5b505afa158015612367573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061238b91906135c3565b9150506123a082848161239a57fe5b046123cb565b9350808411156123bb576123b3816123cb565b6000036123c0565b836000035b979650505050505050565b8060008112156105945760405162461bcd60e51b81526004016111709061390e565b6000739759a6ac90977b93b58547b4a71c78317f391a286001600160a01b038316141561241c57506000610594565b816001600160a01b0316637bd2bea76040518163ffffffff1660e01b815260040160206040518083038186803b15801561245557600080fd5b505afa158015612469573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061248d91906131a3565b6001600160a01b031673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031614156124c257506001610594565b506000919050565b60006001600160a01b03821673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee146124f65781610738565b5073c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2919050565b600081158015906125a45750604051632cdc77ab60e21b8152731b14e8d511c9a4395425314f849bd737baf8208f9063b371deac90612554908790600401613781565b60206040518083038186803b15801561256c57600080fd5b505afa158015612580573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125a491906132c9565b1561263357604051636eeb543160e01b8152731b14e8d511c9a4395425314f849bd737baf8208f90636eeb5431906125e0908790600401613781565b60206040518083038186803b1580156125f857600080fd5b505afa15801561260c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612630919061332d565b91505b816126405750600061090c565b81858161264957fe5b049050600a850481111561265d5750600a84045b6001600160a01b03831673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee14156126c95760405173322d58b9e75a6918f7e7849aee0ff09369977e089082156108fc029083906000818181858888f193505050501580156126c3573d6000803e3d6000fd5b5061090c565b61090c6001600160a01b03841673322d58b9e75a6918f7e7849aee0ff09369977e0883610fc2565b610120820151602001516040516302f5cc7960e11b81526000918291734ba1f38427b33b8ab7bb0490200dae1f1c36823f916305eb98f2916127369190600401613781565b60206040518083038186803b15801561274e57600080fd5b505afa158015612762573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061278691906132c9565b612795575060009050806128e8565b6101208401515160405163e0aa279760e01b81527325dd3f51e0c3c3ff164ddc02a8e4d65bb9cbb12d9163e0aa2797916127d29190600401613781565b60206040518083038186803b1580156127ea57600080fd5b505afa1580156127fe573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061282291906132c9565b612831575060009050806128e8565b6101208401515160408501518551612854926001600160a01b0390911691610fc2565b610120840151805160809091015160405163097396a160e31b81526001600160a01b0390921691634b9cb50891906128929088908890600401613947565b60408051808303818588803b1580156128aa57600080fd5b505af11580156128be573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906128e391906132e9565b915091505b9250929050565b60e082015160405163e0aa279760e01b81526000917325dd3f51e0c3c3ff164ddc02a8e4d65bb9cbb12d9163e0aa27979161292c91600401613781565b60206040518083038186803b15801561294457600080fd5b505afa158015612958573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061297c91906132c9565b6040518060400160405280600f81526020016e15dc985c1c195c881a5b9d985b1a59608a1b815250906129c25760405162461bcd60e51b81526004016111709190613801565b5060e0830151604084015184516129e4926001600160a01b0390911691610fc2565b60008260018111156129f257fe5b1415612a98578260e001516001600160a01b0316635b6f36fc8460000151856020015186604001518761010001516040518563ffffffff1660e01b8152600401612a3f94939291906137ae565b602060405180830381600087803b158015612a5957600080fd5b505af1158015612a6d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a91919061332d565b9050610738565b8260e001516001600160a01b0316633924db668460000151856020015186606001518761010001516040518563ffffffff1660e01b8152600401612adf94939291906137ae565b602060405180830381600087803b158015612af957600080fd5b505af1158015612b0d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120c2919061332d565b6000670de0b6b3a7640000610fb3612b4985856111ca565b6002670de0b6b3a7640000610fad565b600081610fb3612b7185670de0b6b3a76400006111ca565b600285610fad565b6000818381610fba57fe5b612b8c612cde565b6001600160a01b0316826001600160a01b03161415612c0657826001600160a01b031681604051612bbc9061377e565b60006040518083038185875af1925050503d8060008114612bf9576040519150601f19603f3d011682016040523d82523d6000602084013e612bfe565b606091505b505050611018565b6110186001600160a01b0383168483610fc2565b6060612c2585612cf6565b612c415760405162461bcd60e51b81526004016111709061388d565b60006060866001600160a01b03168587604051612c5e9190613762565b60006040518083038185875af1925050503d8060008114612c9b576040519150601f19603f3d011682016040523d82523d6000602084013e612ca0565b606091505b50915091508115612cb457915061090c9050565b805115612cc45780518082602001fd5b8360405162461bcd60e51b81526004016111709190613801565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee90565b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47081811480159061090c575050151592915050565b60405180610140016040528060006001600160a01b0316815260200160006001600160a01b031681526020016000815260200160008152602001600081526020016000815260200160006001600160a01b0316815260200160006001600160a01b0316815260200160608152602001612da6612e65565b905290565b6040518060e001604052806000815260200160006001600160a01b031681526020016000815260200160008152602001600081526020016000151581526020016000151581525090565b6040518061014001604052806000815260200160008152602001600081526020016000815260200160006001600160a01b0316815260200160006001600160a01b031681526020016000815260200160001515815260200160006001600160a01b03168152602001600081525090565b6040518060c0016040528060006001600160a01b0316815260200160006001600160a01b0316815260200160006001600160a01b031681526020016000815260200160008152602001606081525090565b803561073881613a31565b805161073881613a31565b8051801515811461073857600080fd5b600082601f830112612eec578081fd5b8135612eff612efa826139dd565b6139b6565b9150808252836020828501011115612f1657600080fd5b8060208401602084013760009082016020015292915050565b600082601f830112612f3f578081fd5b8151612f4d612efa826139dd565b9150808252836020828501011115612f6457600080fd5b612f75816020840160208601613a01565b5092915050565b6000610140808385031215612f8f578182fd5b612f98816139b6565b915050612fa58383612ec1565b8152612fb48360208401612ec1565b602082015260408201516040820152606082015160608201526080820151608082015260a082015160a0820152612fee8360c08401612ec1565b60c08201526130008360e08401612ec1565b60e08201526101008083015167ffffffffffffffff8082111561302257600080fd5b61302e86838701612f2f565b8385015261012092508285015191508082111561304a57600080fd5b50613057858286016130fb565b82840152505092915050565b600060c08284031215613074578081fd5b61307e60c06139b6565b9050813561308b81613a31565b8152602082013561309b81613a31565b602082015260408201356130ae81613a31565b80604083015250606082013560608201526080820135608082015260a082013567ffffffffffffffff8111156130e357600080fd5b6130ef84828501612edc565b60a08301525092915050565b600060c0828403121561310c578081fd5b61311660c06139b6565b9050815161312381613a31565b8152602082015161313381613a31565b6020820152604082015161314681613a31565b80604083015250606082015160608201526080820151608082015260a082015167ffffffffffffffff81111561317b57600080fd5b6130ef84828501612f2f565b600060208284031215613198578081fd5b81356120c281613a31565b6000602082840312156131b4578081fd5b81516120c281613a31565b600080604083850312156131d1578081fd5b82356131dc81613a31565b9150602083013567ffffffffffffffff8111156131f7578182fd5b61320385828601612edc565b9150509250929050565b6000806040838503121561321f578182fd5b823561322a81613a31565b946020939093013593505050565b60008060008060006080868803121561324f578081fd5b853561325a81613a31565b94506020860135935060408601359250606086013567ffffffffffffffff80821115613284578283fd5b818801915088601f830112613297578283fd5b8135818111156132a5578384fd5b8960208285010111156132b6578384fd5b9699959850939650602001949392505050565b6000602082840312156132da578081fd5b815180151581146120c2578182fd5b600080604083850312156132fb578182fd5b6133058484612ecc565b9150602083015190509250929050565b600060208284031215613326578081fd5b5035919050565b60006020828403121561333e578081fd5b5051919050565b600060208284031215613356578081fd5b813567ffffffffffffffff81111561336c578182fd5b61073484828501612edc565b60008060006060848603121561338c578081fd5b833561339781613a31565b95602085013595506040909401359392505050565b600080604083850312156133be578182fd5b82516133c981613a31565b6020939093015192949293505050565b6000808284036101008112156133ed578283fd5b60e08112156133fa578283fd5b5061340560e06139b6565b83518152602084015161341781613a31565b806020830152506040840151604082015260608401516060820152608084015160808201526134498560a08601612ecc565b60a082015261345b8560c08601612ecc565b60c082015260e084015190925067ffffffffffffffff81111561347c578182fd5b61320385828601612f7c565b600060208284031215613499578081fd5b813567ffffffffffffffff808211156134b0578283fd5b81840191506101408083870312156134c6578384fd5b6134cf816139b6565b90506134db8684612eb6565b81526134ea8660208501612eb6565b602082015260408301356040820152606083013560608201526080830135608082015260a083013560a08201526135248660c08501612eb6565b60c08201526135368660e08501612eb6565b60e0820152610100808401358381111561354e578586fd5b61355a88828701612edc565b8284015250506101208084013583811115613573578586fd5b61357f88828701613063565b918301919091525095945050505050565b6000602082840312156135a1578081fd5b815167ffffffffffffffff8111156135b7578182fd5b61073484828501612f7c565b600080604083850312156135d5578182fd5b505080516020909101519092909150565b600080600080600060a086880312156135fd578283fd5b5050835160208501516040860151606087015160809097015192989197509594509092509050565b6001600160a01b03169052565b6000815180845261364a816020860160208601613a01565b601f01601f19169290920160200192915050565b600061014061366e848451613625565b60208301516136806020860182613625565b5060408301516040850152606083015160608501526080830151608085015260a083015160a085015260c08301516136bb60c0860182613625565b5060e08301516136ce60e0860182613625565b506101008084015182828701526136e783870182613632565b925050506101208084015185830382870152613703838261370d565b9695505050505050565b600060018060a01b0380835116845280602084015116602085015280604084015116604085015250606082015160608401526080820151608084015260a082015160c060a085015261073460c0850182613632565b60008251613774818460208701613a01565b9190910192915050565b90565b6001600160a01b0391909116815260200190565b6001600160a01b03929092168252602082015260400190565b6001600160a01b038581168252841660208201526040810183905260806060820181905260009061370390830184613632565b90815260200190565b9182526001600160a01b0316602082015260400190565b6000602082526120c26020830184613632565b60208082526022908201527f42656c6f77206d696e2e206e756d626572206f66206574682073706563696669604082015261195960f21b606082015260800190565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b6020808252601d908201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604082015260600190565b6020808252602a908201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6040820152691bdd081cdd58d8d9595960b21b606082015260800190565b6020808252600c908201526b696e742d6f766572666c6f7760a01b604082015260600190565b6000602082526120c2602083018461365e565b60006040825261395a604083018561365e565b90506002831061396657fe5b8260208301529392505050565b9283526001600160a01b03919091166020830152604082015260600190565b9283526020830191909152604082015260600190565b918252602082015260400190565b60405181810167ffffffffffffffff811182821017156139d557600080fd5b604052919050565b600067ffffffffffffffff8211156139f3578081fd5b50601f01601f191660200190565b60005b83811015613a1c578181015183820152602001613a04565b83811115613a2b576000848401525b50505050565b6001600160a01b0381168114613a4657600080fd5b5056fea2646970667358221220ffb74415982a5c0be4f613108a63c9911dd6693f1b74368ab13ec6a7c2966c2664736f6c634300060c0033
Deployed Bytecode Sourcemap
314:5078:139:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;795:132:80;;;;;;;;;;-1:-1:-1;795:132:80;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;1122:118:33;;;;;;;;;;-1:-1:-1;1122:118:33;;;;;:::i;:::-;;:::i;:::-;;459:56:79;;;;;;;;;;;;;:::i;238:86:81:-;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;737:80:139:-;;;;;;;;;;;;;:::i;1247:58::-;;;;;;;;;;;;;:::i;4314:182:146:-;;;;;;;;;;-1:-1:-1;4314:182:146;;;;;:::i;:::-;;:::i;610:92:81:-;;;;;;;;;;;;;:::i;5087:218:139:-;;;;;;;;;;-1:-1:-1;5087:218:139;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;1311:42::-;;;;;;;;;;;;;:::i;1405:279:33:-;;;;;;;;;;-1:-1:-1;1405:279:33;;;;;:::i;:::-;;:::i;3818:349:146:-;;;;;;;;;;-1:-1:-1;3818:349:146;;;;;:::i;:::-;;:::i;:::-;;;;;;;;:::i;1283:78:33:-;;;;;;;;;;;;;:::i;665:72:79:-;;;;;;;;;;;;;:::i;1091:85:139:-;;;;;;;;;;;;;:::i;570:38::-;;;;;;;;;;;;;:::i;330:90:81:-;;;;;;;;;;;;;:::i;709:87::-;;;;;;;;;;;;;:::i;117:20:33:-;;;;;;;;;;;;;:::i;421:142:139:-;;;;;;;;;;;;;:::i;933:157:80:-;;;;;;;;;;-1:-1:-1;933:157:80;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;519:85:81:-;;;;;;;;;;;;;:::i;909:118:33:-;;;;;;;;;;-1:-1:-1;909:118:33;;;;;:::i;:::-;;:::i;125:74:81:-;;;;;;;;;;;;;:::i;1182:59:139:-;;;;;;;;;;;;;:::i;521:70:79:-;;;;;;;;;;;;;:::i;2246:54:173:-;;;;;;;;;;;;;:::i;597:62:79:-;;;;;;;;;;;;;:::i;427:86:81:-;;;;;;;;;;;;;:::i;635:156:33:-;;;;;;;;;;-1:-1:-1;635:156:33;;;;;:::i;:::-;;:::i;1702:1057:139:-;;;;;;;;;;-1:-1:-1;1702:1057:139;;;;;:::i;:::-;;:::i;143:20:33:-;;;;;;;;;;;;;:::i;795:132:80:-;870:12;912:7;901:19;;;;;;;;:::i;:::-;;;;;;;;;;;;;894:26;;795:132;;;;:::o;1122:118:33:-;1202:5;;-1:-1:-1;;;;;1202:5:33;1188:10;:19;1180:28;;;;;;1219:5;:14;;-1:-1:-1;;;;;;1219:14:33;-1:-1:-1;;;;;1219:14:33;;;;;;;;;;1122:118::o;459:56:79:-;;;;;;;;;;;;;;-1:-1:-1;;;459:56:79;;;;:::o;238:86:81:-;282:42;238:86;:::o;737:80:139:-;775:42;737:80;:::o;1247:58::-;956:42;1247:58;:::o;4314:182:146:-;4384:7;4403:13;4435:8;-1:-1:-1;;;;;4435:13:146;;4449:6;4435:21;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4403:55;;4476:5;-1:-1:-1;;;;;4476:11:146;;:13;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4469:20;;;4314:182;;;;;:::o;610:92:81:-;660:42;610:92;:::o;5087:218:139:-;5177:18;;-1:-1:-1;;;5177:18:139;;5140:7;;;;956:42;;5177:12;;:18;;5190:4;;5177:18;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;5230:14;;-1:-1:-1;;;5230:14:139;;5159:36;;-1:-1:-1;5210:12:139;;-1:-1:-1;1042:42:139;;5230:8;;:14;;5239:4;;5230:14;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;5205:39;;;;;;5262:36;5267:25;5272:4;956:42;-1:-1:-1;;;;;5278:11:139;;:13;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;5267:4;:25::i;:::-;5294:3;5262:4;:36::i;:::-;5255:43;5087:218;-1:-1:-1;;;;5087:218:139:o;1311:42::-;1042;1311;:::o;1405:279:33:-;209:5;;-1:-1:-1;;;;;209:5:33;218:10;209:19;201:28;;;;;;1504:42:::1;-1:-1:-1::0;;;;;1494:52:33;::::1;;1490:188;;;1570:5;::::0;1562:32:::1;::::0;-1:-1:-1;;;;;1570:5:33;;::::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:33;;::::1;::::0;1652:5:::1;1659:7:::0;1625:26:::1;:42::i;:::-;1405:279:::0;;:::o;3818:349:146:-;3904:4;3910;3926:11;3940:8;-1:-1:-1;;;;;3940:12:146;;:14;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;3926:28;;3964:11;3978:8;-1:-1:-1;;;;;3978:13:146;;3992:6;3978:21;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;3964:35;;4011:15;4028:9;4045:3;-1:-1:-1;;;;;4041:13:146;;4055:4;4061:3;4041:24;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4010:55;;;;4077:9;4097:3;-1:-1:-1;;;;;4093:13:146;;4107:4;4093:19;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4075:37;;;;;;4131:10;4143:16;4148:4;4154;4143;:16::i;:::-;4123:37;;;;;;;;;3818:349;;;;;;:::o;1283:78:33:-;209:5;;-1:-1:-1;;;;;209:5:33;218:10;209:19;201:28;;;;;;1347:5:::1;::::0;-1:-1:-1;;;;;1347:5:33::1;1326:28;665:72:79::0;;;;;;;;;;;;;;-1:-1:-1;;;665:72:79;;;;:::o;1091:85:139:-;1133:42;1091:85;:::o;570:38::-;605:3;570:38;:::o;330:90:81:-;378:42;330:90;:::o;709:87::-;754:42;709:87;:::o;117:20:33:-;;;-1:-1:-1;;;;;117:20:33;;:::o;421:142:139:-;;;-1:-1:-1;;;;;421:142:139;;:::o;933:157:80:-;1001:27;;:::i;:::-;1061:5;1050:33;;;;;;;;;;;;:::i;519:85:81:-;562:42;519:85;:::o;909:118:33:-;989:5;;-1:-1:-1;;;;;989:5:33;975:10;:19;967:28;;;;;;1006:5;:14;;-1:-1:-1;;;;;;1006:14:33;-1:-1:-1;;;;;1006:14:33;;;;;;;;;;909:118::o;125:74:81:-;;;;;;;;;;;;;;-1:-1:-1;;;125:74:81;;;;:::o;1182:59:139:-;866:42;1182:59;:::o;521:70:79:-;;;;;;;;;;;;;;-1:-1:-1;;;521:70:79;;;;:::o;2246:54:173:-;;;-1:-1:-1;;;;;2246:54:173;;:::o;597:62:79:-;;;;;;;;;;;;;;-1:-1:-1;;;597:62:79;;;;:::o;427:86:81:-;471:42;427:86;:::o;635:156:33:-;715:5;;-1:-1:-1;;;;;715:5:33;701:10;:19;693:28;;;;;;739:5;;-1:-1:-1;;;;;739:5:33;:19;731:28;;;;;1702:1057:139;1868:13;1883:23;1910:36;;;;1921:7;1910:36;:::i;:::-;1867:79;;;;1958:44;;:::i;:::-;2004:32;;:::i;:::-;2051:10;2040:62;;;;;;;;;;;;:::i;:::-;1957:145;;;;2113:26;;:::i;:::-;2142:408;;;;;;;;2173:13;:19;;;2142:408;;;;2218:13;:24;;;2142:408;;;;2267:13;:23;;;2142:408;;;;2317:13;:25;;;2142:408;;;;2366:13;:22;;;-1:-1:-1;;;;;2142:408:139;;;;;2409:5;-1:-1:-1;;;;;2142:408:139;;;;;2435:4;2142:408;;;;2460:13;:19;;;2142:408;;;;;;2502:8;-1:-1:-1;;;;;2142:408:139;;;;;2532:7;2142:408;;;2113:437;;2561:12;2592:9;:15;;;-1:-1:-1;;;;;2576:39:139;;:41;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;605:3;2628:26;;;:40;-1:-1:-1;;;;;2678:24:139;;:17;;;:24;2561:56;-1:-1:-1;2713:39:139;2722:9;2628:12;2561:56;2713:8;:39::i;:::-;1702:1057;;;;;;;;;;;:::o;143:20:33:-;;;-1:-1:-1;;;;;143:20:33;;:::o;1162:120:3:-;1221:9;1023:6;1246:23;1250:9;1254:1;1257;1250:3;:9::i;:::-;1267:1;1023:6;1261:7;;1246:3;:23::i;:::-;:29;;;;;;;1162:120;-1:-1:-1;;;1162:120:3:o;197:174:175:-;278:86;298:5;328:23;;;353:2;357:5;305:58;;;;;;;;;:::i;:::-;;;;-1:-1:-1;;305:58:175;;;;;;;;;;;;;;-1:-1:-1;;;;;305:58:175;-1:-1:-1;;;;;;305:58:175;;;;;;;;;;278:19;:86::i;:::-;197:174;;;:::o;2765:1133:139:-;2924:16;;2942:30;;-1:-1:-1;;;2942:30:139;;2912:83;;2924:16;1133:42;;2942:12;;:30;;2924:16;;2942:30;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2974:10;:20;;;2912:11;:83::i;:::-;3027:16;3046:79;3064:10;:16;;;3082:10;:19;;;3103:10;:21;;;3046:17;:79::i;:::-;3027:98;;3161:14;3194:10;:16;;;3190:282;;;3226:23;;;:37;;;3293:20;3226:13;3293:5;:20::i;:::-;3277:36;-1:-1:-1;3190:282:139;;-1:-1:-1;3190:282:139;;3395:16;;;;3372:20;;;;:39;3344:24;;;:68;3442:19;3344:13;3442:4;:19::i;:::-;3426:35;-1:-1:-1;;3190:282:139;3482:17;3502:37;3519:10;:19;;;3502:16;:37::i;:::-;3482:57;;3554:10;:16;;;3550:70;;;-1:-1:-1;775:42:139;3550:70;3663:10;:22;;;3638:21;3649:9;3638:10;:21::i;:::-;:47;;3630:94;;;;-1:-1:-1;;;3630:94:139;;;;;;;:::i;:::-;;;;;;;;;3735:92;3767:10;:18;;;3787:39;3809:10;:16;;;3787:10;:17;;;:21;;:39;;;;:::i;:::-;3735:31;:92::i;:::-;3838:52;3851:9;775:42;3883:5;3838:12;:52::i;:::-;2765:1133;;;;;;:::o;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;;;;;1639:412:175;1719:23;1745:69;1773:4;1745:69;;;;;;;;;;;;;;;;;1753:5;-1:-1:-1;;;;;1745:27:175;;;:69;;;;;:::i;:::-;1828:17;;1719:95;;-1:-1:-1;1828:21:175;1824:221;;1968:10;1957:30;;;;;;;;;;;;:::i;:::-;1949:85;;;;-1:-1:-1;;;1949:85:175;;;;;;;:::i;4497:308:139:-;4595:20;;-1:-1:-1;;;4595:20:139;;4581:11;;1133:42;;4595:12;;:20;;4608:6;;4595:20;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4581:34;;866:42;-1:-1:-1;;;;;4626:11:139;;:13;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;4626:21:139;;866:42;4666:10;4626:51;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4687:29:139;;-1:-1:-1;;;4687:29:139;;866:42;;-1:-1:-1;4687:12:139;;-1:-1:-1;4687:29:139;;4700:3;;4705:10;;4687:29;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1133:42;-1:-1:-1;;;;;4727:12:139;;4740:6;4748:1;4751:46;1042:42;4787:3;4792:4;4751:22;:46::i;:::-;4727:71;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4497:308;;;;:::o;3904:587::-;3995:4;1133:42;4011:12;4024:6;4033:22;4047:7;4033:13;:22::i;:::-;4032:23;;4057:1;4011:48;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4069:44:139;;-1:-1:-1;;;4069:44:139;;1133:42;;-1:-1:-1;4069:12:139;;-1:-1:-1;4069:44:139;;4082:6;;4098:4;;4105:7;;4069:44;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4124:15;4142:7;4124:25;;4169:9;-1:-1:-1;;;;;4164:19:139;;:21;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4189:2;4164:27;4160:117;;4248:9;-1:-1:-1;;;;;4243:19:139;;:21;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4238:2;:26;4231:2;:34;4220:7;:46;;;;;;4207:59;;4160:117;4287:47;;-1:-1:-1;;;4287:47:139;;-1:-1:-1;;;;;4287:20:139;;;;;:47;;4316:4;;4323:10;;4287:47;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4349:24;4363:9;4349:13;:24::i;:::-;4345:112;;;4394:9;-1:-1:-1;;;;;4389:19:139;;:21;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;4389:30:139;;4420:10;4389:42;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4474:10;3904:587;-1:-1:-1;;;;3904:587:139:o;1002:1627:79:-;1063:7;1072:4;1089:15;1114:17;1141:12;282:42:81;-1:-1:-1;;;;;1211:35:79;:6;:14;;;-1:-1:-1;;;;;1211:35:79;;1207:198;;;1293:14;;1279:29;;:13;:29::i;:::-;-1:-1:-1;;;;;1262:46:79;;;1375:16;;;;;1322:72;;-1:-1:-1;;;1322:72:79;;;;378:42:81;;1322:45:79;;1375:16;1322:72;;;;;1262:14;;1322:72;;;;;;;1375:16;378:42:81;1322:72:79;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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:79;-1:-1:-1;1707:88:79;;;;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;378:42:81;2070:10:79;:33::i;:::-;:37;2066:208;;;2187:62;;-1:-1:-1;;;2187:62:79;;378:42:81;;2123:46:79;;378:42:81;;2187:47:79;;:62;;2243:4;;2187:62;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2123:140;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2066:208;2288:15;;;;-1:-1:-1;;;;;2288:40:79;378:42:81;2288:40:79;2284:298;;;2385:39;2390:6;:15;;;2407:6;:16;;;2385:4;:39::i;:::-;2352:29;282:42:81;2352:10:79;:29::i;:::-;:72;;2426:16;;;;;;;;;;;;;-1:-1:-1;;;2426:16:79;;;2344:99;;;;;-1:-1:-1;;;2344:99:79;;;;;;;;:::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:79;;;2474:97;;;;;-1:-1:-1;;;2474:97:79;;;;;;;;:::i;:::-;;2284:298;-1:-1:-1;2600:7:79;;-1:-1:-1;2609:12:79;-1:-1:-1;1002:1627:79;;;:::o;2891:1845::-;2951:7;2960:4;2977:15;3002:17;3029:12;3060:6;:17;;;3081:1;3060:22;;3084:23;;;;;;;;;;;;;-1:-1:-1;;;3084:23:79;;;3052:56;;;;;-1:-1:-1;;;3052:56:79;;;;;;;;:::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:79;282:42:81;3272:35:79;3268:198;;;3354:14;;3340:29;;:13;:29::i;:::-;-1:-1:-1;;;;;3323:46:79;;;3436:16;;;;;3383:72;;-1:-1:-1;;;3383:72:79;;;;378:42:81;;3383:45:79;;3436:16;3383:72;;;;;3323:14;;3383:72;;;;;;;3436:16;378:42:81;3383:72:79;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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:79;-1:-1:-1;3599:88:79;;;;3640:6;:19;;;:32;;;3630:42;;3599:88;3764:7;3759:370;;3958:60;3962:50;3966:40;3971:6;:17;;;3990:6;:15;;;3966:4;:40::i;:::-;4008:3;3962;:50::i;:::-;4014:3;3958;:60::i;:::-;3939:16;;;:79;4047:33;3939:6;4065:14;4047:9;:33::i;:::-;4032:48;;4104:6;:14;;;4094:24;;3759:370;4242:1;4206:33;378:42:81;4206:10:79;:33::i;:::-;:37;4202:208;;;4323:62;;-1:-1:-1;;;4323:62:79;;378:42:81;;4259:46:79;;378:42:81;;4323:47:79;;:62;;4379:4;;4323:62;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4259:140;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4202:208;4424:15;;;;-1:-1:-1;;;;;4424:40:79;378:42:81;4424:40:79;4420:254;;;4521:6;:17;;;4488:29;282:42:81;4488:10:79;:29::i;:::-;:50;;4540:16;;;;;;;;;;;;;-1:-1:-1;;;4540:16:79;;;4480:77;;;;;-1:-1:-1;;;4480:77:79;;;;;;;;:::i;:::-;;4420:254;;;4627:6;:17;;;4596:27;4607:6;:15;;;4596:10;:27::i;:::-;:48;;4646:16;;;;;;;;;;;;;-1:-1:-1;;;4646:16:79;;;4588:75;;;;;-1:-1:-1;;;4588:75:79;;;;;;;;:::i;:::-;;4420:254;4692:7;4701:27;4712:6;:15;;;4701:10;:27::i;:::-;4684:45;;;;;;;2891:1845;;;:::o;4811:270:139:-;4879:7;4898:17;4931:9;-1:-1:-1;;;;;4926:19:139;;:21;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4898:50;-1:-1:-1;;;;;;4963:34:139;;378:42:81;4963:34:139;4959:89;;;282:42:81;5013:24:139;;;;;980:269:81;1043:12;-1:-1:-1;;;;;1071:31:81;;282:42;1071:31;1067:176;;;-1:-1:-1;1128:21:81;1067:176;;;1190:42;;-1:-1:-1;;;1190:42:81;;-1:-1:-1;;;;;1190:27:81;;;;;:42;;1226:4;;1190:42;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;48:176:176:-;106:7;137:5;;;160:6;;;;152:46;;;;-1:-1:-1;;;152:46:176;;;;;;;:::i;:::-;216:1;48:176;-1:-1:-1;;;48:176:176:o;2463:215:173:-;2559:20;2582:17;;;;;;;;;-1:-1:-1;;;;;2582:17:173;-1:-1:-1;;;;;2582:36:173;;:38;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2559:61;;2631:40;2648:4;2653:8;2663:7;2631:16;:40::i;1255:489:81:-;1408:21;:25;1404:91;;1449:35;;-1:-1:-1;;;;;1449:12:81;;;1462:21;1449:35;;;;;;;;;1462:21;1449:12;:35;;;;;;;;;;;;;;;;;;;;;1404:91;1532:1;1509:20;1520:8;1509:10;:20::i;:::-;:24;1505:110;;;1549:55;1578:3;1583:20;1594:8;1583:10;:20::i;:::-;-1:-1:-1;;;;;1549:28:81;;;:55;:28;:55::i;:::-;1653:1;1629:21;1640:9;1629:10;:21::i;:::-;:25;1625:113;;;1670:57;1700:3;1705:21;1716:9;1705:10;:21::i;:::-;-1:-1:-1;;;;;1670:29:81;;;:57;:29;:57::i;1237:194:167:-;1340:12;1371:53;1394:6;1402:4;1408:1;1411:12;1371:22;:53::i;1841:374:146:-;1938:10;1960:8;1975:4;-1:-1:-1;;;;;1971:13:146;;1985:4;1971:19;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;1960:30;;2004:9;2024:4;-1:-1:-1;;;;;2020:14:146;;2035:4;2020:20;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2001:39;;;;;;2053:8;2069:4;-1:-1:-1;;;;;2065:14:146;;2080:4;2086;2065:26;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2050:41;;;2111:25;2131:4;2125:3;:10;;;;;;2111:13;:25::i;:::-;2102:34;;2171:3;2160:6;2155:19;;:53;;2190:18;2204:3;2190:13;:18::i;:::-;2188:20;;2155:53;;;2179:6;2177:8;;2155:53;2146:62;1841:374;-1:-1:-1;;;;;;;1841:374:146:o;1525:131::-;1605:2;1580:5;1626:6;;;1618:31;;;;-1:-1:-1;;;1618:31:146;;;;;;;:::i;3225:426::-;3290:4;3387:42;-1:-1:-1;;;;;3374:55:146;;;3370:73;;;-1:-1:-1;3438:5:146;3431:12;;3370:73;3521:9;-1:-1:-1;;;;;3516:19:146;;:21;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;3508:76:146;3542:42;-1:-1:-1;;;;;3508:76:146;;3504:118;;;-1:-1:-1;3607:4:146;3600:11;;3504:118;-1:-1:-1;3639:5:146;3225:426;;;:::o;3618:149:81:-;3678:7;-1:-1:-1;;;;;3704:25:81;;282:42;3704:25;:56;;3756:4;3704:56;;;-1:-1:-1;378:42:81;;3618:149;-1:-1:-1;3618:149:81:o;2058:809::-;2164:17;2197:19;;;;;:71;;-1:-1:-1;2220:48:81;;-1:-1:-1;;;2220:48:81;;562:42;;2220:41;;:48;;2262:5;;2220:48;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2193:172;;;2301:53;;-1:-1:-1;;;2301:53:81;;562:42;;2301:46;;:53;;2348:5;;2301:53;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2284:70;;2193:172;2379:19;2375:486;;-1:-1:-1;2426:1:81;2375:486;;;2480:14;2470:7;:24;;;;;;;-1:-1:-1;2593:2:81;2583:7;:12;2570:9;:26;2566:89;;;-1:-1:-1;2638:2:81;2628:12;;2566:89;-1:-1:-1;;;;;2673:27:81;;282:42;2673:27;2669:182;;;2720:29;;471:42;;2720:29;;;;;2739:9;;2720:29;;;;2739:9;471:42;2720:29;;;;;;;;;;;;;;;;;;;;;2669:182;;;2788:48;-1:-1:-1;;;;;2788:26:81;;471:42;2826:9;2788:26;:48::i;4863:676:79:-;5049:20;;;;:33;;;5006:77;;-1:-1:-1;;;5006:77:79;;4968:12;;;;754:42:81;;5006::79;;:77;;5049:33;5006:77;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;5001:126;;-1:-1:-1;5107:5:79;;-1:-1:-1;5107:5:79;5099:17;;5001:126;5199:20;;;;:28;5142:86;;-1:-1:-1;;;5142:86:79;;660:42:81;;5142:56:79;;:86;;5199:28;5142:86;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;5137:135;;-1:-1:-1;5252:5:79;;-1:-1:-1;5252:5:79;5244:17;;5137:135;5345:20;;;;:28;5375:17;;;;5315:15;;5309:84;;-1:-1:-1;;;;;5309:35:79;;;;;:84::i;:::-;5436:20;;;;:28;;5483:32;;;;;5411:121;;-1:-1:-1;;;5411:121:79;;-1:-1:-1;;;;;5411:64:79;;;;;;5483:32;5411:121;;5436:7;;5526:5;;5411:121;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;5404:128;;;;4863:676;;;;;;:::o;5799:705::-;5977:15;;;;5920:73;;-1:-1:-1;;;5920:73:79;;5883:17;;660:42:81;;5920:56:79;;:73;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;5995:19;;;;;;;;;;;;;-1:-1:-1;;;5995:19:79;;;5912:103;;;;;-1:-1:-1;;;5912:103:79;;;;;;;;:::i;:::-;-1:-1:-1;6062:15:79;;;;6079:17;;;;6032:15;;6026:71;;-1:-1:-1;;;;;6026:35:79;;;;;:71::i;:::-;6121:15;6112:5;:24;;;;;;;;;6108:390;;;6187:7;:15;;;-1:-1:-1;;;;;6167:62:79;;6230:7;:15;;;6247:7;:16;;;6265:7;:17;;;6284:7;:19;;;6167:137;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;6152:152;;6108:390;;;6370:7;:15;;;-1:-1:-1;;;;;6350:61:79;;6412:7;:15;;;6429:7;:16;;;6447:7;:18;;;6467:7;:19;;;6350:137;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;1036:120:3:-;1095:9;988:6;1120:23;1124:9;1128:1;1131;1124:3;:9::i;:::-;1141:1;988:6;1135:7;;1288:118;1347:9;1398:1;1372:23;1376:11;1380:1;988:6;1376:3;:11::i;:::-;1393:1;1389;:5;;413:98;471:9;503:1;499;:5;;;;2684:345:173;2810:26;:24;:26::i;:::-;-1:-1:-1;;;;;2798:38:173;:8;-1:-1:-1;;;;;2798:38:173;;2795:164;;;2891:12;-1:-1:-1;;;;;2891:17:173;2916:7;2891:37;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2942:7;;2795:164;2969:51;-1:-1:-1;;;;;2969:28:173;;2998:12;3012:7;2969:28;:51::i;1986:958:167:-;2116:12;2148:18;2159:6;2148:10;:18::i;:::-;2140:60;;;;-1:-1:-1;;;2140:60:167;;;;;;;:::i;:::-;2271:12;2285:23;2312:6;-1:-1:-1;;;;;2312:11:167;2332:8;2343:4;2312:36;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2270:78;;;;2362:7;2358:580;;;2392:10;-1:-1:-1;2385:17:167;;-1:-1:-1;2385:17:167;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:167;;;;;;;;:::i;1990:119:173:-;2060:42;1990:119;:::o;47:610:167:-;107:4;568:20;;413:66;607:23;;;;;;:42;;-1:-1:-1;;634:15:167;;;47:610;-1:-1:-1;;47:610:167: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;593:128::-;668:13;;40589;;40582:21;44609:32;;44599:2;;44655:1;;44645:12;1365:440;;1466:3;1459:4;1451:6;1447:17;1443:27;1433:2;;-1:-1;;1474:12;1433:2;1521:6;1508:20;1543:64;1558:48;1599:6;1558:48;:::i;:::-;1543:64;:::i;:::-;1534:73;;1627:6;1620:5;1613:21;1731:3;1663:4;1722:6;1655;1713:16;;1710:25;1707:2;;;1748:1;;1738:12;1707:2;43721:6;1663:4;1655:6;1651:17;1663:4;1689:5;1685:16;43698:30;43777:1;43759:16;;;1663:4;43759:16;43752:27;1689:5;1426:379;-1:-1;;1426:379::o;1814:442::-;;1926:3;1919:4;1911:6;1907:17;1903:27;1893:2;;-1:-1;;1934:12;1893:2;1974:6;1968:13;1996:64;2011:48;2052:6;2011:48;:::i;1996:64::-;1987:73;;2080:6;2073:5;2066:21;2184:3;2116:4;2175:6;2108;2166:16;;2163:25;2160:2;;;2201:1;;2191:12;2160:2;2211:39;2243:6;2116:4;2142:5;2138:16;2116:4;2108:6;2104:17;2211:39;:::i;:::-;;1886:370;;;;:::o;6126:2007::-;;6257:6;;6245:9;6240:3;6236:19;6232:32;6229:2;;;-1:-1;;6267:12;6229:2;6295:22;6257:6;6295:22;:::i;:::-;6286:31;;;6402:60;6458:3;6434:22;6402:60;:::i;:::-;6384:16;6377:86;6561:60;6617:3;6528:2;6597:9;6593:22;6561:60;:::i;:::-;6528:2;6547:5;6543:16;6536:86;6688:2;6757:9;6753:22;10883:13;6688:2;6707:5;6703:16;6696:86;6849:2;6918:9;6914:22;10883:13;6849:2;6868:5;6864:16;6857:86;7008:3;7078:9;7074:22;10883:13;7008:3;7028:5;7024:16;7017:86;7173:3;7243:9;7239:22;10883:13;7173:3;7193:5;7189:16;7182:86;7363:60;7419:3;7329;7399:9;7395:22;7363:60;:::i;:::-;7329:3;7349:5;7345:16;7338:86;7522:60;7578:3;7488;7558:9;7554:22;7522:60;:::i;:::-;7488:3;7508:5;7504:16;7497:86;7672:3;;7661:9;7657:19;7651:26;7697:18;;7689:6;7686:30;7683:2;;;6370:1;;7719:12;7683:2;7766:69;7831:3;7822:6;7811:9;7807:22;7766:69;:::i;:::-;7672:3;7750:5;7746:18;7739:97;7926:3;;;;7915:9;7911:19;7905:26;7891:40;;7697:18;7943:6;7940:30;7937:2;;;6370:1;;7973:12;7937:2;;8020:91;8107:3;8098:6;8087:9;8083:22;8020:91;:::i;:::-;7926:3;8004:5;8000:18;7993:119;;;6223:1910;;;;:::o;8182:1180::-;;8302:4;8290:9;8285:3;8281:19;8277:30;8274:2;;;-1:-1;;8310:12;8274:2;8338:20;8302:4;8338:20;:::i;:::-;8329:29;;85:6;72:20;97:33;124:5;97:33;:::i;:::-;8418:75;;8562:2;8616:22;;72:20;97:33;72:20;97:33;:::i;:::-;8562:2;8577:16;;8570:75;8717:2;8771:22;;72:20;97:33;72:20;97:33;:::i;:::-;8750:49;8717:2;8736:5;8732:16;8725:75;;8862:2;8920:9;8916:22;10735:20;8862:2;8881:5;8877:16;8870:75;9013:3;9072:9;9068:22;10735:20;9013:3;9033:5;9029:16;9022:75;9190:3;9179:9;9175:19;9162:33;9215:18;9207:6;9204:30;9201:2;;;-1:-1;;9237:12;9201:2;9282:58;9336:3;9327:6;9316:9;9312:22;9282:58;:::i;:::-;9190:3;9268:5;9264:16;9257:84;;8268:1094;;;;:::o;9411:1250::-;;9542:4;9530:9;9525:3;9521:19;9517:30;9514:2;;;-1:-1;;9550:12;9514:2;9578:20;9542:4;9578:20;:::i;:::-;9569:29;;226:6;220:13;238:33;265:5;238:33;:::i;:::-;9658:86;;9813:2;9878:22;;220:13;238:33;220:13;238:33;:::i;:::-;9813:2;9828:16;;9821:86;9979:2;10044:22;;220:13;238:33;220:13;238:33;:::i;:::-;10012:60;9979:2;9998:5;9994:16;9987:86;;10135:2;10204:9;10200:22;10883:13;10135:2;10154:5;10150:16;10143:86;10297:3;10367:9;10363:22;10883:13;10297:3;10317:5;10313:16;10306:86;10478:3;10467:9;10463:19;10457:26;10503:18;10495:6;10492:30;10489:2;;;-1:-1;;10525:12;10489:2;10570:69;10635:3;10626:6;10615:9;10611:22;10570:69;:::i;10946:241::-;;11050:2;11038:9;11029:7;11025:23;11021:32;11018:2;;;-1:-1;;11056:12;11018:2;85:6;72:20;97:33;124:5;97:33;:::i;11194:263::-;;11309:2;11297:9;11288:7;11284:23;11280:32;11277:2;;;-1:-1;;11315:12;11277:2;226:6;220:13;238:33;265:5;238:33;:::i;11750:486::-;;;11888:2;11876:9;11867:7;11863:23;11859:32;11856:2;;;-1:-1;;11894:12;11856:2;371:6;358:20;383:41;418:5;383:41;:::i;:::-;11946:71;-1:-1;12082:2;12067:18;;12054:32;12106:18;12095:30;;12092:2;;;-1:-1;;12128:12;12092:2;12158:62;12212:7;12203:6;12192:9;12188:22;12158:62;:::i;:::-;12148:72;;;11850:386;;;;;:::o;12243:366::-;;;12364:2;12352:9;12343:7;12339:23;12335:32;12332:2;;;-1:-1;;12370:12;12332:2;85:6;72:20;97:33;124:5;97:33;:::i;:::-;12422:63;12522:2;12561:22;;;;10735:20;;-1:-1;;;12326:283::o;12616:741::-;;;;;;12790:3;12778:9;12769:7;12765:23;12761:33;12758:2;;;-1:-1;;12797:12;12758:2;85:6;72:20;97:33;124:5;97:33;:::i;:::-;12849:63;-1:-1;12949:2;12988:22;;10735:20;;-1:-1;13057:2;13096:22;;10735:20;;-1:-1;13193:2;13178:18;;13165:32;13217:18;13206:30;;;13203:2;;;-1:-1;;13239:12;13203:2;13324:6;13313:9;13309:22;;;1134:3;1127:4;1119:6;1115:17;1111:27;1101:2;;-1:-1;;1142:12;1101:2;1185:6;1172:20;13217:18;1204:6;1201:30;1198:2;;;-1:-1;;1234:12;1198:2;1329:3;12949:2;1309:17;1270:6;1295:32;;1292:41;1289:2;;;-1:-1;;1336:12;1289:2;12752:605;;;;-1:-1;12752:605;;-1:-1;12949:2;1266:17;;13259:82;12752:605;-1:-1;;;12752:605::o;13364:257::-;;13476:2;13464:9;13455:7;13451:23;13447:32;13444:2;;;-1:-1;;13482:12;13444:2;674:6;668:13;44634:5;40589:13;40582:21;44612:5;44609:32;44599:2;;-1:-1;;44645:12;13628:393;;;13757:2;13745:9;13736:7;13732:23;13728:32;13725:2;;;-1:-1;;13763:12;13725:2;13825:61;13878:7;13854:22;13825:61;:::i;:::-;13815:71;;13923:2;13977:9;13973:22;10883:13;13931:74;;13719:302;;;;;:::o;14028:241::-;;14132:2;14120:9;14111:7;14107:23;14103:32;14100:2;;;-1:-1;;14138:12;14100:2;-1:-1;795:20;;14094:175;-1:-1;14094:175::o;14276:263::-;;14391:2;14379:9;14370:7;14366:23;14362:32;14359:2;;;-1:-1;;14397:12;14359:2;-1:-1;943:13;;14353:186;-1:-1;14353:186::o;14546:345::-;;14659:2;14647:9;14638:7;14634:23;14630:32;14627:2;;;-1:-1;;14665:12;14627:2;14723:17;14710:31;14761:18;14753:6;14750:30;14747:2;;;-1:-1;;14783:12;14747:2;14813:62;14867:7;14858:6;14847:9;14843:22;14813:62;:::i;15601:525::-;;;;15756:2;15744:9;15735:7;15731:23;15727:32;15724:2;;;-1:-1;;15762:12;15724:2;2528:6;2515:20;2540:50;2584:5;2540:50;:::i;:::-;15814:80;15931:2;15970:22;;10735:20;;-1:-1;16039:2;16078:22;;;795:20;;15718:408;-1:-1;;;15718:408::o;16133:443::-;;;16287:2;16275:9;16266:7;16262:23;16258:32;16255:2;;;-1:-1;;16293:12;16255:2;2708:6;2702:13;2720:55;2769:5;2720:55;:::i;:::-;16478:2;16528:22;;;;10883:13;16345:96;;10883:13;;-1:-1;;;16249:327::o;16583:598::-;;;16762:9;16753:7;16749:23;16774:3;16749:23;16745:33;16742:2;;;-1:-1;;16781:12;16742:2;2952:4;2931:19;2927:30;2924:2;;;-1:-1;;2960:12;2924:2;;2988:20;2952:4;2988:20;:::i;:::-;10889:6;10883:13;3073:16;3066:86;3217:2;3286:9;3282:22;220:13;238:33;265:5;238:33;:::i;:::-;3250:60;3217:2;3236:5;3232:16;3225:86;;3378:2;3447:9;3443:22;10883:13;3378:2;3397:5;3393:16;3386:86;3538:2;3607:9;3603:22;10883:13;3538:2;3557:5;3553:16;3546:86;3700:3;3770:9;3766:22;10883:13;3700:3;3720:5;3716:16;3709:86;3895:57;3948:3;3861;3928:9;3924:22;3895:57;:::i;:::-;3861:3;3881:5;3877:16;3870:83;4049:57;4102:3;4015;4082:9;4078:22;4049:57;:::i;:::-;4015:3;4031:16;;4024:83;2952:4;16978:19;;16972:26;4035:5;;-1:-1;17018:18;17007:30;;17004:2;;;-1:-1;;17040:12;17004:2;17070:95;17157:7;17148:6;17137:9;17133:22;17070:95;:::i;17188:389::-;;17323:2;17311:9;17302:7;17298:23;17294:32;17291:2;;;-1:-1;;17329:12;17291:2;17387:17;17374:31;17425:18;;17417:6;17414:30;17411:2;;;-1:-1;;17447:12;17411:2;17544:6;17533:9;17529:22;;;4297:6;;4285:9;4280:3;4276:19;4272:32;4269:2;;;-1:-1;;4307:12;4269:2;4335:22;4297:6;4335:22;:::i;:::-;4326:31;;4442:49;4487:3;4463:22;4442:49;:::i;:::-;4424:16;4417:75;4590:49;4635:3;17323:2;4615:9;4611:22;4590:49;:::i;:::-;17323:2;4576:5;4572:16;4565:75;4706:2;4764:9;4760:22;10735:20;4706:2;4725:5;4721:16;4714:75;4856:2;4914:9;4910:22;10735:20;4856:2;4875:5;4871:16;4864:75;5004:3;5063:9;5059:22;10735:20;5004:3;5024:5;5020:16;5013:75;5158:3;5217:9;5213:22;10735:20;5158:3;5178:5;5174:16;5167:75;5337:49;5382:3;5303;5362:9;5358:22;5337:49;:::i;:::-;5303:3;5323:5;5319:16;5312:75;5485:49;5530:3;5451;5510:9;5506:22;5485:49;:::i;:::-;5451:3;5471:5;5467:16;5460:75;5631:3;;5620:9;5616:19;5603:33;17425:18;5648:6;5645:30;5642:2;;;-1:-1;;5678:12;5642:2;5725:58;5779:3;5770:6;5759:9;5755:22;5725:58;:::i;:::-;5631:3;5709:5;5705:18;5698:86;;;5881:3;;5870:9;5866:19;5853:33;17425:18;5898:6;5895:30;5892:2;;;-1:-1;;5928:12;5892:2;5975:80;6051:3;6042:6;6031:9;6027:22;5975:80;:::i;:::-;5955:18;;;5948:108;;;;-1:-1;5959:5;17285:292;-1:-1;;;;;17285:292::o;17584:404::-;;17730:2;17718:9;17709:7;17705:23;17701:32;17698:2;;;-1:-1;;17736:12;17698:2;17787:17;17781:24;17825:18;17817:6;17814:30;17811:2;;;-1:-1;;17847:12;17811:2;17877:95;17964:7;17955:6;17944:9;17940:22;17877:95;:::i;18265:399::-;;;18397:2;18385:9;18376:7;18372:23;18368:32;18365:2;;;-1:-1;;18403:12;18365:2;-1:-1;;10883:13;;18566:2;18616:22;;;10883:13;;;;;-1:-1;18359:305::o;18671:809::-;;;;;;18854:3;18842:9;18833:7;18829:23;18825:33;18822:2;;;-1:-1;;18861:12;18822:2;-1:-1;;10883:13;;19024:2;19074:22;;10883:13;19143:2;19193:22;;10883:13;19262:2;19312:22;;10883:13;19381:3;19432:22;;;10883:13;;;;;-1:-1;10883:13;;-1:-1;10883:13;;-1:-1;18816:664;-1:-1;18816:664::o;19636:137::-;-1:-1;;;;;41325:54;19723:45;;19717:56::o;20130:323::-;;20262:5;39492:12;39767:6;39762:3;39755:19;20345:52;20390:6;39804:4;39799:3;39795:14;39804:4;20371:5;20367:16;20345:52;:::i;:::-;44154:7;44138:14;-1:-1;;44134:28;20409:39;;;;39804:4;20409:39;;20210:243;-1:-1;;20210:243::o;24972:1990::-;;25135:6;25235:63;25283:14;25212:16;25206:23;25235:63;:::i;:::-;25381:4;25374:5;25370:16;25364:23;25393:63;25381:4;25445:3;25441:14;25427:12;25393:63;:::i;:::-;;25540:4;25533:5;25529:16;25523:23;25540:4;25604:3;25600:14;20081:37;25700:4;25693:5;25689:16;25683:23;25700:4;25764:3;25760:14;20081:37;25858:4;25851:5;25847:16;25841:23;25858:4;25922:3;25918:14;20081:37;26021:4;26014:5;26010:16;26004:23;26021:4;26085:3;26081:14;20081:37;26175:4;26168:5;26164:16;26158:23;26187:63;26175:4;26239:3;26235:14;26221:12;26187:63;:::i;:::-;;26332:4;26325:5;26321:16;26315:23;26344:63;26332:4;26396:3;26392:14;26378:12;26344:63;:::i;:::-;;26493:6;;26486:5;26482:18;26476:25;25135:6;26493;26525:3;26521:16;26514:40;26569:71;25135:6;25130:3;25126:16;26621:12;26569:71;:::i;:::-;26561:79;;;;26733:6;;26726:5;26722:18;26716:25;26789:3;26783:4;26779:14;26733:6;26765:3;26761:16;26754:40;26809:115;26919:4;26905:12;26809:115;:::i;:::-;26946:11;25108:1854;-1:-1;;;;;;25108:1854::o;27050:1221::-;;7697:18;;41336:42;;;;27278:16;27272:23;41325:54;19730:3;19723:45;41336:42;27451:4;27444:5;27440:16;27434:23;41325:54;27451:4;27515:3;27511:14;19723:45;41336:42;27616:4;27609:5;27605:16;27599:23;41325:54;27616:4;27680:3;27676:14;19723:45;;27771:4;27764:5;27760:16;27754:23;27771:4;27835:3;27831:14;20081:37;27932:4;27925:5;27921:16;27915:23;27932:4;27996:3;27992:14;20081:37;28090:4;28083:5;28079:16;28073:23;27203:4;28090;28120:3;28116:14;28109:38;28162:71;27203:4;27198:3;27194:14;28214:12;28162:71;:::i;28508:271::-;;20970:5;39492:12;21081:52;21126:6;21121:3;21114:4;21107:5;21103:16;21081:52;:::i;:::-;21145:16;;;;;28642:137;-1:-1;;28642:137::o;28786:379::-;29150:10;28974:191::o;29172:222::-;-1:-1;;;;;41325:54;;;;19723:45;;29299:2;29284:18;;29270:124::o;29907:349::-;-1:-1;;;;;41325:54;;;;19566:58;;30242:2;30227:18;;20081:37;30070:2;30055:18;;30041:215::o;30263:640::-;-1:-1;;;;;41325:54;;;19723:45;;41325:54;;30657:2;30642:18;;19723:45;30740:2;30725:18;;20081:37;;;30492:3;30777:2;30762:18;;30755:48;;;30263:640;;30817:76;;30477:19;;30879:6;30817:76;:::i;31250:222::-;20081:37;;;31377:2;31362:18;;31348:124::o;31479:333::-;20081:37;;;-1:-1;;;;;41325:54;31798:2;31783:18;;19723:45;31634:2;31619:18;;31605:207::o;31819:306::-;;31964:2;31985:17;31978:47;32039:76;31964:2;31953:9;31949:18;32101:6;32039:76;:::i;33800:416::-;34000:2;34014:47;;;23048:2;33985:18;;;39755:19;23084:34;39795:14;;;23064:55;-1:-1;;;23139:12;;;23132:26;23177:12;;;33971:245::o;34223:416::-;34423:2;34437:47;;;23428:2;34408:18;;;39755:19;23464:29;39795:14;;;23444:50;23513:12;;;34394:245::o;34646:416::-;34846:2;34860:47;;;24069:2;34831:18;;;39755:19;24105:31;39795:14;;;24085:52;24156:12;;;34817:245::o;35069:416::-;35269:2;35283:47;;;24407:2;35254:18;;;39755:19;24443:34;39795:14;;;24423:55;-1:-1;;;24498:12;;;24491:34;24544:12;;;35240:245::o;35492:416::-;35692:2;35706:47;;;24795:2;35677:18;;;39755:19;-1:-1;;;39795:14;;;24811:35;24865:12;;;35663:245::o;35915:394::-;;36104:2;36125:17;36118:47;36179:120;36104:2;36093:9;36089:18;36285:6;36179:120;:::i;36316:533::-;;36547:2;36568:17;36561:47;36622:120;36547:2;36536:9;36532:18;36728:6;36622:120;:::i;:::-;36614:128;;44260:1;44253:5;44250:12;44240:2;;44266:9;44240:2;43223:40;36835:2;36824:9;36820:18;22129:64;36518:331;;;;;:::o;37085:460::-;20081:37;;;-1:-1;;;;;41325:54;;;;37448:2;37433:18;;19566:58;37531:2;37516:18;;20081:37;37276:2;37261:18;;37247:298::o;37552:454::-;20081:37;;;37902:2;37887:18;;20081:37;;;;37992:2;37977:18;;22400:57;37740:2;37725:18;;37711:295::o;38474:333::-;20081:37;;;38793:2;38778:18;;20081:37;38629:2;38614:18;;38600:207::o;38814:256::-;38876:2;38870:9;38902:17;;;38977:18;38962:34;;38998:22;;;38959:62;38956:2;;;39034:1;;39024:12;38956:2;38876;39043:22;38854:216;;-1:-1;38854:216::o;39077:321::-;;39220:18;39212:6;39209:30;39206:2;;;-1:-1;;39242:12;39206:2;-1:-1;44154:7;39296:17;-1:-1;;39292:33;39383:4;39373:15;;39143:255::o;43794:268::-;43859:1;43866:101;43880:6;43877:1;43874:13;43866:101;;;43947:11;;;43941:18;43928:11;;;43921:39;43902:2;43895:10;43866:101;;;43982:6;43979:1;43976:13;43973:2;;;43859:1;44038:6;44033:3;44029:16;44022:27;43973:2;;43843:219;;;:::o;44289:117::-;-1:-1;;;;;41325:54;;44348:35;;44338:2;;44397:1;;44387:12;44338:2;44332:74;:::o
Swarm Source
ipfs://ffb74415982a5c0be4f613108a63c9911dd6693f1b74368ab13ec6a7c2966c26
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.