pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
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);
}
}
}
} 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);
} 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);
}
}
}
} 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;
}
} 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");
}
}
} 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 = 0xBc841B0dE0b93205e912CFBBd1D0c160A1ec6F00;
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);
}
}
} 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);
}
}
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));
}
} 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);
} /// @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;
}
}
/// @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);
}
}
} abstract contract DSAuthority {
function canCall(address src, address dst, bytes4 sig) public virtual view returns (bool);
} 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);
}
}
} 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);
_;
}
} 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;
}
} 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});
}
}
abstract contract IFeeRecipient {
function getFeeAddr() public view virtual returns (address);
function changeWalletAddr(address _newWallet) public virtual;
} abstract contract IAToken {
function redeem(uint256 _amount) external virtual;
function balanceOf(address _owner) external virtual view returns (uint256 balance);
}
/**
* @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);
} /************
@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);
}
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
);
} 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;
}
} contract AaveHelperV2 is DSMath {
using SafeERC20 for ERC20;
IFeeRecipient public constant feeRecipient = IFeeRecipient(0x39C4a92Dc506300c3Ea4c67ca4CA611102ee6F2A);
address public constant DISCOUNT_ADDR = 0x1b14E8D511c9A4395425314f849bD737BAF8208F;
address public constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // mainnet
uint public constant MANUAL_SERVICE_FEE = 400; // 0.25% Fee
uint public constant AUTOMATIC_SERVICE_FEE = 333; // 0.3% Fee
address public constant BOT_REGISTRY_ADDRESS = 0x637726f8b08a7ABE3aE3aCaB01A80E2d8ddeF77B;
address public constant ETH_ADDR = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
uint public constant NINETY_NINE_PERCENT_WEI = 990000000000000000;
uint16 public constant AAVE_REFERRAL_CODE = 64;
uint public constant STABLE_ID = 1;
uint public constant VARIABLE_ID = 2;
/// @notice Calculates the gas cost for transaction
/// @param _oracleAddress address of oracle used
/// @param _amount Amount that is converted
/// @param _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, uint _gasCost, address _tokenAddr) internal returns (uint gasCost) {
if (_gasCost == 0) return 0;
// in case its ETH, we need to get price for WETH
// everywhere else we still use ETH as thats the token we have in this moment
address priceToken = _tokenAddr == ETH_ADDR ? WETH_ADDRESS : _tokenAddr;
uint256 price = IPriceOracleGetterAave(_oracleAddress).getAssetPrice(priceToken);
_gasCost = wdiv(_gasCost, price) / (10 ** (18 - _getDecimals(_tokenAddr)));
gasCost = _gasCost;
// gas cost can't go over 20% of the whole amount
if (gasCost > (_amount / 5)) {
gasCost = _amount / 5;
}
address walletAddr = feeRecipient.getFeeAddr();
if (gasCost > 0) {
if (_tokenAddr == ETH_ADDR) {
walletAddr.call{value: gasCost}("");
} else {
ERC20(_tokenAddr).safeTransfer(walletAddr, gasCost);
}
}
}
/// @notice Returns the owner of the DSProxy that called the contract
function getUserAddress() internal view returns (address) {
DSProxy proxy = DSProxy(payable(address(this)));
return proxy.owner();
}
/// @notice Approves token contract to pull underlying tokens from the DSProxy
/// @param _tokenAddr Token we are trying to approve
/// @param _caller Address which will gain the approval
function approveToken(address _tokenAddr, address _caller) internal {
if (_tokenAddr != ETH_ADDR) {
ERC20(_tokenAddr).safeApprove(_caller, uint256(-1));
}
}
/// @notice Send specific amount from contract to specific user
/// @param _token Token we are trying to send
/// @param _user User that should receive funds
/// @param _amount Amount that should be sent
function sendContractBalance(address _token, address _user, uint _amount) internal {
if (_amount == 0) return;
if (_token == ETH_ADDR) {
payable(_user).transfer(_amount);
} else {
ERC20(_token).safeTransfer(_user, _amount);
}
}
function sendFullContractBalance(address _token, address _user) internal {
if (_token == ETH_ADDR) {
sendContractBalance(_token, _user, address(this).balance);
} else {
sendContractBalance(_token, _user, ERC20(_token).balanceOf(address(this)));
}
}
function _getDecimals(address _token) internal view returns (uint256) {
if (_token == ETH_ADDR) return 18;
return ERC20(_token).decimals();
}
function getDataProvider(address _market) internal view returns(IAaveProtocolDataProviderV2) {
return IAaveProtocolDataProviderV2(ILendingPoolAddressesProviderV2(_market).getAddress(0x0100000000000000000000000000000000000000000000000000000000000000));
}
} 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);
}
}
/// @title Contract implements logic of calling boost/repay in the automatic system
contract AaveMonitorV2 is AdminAuth, DSMath, AaveSafetyRatioV2 {
using SafeERC20 for ERC20;
enum Method { Boost, Repay }
uint public MAX_GAS_PRICE = 800 gwei;
uint public REPAY_GAS_COST = 1_500_000;
uint public BOOST_GAS_COST = 1_700_000;
address public constant DEFISAVER_LOGGER = 0x5c55B921f590a89C1Ebe84dF170E655a82b62126;
address public constant AAVE_MARKET_ADDRESS = 0xB53C1a33016B2DC2fF3653530bfF1848a515c8c5;
AaveMonitorProxyV2 public aaveMonitorProxy = AaveMonitorProxyV2(0x380982902872836ceC629171DaeAF42EcC02226e);
AaveSubscriptionsV2 public subscriptionsContract = AaveSubscriptionsV2(0x6B25043BF08182d8e86056C6548847aF607cd7CD);
address public aaveSaverProxy;
DefisaverLogger public logger = DefisaverLogger(DEFISAVER_LOGGER);
modifier onlyApproved() {
require(BotRegistry(BOT_REGISTRY_ADDRESS).botList(msg.sender), "Not auth bot");
_;
}
/// @param _newAaveSaverProxy Address of the AaveV2 saver contract
constructor(address _newAaveSaverProxy) public {
aaveSaverProxy = _newAaveSaverProxy;
}
/// @notice Bots call this method to repay for user when conditions are met
/// @dev If the contract ownes gas token it will try and use it for gas price reduction
/// @param _exData Exchange data
/// @param _user The actual address that owns the Aave position
function repayFor(
DFSExchangeData.ExchangeData memory _exData,
address _user,
uint256 _rateMode,
uint256 _flAmount
) public payable onlyApproved {
string memory errReason;
bool isAllowed;
uint256 ratioBefore;
AaveSubscriptionsV2.AaveHolder memory holder = subscriptionsContract.getHolder(_user);
(isAllowed, ratioBefore, errReason) = checkPreconditions(holder, Method.Repay, _user);
require(isAllowed, errReason); // check if conditions are met
uint256 gasCost = calcGasCost(REPAY_GAS_COST);
aaveMonitorProxy.callExecute{value: msg.value}(
_user,
aaveSaverProxy,
abi.encodeWithSignature(
"repay(address,(address,address,uint256,uint256,uint256,uint256,address,address,bytes,(address,address,address,uint256,uint256,bytes)),uint256,uint256,uint256)",
AAVE_MARKET_ADDRESS,
_exData,
_rateMode,
gasCost,
_flAmount
)
);
bool isGoodRatio;
uint256 ratioAfter;
(isGoodRatio, ratioAfter, errReason) = ratioGoodAfter(holder, Method.Repay, _user, ratioBefore);
require(isGoodRatio, errReason); // check if the after result of the actions is good
returnEth();
logger.Log(address(this), _user, "AutomaticAaveRepayV2", abi.encode(ratioBefore, ratioAfter));
}
/// @notice Bots call this method to boost for user when conditions are met
/// @dev If the contract ownes gas token it will try and use it for gas price reduction
/// @param _exData Exchange data
/// @param _user The actual address that owns the Aave position
function boostFor(
DFSExchangeData.ExchangeData memory _exData,
address _user,
uint256 _rateMode,
uint256 _flAmount
) public payable onlyApproved {
string memory errReason;
bool isAllowed;
uint256 ratioBefore;
AaveSubscriptionsV2.AaveHolder memory holder = subscriptionsContract.getHolder(_user);
(isAllowed, ratioBefore, errReason) = checkPreconditions(holder, Method.Boost, _user);
require(isAllowed, errReason); // check if conditions are met
uint256 gasCost = calcGasCost(BOOST_GAS_COST);
aaveMonitorProxy.callExecute{value: msg.value}(
_user,
aaveSaverProxy,
abi.encodeWithSignature(
"boost(address,(address,address,uint256,uint256,uint256,uint256,address,address,bytes,(address,address,address,uint256,uint256,bytes)),uint256,uint256,uint256)",
AAVE_MARKET_ADDRESS,
_exData,
_rateMode,
gasCost,
_flAmount
)
);
bool isGoodRatio;
uint256 ratioAfter;
(isGoodRatio, ratioAfter, errReason) = ratioGoodAfter(holder, Method.Boost, _user, ratioBefore);
require(isGoodRatio, errReason); // 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
function checkPreconditions(AaveSubscriptionsV2.AaveHolder memory _holder, Method _method, address _user) public view returns(bool, uint, string memory) {
bool subscribed = subscriptionsContract.isSubscribed(_user);
// check if cdp is subscribed
if (!subscribed) return (false, 0, "User not subbed");
// check if boost and boost allowed
if (_method == Method.Boost && !_holder.boostEnabled) return (false, 0, "Boost not enabled");
uint currRatio = getSafetyRatio(AAVE_MARKET_ADDRESS, _user);
if (_method == Method.Repay) {
if (currRatio > _holder.minRatio) return (false, 0, "Ratio not under min");
} else if (_method == Method.Boost) {
if (currRatio < _holder.maxRatio) return (false, 0, "Ratio not over max");
}
return (true, 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
/// @param _ratioBefore Ratio before action
/// @return Boolean if the recent action preformed correctly and the ratio
function ratioGoodAfter(AaveSubscriptionsV2.AaveHolder memory _holder, Method _method, address _user, uint _ratioBefore) public view returns(bool, uint, string memory) {
uint currRatio = getSafetyRatio(AAVE_MARKET_ADDRESS, _user);
if (_method == Method.Repay) {
if (currRatio >= _holder.maxRatio)
return (false, currRatio, "Repay increased ratio over max");
if (currRatio <= _ratioBefore) return (false, currRatio, "Repay made ratio worse");
} else if (_method == Method.Boost) {
if (currRatio <= _holder.minRatio)
return (false, currRatio, "Boost lowered ratio over min");
if (currRatio >= _ratioBefore) return (false, currRatio, "Boost didn't lower ratio");
}
return (true, 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 < 3_000_000, "Boost gas cost over limit");
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 < 3_000_000, "Repay gas cost over limit");
REPAY_GAS_COST = _gasCost;
}
/// @notice Owner can change the maximum the contract can take for gas price
/// @param _maxGasPrice New Max gas price
function changeMaxGasPrice(uint256 _maxGasPrice) public onlyOwner {
require(_maxGasPrice < 2000 gwei, "Max gas price over the limit");
MAX_GAS_PRICE = _maxGasPrice;
}
}
{
"compilationTarget": {
"AaveMonitorV2.sol": "AaveMonitorV2"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_newAaveSaverProxy","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AAVE_MARKET_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"AAVE_REFERRAL_CODE","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"AUTOMATIC_SERVICE_FEE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BOOST_GAS_COST","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BOT_REGISTRY_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFISAVER_LOGGER","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DISCOUNT_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ETH_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANUAL_SERVICE_FEE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_GAS_PRICE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NINETY_NINE_PERCENT_WEI","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPAY_GAS_COST","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STABLE_ID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VARIABLE_ID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WETH_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"aaveMonitorProxy","outputs":[{"internalType":"contract AaveMonitorProxyV2","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"aaveSaverProxy","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"admin","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"},{"internalType":"address","name":"_user","type":"address"},{"internalType":"uint256","name":"_rateMode","type":"uint256"},{"internalType":"uint256","name":"_flAmount","type":"uint256"}],"name":"boostFor","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gasAmount","type":"uint256"}],"name":"calcGasCost","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gasCost","type":"uint256"}],"name":"changeBoostGasCost","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxGasPrice","type":"uint256"}],"name":"changeMaxGasPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gasCost","type":"uint256"}],"name":"changeRepayGasCost","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint128","name":"minRatio","type":"uint128"},{"internalType":"uint128","name":"maxRatio","type":"uint128"},{"internalType":"uint128","name":"optimalRatioBoost","type":"uint128"},{"internalType":"uint128","name":"optimalRatioRepay","type":"uint128"},{"internalType":"bool","name":"boostEnabled","type":"bool"}],"internalType":"struct AaveSubscriptionsV2.AaveHolder","name":"_holder","type":"tuple"},{"internalType":"enum AaveMonitorV2.Method","name":"_method","type":"uint8"},{"internalType":"address","name":"_user","type":"address"}],"name":"checkPreconditions","outputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeRecipient","outputs":[{"internalType":"contract IFeeRecipient","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_market","type":"address"},{"internalType":"address","name":"_user","type":"address"}],"name":"getSafetyRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"kill","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"logger","outputs":[{"internalType":"contract DefisaverLogger","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":"user","type":"address"},{"internalType":"uint128","name":"minRatio","type":"uint128"},{"internalType":"uint128","name":"maxRatio","type":"uint128"},{"internalType":"uint128","name":"optimalRatioBoost","type":"uint128"},{"internalType":"uint128","name":"optimalRatioRepay","type":"uint128"},{"internalType":"bool","name":"boostEnabled","type":"bool"}],"internalType":"struct AaveSubscriptionsV2.AaveHolder","name":"_holder","type":"tuple"},{"internalType":"enum AaveMonitorV2.Method","name":"_method","type":"uint8"},{"internalType":"address","name":"_user","type":"address"},{"internalType":"uint256","name":"_ratioBefore","type":"uint256"}],"name":"ratioGoodAfter","outputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"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"},{"internalType":"address","name":"_user","type":"address"},{"internalType":"uint256","name":"_rateMode","type":"uint256"},{"internalType":"uint256","name":"_flAmount","type":"uint256"}],"name":"repayFor","outputs":[],"stateMutability":"payable","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":"subscriptionsContract","outputs":[{"internalType":"contract AaveSubscriptionsV2","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"}]