diff --git a/contracts/DForceLendingHandler.sol b/contracts/DForceLendingHandler.sol new file mode 100644 index 0000000..7f21c5e --- /dev/null +++ b/contracts/DForceLendingHandler.sol @@ -0,0 +1,252 @@ +pragma solidity 0.5.12; + +import "./Handler.sol"; +import "./interface/IDForceLending.sol"; +import "./library/ReentrancyGuard.sol"; + +contract DForceLendingHandler is Handler, ReentrancyGuard { + uint256 constant BASE = 10**18; + + struct InterestDetails { + uint256 totalUnderlyingBalance; // Total underlying balance including interest + uint256 interest; // Total interest + } + // Based on underlying token, get current interest details + mapping(address => InterestDetails) public interestDetails; + + IController public controller; + IiToken public iToken; + address public underlying; + + event RewardClaimed( + address indexed _underlying, + uint256 indexed compBalance + ); + + constructor(address _dTokenController, IiToken _iToken) public { + initialize(_dTokenController, _iToken); + } + + // --- Init --- + // This function is used with contract proxy, do not modify this function. + function initialize(address _dTokenController, IiToken _iToken) + public + { + super.initialize(_dTokenController); + require(_iToken.isiToken(), "Token is not a iToken"); + iToken = _iToken; + + controller = _iToken.controller(); + underlying = _iToken.underlying(); + initReentrancyStatus(); + approve(underlying, uint256(-1)); + // _enableToken(underlying); + } + + /** + * @dev Authorized function to approves market and dToken to transfer handler's underlying token. + */ + function approve(uint256 _amount) public auth { + + require( + doApprove(underlying, address(iToken), _amount), + "approve: Approve cToken failed!" + ); + + approve(underlying, _amount); + } + + /** + * @dev Deposit token to market, only called by dToken contract. + * @param _underlying Token to deposit. + * @return The actual deposited token amount. + */ + function deposit(address _underlying, uint256 _amount) + external + whenNotPaused + auth + nonReentrant + returns (uint256) + { + require( + underlying == _underlying, + "deposit: Token is disabled!" + ); + require( + _amount > 0, + "deposit: Deposit amount should be greater than 0!" + ); + + IiToken _iToken = iToken; + require(address(_iToken) != address(0), "deposit: Do not support token!"); + + uint256 _MarketBalanceBefore = _iToken.balanceOfUnderlying(address(this)); + + // Update the stored interest with the market balance before the mint + InterestDetails storage _details = interestDetails[_underlying]; + uint256 _interest = _MarketBalanceBefore.sub( + _details.totalUnderlyingBalance + ); + _details.interest = _details.interest.add(_interest); + + // Mint all the token balance of the handler, + // which should be the exact deposit amount normally, + // but there could be some unexpected transfers before. + _iToken.mint(address(this), IERC20(_underlying).balanceOf(address(this))); + + // including unexpected transfers. + uint256 _MarketBalanceAfter = IiToken(_iToken).balanceOfUnderlying( + address(this) + ); + + // Store the latest real balance. + _details.totalUnderlyingBalance = _MarketBalanceAfter; + + uint256 _changedAmount = _MarketBalanceAfter.sub(_MarketBalanceBefore); + + // Return the smaller value as unexpected transfers were also included. + return _changedAmount > _amount ? _amount : _changedAmount; + } + + /** + * @dev Withdraw token from market, but only for dToken contract. + * @param _underlying Token to withdraw. + * @param _amount Token amount to withdraw. + * @return The actual withdrown token amount. + */ + function withdraw(address _underlying, uint256 _amount) + external + whenNotPaused + auth + nonReentrant + returns (uint256) + { + require( + underlying == _underlying, + "withdraw: Token is disabled!" + ); + + require( + _amount > 0, + "withdraw: Withdraw amount should be greater than 0!" + ); + + IiToken _iToken = iToken; + // address _cToken = cTokens[_underlying]; + require(address(_iToken) != address(0), "withdraw: Do not support token!"); + + uint256 _MarketBalanceBefore = _iToken.balanceOfUnderlying(address(this)); + + // Update the stored interest with the market balance before the redeem + InterestDetails storage _details = interestDetails[_underlying]; + uint256 _interest = _MarketBalanceBefore.sub( + _details.totalUnderlyingBalance + ); + _details.interest = _details.interest.add(_interest); + + uint256 _handlerBalanceBefore = IERC20(_underlying).balanceOf( + address(this) + ); + + // Redeem all or just the amount of underlying token + if (_amount == uint256(-1)) { + _iToken.redeem(address(this), IERC20(address(_iToken)).balanceOf(address(this))); + } else { + _iToken.redeemUnderlying(address(this), _amount); + } + + // Store the latest real balance. + _details.totalUnderlyingBalance = _iToken.balanceOfUnderlying(address(this)); + + uint256 _changedAmount = IERC20(_underlying).balanceOf(address(this)).sub( + _handlerBalanceBefore + ); + + // return a smaller value. + return _changedAmount > _amount ? _amount : _changedAmount; + } + + /** + * @dev external function to transfer reward token airdrops to corresponding dToken to distribute. + */ + function claimReward() external { + address[] memory _holders = new address[](1); + address[] memory _iTokens = new address[](1); + _holders[0] = address(this); + _iTokens[0] = address(iToken); + controller.rewardDistributor().claimReward(_holders, _iTokens); + + address _rewardToken = controller.rewardDistributor().rewardToken(); + uint256 _rewardBalance = IERC20(_rewardToken).balanceOf(_holders[0]); + if (_rewardBalance > 0) { + address _dToken = IDTokenController(dTokenController).getDToken(underlying); + require( + doTransferOut(_rewardToken, _dToken, _rewardBalance), + "deposit: Comp transfer out of contract failed." + ); + emit RewardClaimed(_dToken, _rewardBalance); + } + } + + /** + * @dev Update exchange rate in cToken and get the latest total balance for + * handler's _underlying, with all accumulated interest included. + * @param _underlying Token to get actual balance. + */ + function getRealBalance(address _underlying) + external + returns (uint256) + { + _underlying; + return iToken.balanceOfUnderlying(address(this)); + } + + /** + * @dev The latest maximum withdrawable _underlying in the market. + * @param _underlying Token to get liquidity. + */ + function getRealLiquidity(address _underlying) + external + returns (uint256) + { + _underlying; + IiToken _iToken = iToken; + uint256 _underlyingBalance = _iToken.balanceOfUnderlying(address(this)); + uint256 _cash = _iToken.getCash(); + + return _underlyingBalance > _cash ? _cash : _underlyingBalance; + } + + /***************************************************/ + /*** View Interfaces For Backwards compatibility ***/ + /***************************************************/ + + /** + * @dev Total balance of handler's _underlying, accumulated interest included + * @param _underlying Token to get balance. + */ + function getBalance(address _underlying) + public + view + returns (uint256) + { + _underlying; + IiToken _iToken = iToken; + return IERC20(address(_iToken)).balanceOf(address(this)).mul(_iToken.exchangeRateStored()) / BASE; + } + + /** + * @dev The maximum withdrawable amount of _underlying in the market. + * @param _underlying Token to get liquidity. + */ + function getLiquidity(address _underlying) + external + view + returns (uint256) + { + uint256 _underlyingBalance = getBalance(_underlying); + uint256 _cash = iToken.getCash(); + + return _underlyingBalance > _cash ? _cash : _underlyingBalance; + } +} diff --git a/contracts/interface/IDForceLending.sol b/contracts/interface/IDForceLending.sol new file mode 100644 index 0000000..e43cfbf --- /dev/null +++ b/contracts/interface/IDForceLending.sol @@ -0,0 +1,34 @@ +pragma solidity 0.5.12; + +interface IController { + function hasiToken(address _iToken) external view returns (bool); + function rewardDistributor() external view returns (IRewardDistributor); +} + +interface IRewardDistributor { + function claimAllReward(address[] calldata _holders) external; + function claimReward(address[] calldata _holders, address[] calldata _iTokens) external; + + function rewardToken() external view returns (address); +} + +interface IiToken { + function mint(address _recipient, uint256 _mintAmount) external; + + function redeem(address _from, uint256 _redeemiToken) external; + + function redeemUnderlying(address _from, uint256 _redeemUnderlying) external; + + function balanceOfUnderlying(address _account) external returns (uint256); + + function exchangeRateStored() external view returns (uint256); + + function getCash() external view returns (uint256); + + function controller() external view returns (IController); + + function underlying() external view returns (address); + + function isiToken() external view returns (bool); +} +