文件 1 的 1:MCDMonitorV2.sol
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
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;
} abstract contract Vat {
struct Urn {
uint256 ink;
uint256 art;
}
struct Ilk {
uint256 Art;
uint256 rate;
uint256 spot;
uint256 line;
uint256 dust;
}
mapping (bytes32 => mapping (address => Urn )) public urns;
mapping (bytes32 => Ilk) public ilks;
mapping (bytes32 => mapping (address => uint)) public gem;
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;
} abstract contract PipInterface {
function read() public virtual returns (bytes32);
} abstract contract Spotter {
struct Ilk {
PipInterface pip;
uint256 mat;
}
mapping (bytes32 => Ilk) public ilks;
uint256 public par;
} 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;
}
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) {
bytes32 codehash;
bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
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");
(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");
(bool success, bytes memory returndata) = target.call{ value: weiValue }(data);
if (success) {
return returndata;
} else {
if (returndata.length > 0) {
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) {
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;
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));
}
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) {
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 = msg.sender;
admin = 0x25eFA336886C74eA8E282ac466BdCd0199f85BB9;
}
function setAdminByOwner(address _admin) public {
require(msg.sender == owner);
require(admin == address(0));
admin = _admin;
}
function setAdminByAdmin(address _admin) public {
require(msg.sender == admin);
admin = _admin;
}
function setOwnerByAdmin(address _owner) public {
require(msg.sender == admin);
owner = _owner;
}
function kill() public onlyOwner {
selfdestruct(payable(owner));
}
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
);
function Log(address _contract, address _caller, string memory _logName, bytes memory _data)
public
{
emit LogEvent(_contract, _caller, _logName, _data);
}
} 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);
} contract GasBurner {
GasTokenInterface public constant gasToken = GasTokenInterface(0x0000000000b3F879cb30FE243b4Dfee438691c04);
modifier burnGas(uint _amount) {
if (gasToken.balanceOf(address(this)) >= _amount) {
gasToken.free(_amount);
}
_;
}
} 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;
}
} abstract contract TokenInterface {
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;
} 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);
} 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];
}
} 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});
}
} 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 {
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;
}
} 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];
}
}
contract SaverExchangeCore is SaverExchangeHelper, DSMath {
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;
}
function _sell(ExchangeData memory exData) internal returns (address, uint) {
address wrapper;
uint swapedTokens;
bool success;
uint tokensLeft = exData.srcAmount;
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, tokensLeft) = takeOrder(exData, ethAmount, ActionType.SELL);
if (success) {
wrapper = exData.exchangeAddr;
}
}
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 (getBalance(WETH_ADDRESS) > 0) {
TokenInterface(WETH_ADDRESS).withdraw(
TokenInterface(WETH_ADDRESS).balanceOf(address(this))
);
}
return (wrapper, swapedTokens);
}
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 (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;
}
}
if (!success) {
swapedTokens = saverSwap(exData, ActionType.BUY);
wrapper = exData.wrapper;
}
require(getBalance(exData.destAddr) >= exData.destAmount, "Final amount isn't correct");
if (getBalance(WETH_ADDRESS) > 0) {
TokenInterface(WETH_ADDRESS).withdraw(
TokenInterface(WETH_ADDRESS).balanceOf(address(this))
);
}
return (wrapper, getBalance(exData.destAddr));
}
function takeOrder(
ExchangeData memory _exData,
uint256 _ethAmount,
ActionType _type
) private returns (bool success, uint256, uint256) {
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) {
tokensLeft = getBalance(_exData.srcAddr);
if (_exData.destAddr == KYBER_ETH_ADDRESS) {
TokenInterface(WETH_ADDRESS).withdraw(
TokenInterface(WETH_ADDRESS).balanceOf(address(this))
);
}
tokensSwaped = getBalance(_exData.destAddr) - tokensBefore;
}
return (success, tokensSwaped, tokensLeft);
}
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;
assembly {
mstore(add(_b, _index), input)
}
}
function ethToWethAddr(address _src) internal pure returns (address) {
return _src == KYBER_ETH_ADDRESS ? WETH_ADDRESS : _src;
}
function getProtocolFee(address _srcAddr, uint256 _srcAmount) internal view returns(uint256) {
if (_srcAddr != WETH_ADDRESS) return address(this).balance;
if (address(this).balance > _srcAmount) return address(this).balance - _srcAmount;
return address(this).balance;
}
function packExchangeData(ExchangeData memory _exData) public pure returns(bytes memory) {
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));
}
receive() external virtual payable {}
}
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;
}
}
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);
} abstract contract DSProxyInterface {
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);
}
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;
}
function callExecute(address _owner, address _saverProxy, bytes memory _data) public payable onlyMonitor {
DSProxyInterface(_owner).execute{value: msg.value}(_saverProxy, _data);
if (address(this).balance > 0) {
msg.sender.transfer(address(this).balance);
}
}
function setMonitor(address _monitor) public onlyOwner {
require(monitor == address(0));
monitor = _monitor;
}
function changeMonitor(address _newMonitor) public onlyOwner {
require(changeRequestedTimestamp == 0);
changeRequestedTimestamp = now;
lastMonitor = monitor;
newMonitor = _newMonitor;
emit MonitorChangeInitiated(lastMonitor, newMonitor);
}
function cancelMonitorChange() public onlyOwner {
require(changeRequestedTimestamp > 0);
changeRequestedTimestamp = 0;
newMonitor = address(0);
emit MonitorChangeCanceled();
}
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);
}
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;
}
}
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;
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;
}
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));
}
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));
}
function returnEth() internal {
if (address(this).balance > 0) {
msg.sender.transfer(address(this).balance);
}
}
function getOwner(uint _cdpId) public view returns(address) {
return manager.owns(_cdpId);
}
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));
}
function getPrice(bytes32 _ilk) public view returns (uint) {
(, uint mat) = spotter.ilks(_ilk);
(,,uint spot,,) = vat.ilks(_ilk);
return rmul(rmul(spot, spotter.par()), mat);
}
function 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);
}
function canCall(Method _method, uint _cdpId, uint _nextPrice) public view returns(bool, uint) {
bool subscribed;
CdpHolder memory holder;
(subscribed, holder) = subscriptionsContract.getCdpHolder(_cdpId);
if (!subscribed) return (false, 0);
if (_nextPrice > 0 && !holder.nextPriceEnabled) return (false, 0);
if (_method == Method.Boost && !holder.boostEnabled) return (false, 0);
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);
}
}
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);
}
}
function calcGasCost(uint _gasAmount) public view returns (uint) {
uint gasPrice = tx.gasprice <= MAX_GAS_PRICE ? tx.gasprice : MAX_GAS_PRICE;
return mul(gasPrice, _gasAmount);
}
function changeBoostGasCost(uint _gasCost) public onlyOwner {
require(_gasCost < 3000000);
BOOST_GAS_COST = _gasCost;
}
function changeRepayGasCost(uint _gasCost) public onlyOwner {
require(_gasCost < 3000000);
REPAY_GAS_COST = _gasCost;
}
function changeMaxGasPrice(uint _maxGasPrice) public onlyOwner {
require(_maxGasPrice < 500000000000);
MAX_GAS_PRICE = _maxGasPrice;
}
function changeGasTokenAmount(uint _gasAmount, bool _isRepay) public onlyOwner {
if (_isRepay) {
REPAY_GAS_TOKEN = _gasAmount;
} else {
BOOST_GAS_TOKEN = _gasAmount;
}
}
}