-
-
Save andrecronje/4df5ad24de83bd18a893efb2524b3536 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity 0.8.6; | |
library Math { | |
function min(uint256 a, uint256 b) internal pure returns (uint256) { | |
return a < b ? a : b; | |
} | |
} | |
interface IERC20 { | |
function transfer(address recipient, uint256 amount) external returns (bool); | |
function decimals() external view returns (uint256); | |
function symbol() external view returns (string memory); | |
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); | |
} | |
interface Oracle { | |
struct ReferenceData { | |
uint256 rate; // base/quote exchange rate, multiplied by 1e18. | |
uint256 lastUpdatedBase; // UNIX epoch of the last time when base price gets updated. | |
uint256 lastUpdatedQuote; // UNIX epoch of the last time when quote price gets updated. | |
} | |
function getReferenceData(string memory _base, string memory _quote) | |
external | |
view | |
returns (ReferenceData memory); | |
} | |
interface cToken { | |
function underlying() external view returns (address); | |
} | |
interface comptroller { | |
function getAllMarkets() external view returns (address[] memory); | |
function markets(address _market) external view returns (bool isListed, uint256 collateralFactorMantissa, bool isComped); | |
} | |
interface ibtroller { | |
function getAllMarkets() external view returns (address[] memory); | |
function markets(address _market) external view returns (bool isListed, uint256 collateralFactorMantissa); | |
} | |
interface aavecore { | |
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; | |
} | |
function getReserveConfiguration(address _market) external view returns (uint, uint, uint, bool); | |
function getConfiguration(address _market) external view returns (ReserveConfigurationMap memory); | |
} | |
interface vaultparams { | |
function initialCollateralRatio(address _token) external view returns (uint); | |
} | |
interface SushiswapV2Router02 { | |
function getAmountsOut(uint amountIn, address[] memory path) external view returns (uint[] memory amounts); | |
} | |
contract FixedUSD { | |
string public constant name = "Fixed USD"; | |
string public constant symbol = "USDF"; | |
uint8 public constant decimals = 18; | |
address constant _oracle = 0xDA7a001b254CD22e46d3eAB04d937489c93174C3; | |
address constant _stable = 0x6B175474E89094C44Da98b954EedeAC495271d0F; | |
address constant _router = 0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F; | |
string constant _quote = "USD"; | |
uint constant _BASE = 100; | |
uint constant _LIQUIDITY_THRESHOLD = 5; | |
uint constant _LIQUIDATION_VALUE = 90; | |
uint constant _CACHE = 1 days; | |
uint constant _minLiquidity = 500000e18; | |
/// @notice Total number of tokens in circulation | |
uint public totalSupply = 0; | |
mapping(address => mapping (address => uint)) internal allowances; | |
mapping(address => uint) internal balances; | |
event Transfer(address indexed from, address indexed to, uint amount); | |
event Approval(address indexed owner, address indexed spender, uint amount); | |
mapping(address => mapping(address => uint)) public credit; | |
mapping(address => mapping(address => uint)) public collateral; | |
mapping(address => uint) public credits; | |
mapping(address => uint) public collaterals; | |
mapping(address => uint) public ltvs; | |
mapping(address => uint) _ltvCaches; | |
mapping(address => uint) public liquidities; | |
mapping(address => uint) _liquidityCaches; | |
mapping(address => uint) public fallbackQuote; | |
uint public arb; | |
event Mint(address indexed from, address indexed asset, address indexed to, uint amount); | |
event Burn(address indexed from, address indexed asset, address indexed to, uint amount); | |
event Liquidate(address indexed from, address indexed asset, address indexed to, uint amount); | |
function validateLTV(uint __ltv) public pure returns (bool) { | |
if ((__ltv == 60) || (__ltv == 65) || (__ltv == 70) || (__ltv == 75)) { | |
return true; | |
} else if ((__ltv == 80) || (__ltv == 85) || __ltv >= 90) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
function gentleRepaymentCalculator(uint __ltv, uint debt, uint value) public pure returns (uint repayment) { | |
if (__ltv == 60) { | |
return Math.min((debt - value) * 310 / _BASE, debt); | |
} else if (__ltv == 65) { | |
return Math.min((debt - value) * 370 / _BASE, debt); | |
} else if (__ltv == 70) { | |
return Math.min((debt - value) * 460 / _BASE, debt); | |
} else if (__ltv == 75) { | |
return Math.min((debt - value) * 610 / _BASE, debt); | |
} else if (__ltv == 80) { | |
return Math.min((debt - value) * 910 / _BASE, debt); | |
} else if (__ltv == 85) { | |
return Math.min((debt - value) * 1810 / _BASE, debt); | |
} else if (__ltv >= 90) { | |
return debt; | |
} | |
} | |
function _lookup(address quoted, uint amount) internal returns (uint) { | |
uint _quoted = Oracle(_oracle).getReferenceData(IERC20(quoted).symbol(), _quote).rate; | |
if (_quoted == 0 && fallbackQuote[quoted] != 0) { | |
_quoted = fallbackQuote[quoted]; | |
} else { | |
fallbackQuote[quoted] = _quoted; | |
} | |
return _quoted * (amount * _ltv(quoted) / _BASE) / 10 ** IERC20(quoted).decimals(); | |
} | |
function lookup(address quoted, uint amount) public view returns (uint) { | |
uint _quoted = Oracle(_oracle).getReferenceData(IERC20(quoted).symbol(), _quote).rate; | |
if (_quoted == 0 && fallbackQuote[quoted] != 0) { | |
_quoted = fallbackQuote[quoted]; | |
} | |
return _quoted * (amount * ltvs[quoted] / _BASE) / 10 ** IERC20(quoted).decimals(); | |
} | |
function lookup(address quoted) external view returns (uint) { | |
return Oracle(_oracle).getReferenceData(IERC20(quoted).symbol(), _quote).rate; | |
} | |
function lookupLiq(address quoted, uint amount) public view returns (uint) { | |
return Oracle(_oracle).getReferenceData(IERC20(quoted).symbol(), _quote).rate * (amount * _LIQUIDATION_VALUE / _BASE) / 10 ** IERC20(quoted).decimals(); | |
} | |
function mintArb(uint amount) external { | |
_mintArb(amount, msg.sender); | |
} | |
function mintArb(uint amount, address recipient) external { | |
_mintArb(amount, recipient); | |
} | |
function _mintArb(uint amount, address recipient) internal { | |
_safeTransferFrom(_stable, msg.sender, address(this), amount); | |
_mint(recipient, amount); | |
arb += amount; | |
emit Mint(msg.sender, _stable, recipient, amount); | |
} | |
function burnArb(uint amount) external { | |
_burnArb(amount, msg.sender); | |
} | |
function burnArb(uint amount, address recipient) external { | |
_burnArb(amount, recipient); | |
} | |
function _burnArb(uint amount, address recipient) internal { | |
_burn(msg.sender, amount); | |
_safeTransfer(_stable, recipient, amount); | |
arb -= amount; | |
emit Burn(msg.sender, _stable, recipient, amount); | |
} | |
function mint(address asset, uint amount, uint minted) external { | |
_mint(asset, amount, minted, msg.sender); | |
} | |
function mint(address asset, uint amount, uint minted, address recipient) external { | |
_mint(asset, amount, minted, recipient); | |
} | |
function _mint(address asset, uint amount, uint minted, address recipient) internal { | |
if (amount > 0) { | |
_safeTransferFrom(asset, msg.sender, address(this), amount); | |
} | |
collateral[msg.sender][asset] += amount; | |
collaterals[asset] += amount; | |
credit[msg.sender][asset] += minted; | |
credits[asset] += minted; | |
require(_liquidity(asset, collaterals[asset]) >= credits[asset]); | |
require(_lookup(asset, collateral[msg.sender][asset]) >= credit[msg.sender][asset]); | |
_mint(recipient, minted); | |
emit Mint(msg.sender, asset, recipient, amount); | |
} | |
function burn(address asset, uint amount, uint burned) external { | |
_burn(asset, amount, burned, msg.sender); | |
} | |
function burn(address asset, uint amount, uint burned, address recipient) external { | |
_burn(asset, amount, burned, recipient); | |
} | |
function _burn(address asset, uint amount, uint burned, address recipient) internal { | |
_burn(msg.sender, burned); | |
credit[msg.sender][asset] -= burned; | |
credits[asset] -= burned; | |
collateral[msg.sender][asset] -= amount; | |
collaterals[asset] -= amount; | |
require(lookup(asset, collateral[msg.sender][asset]) >= credit[msg.sender][asset]); | |
if (amount > 0) { | |
_safeTransfer(asset, recipient, amount); | |
} | |
emit Burn(msg.sender, asset, recipient, amount); | |
} | |
function repaymentCalculator(address owner, address asset) external view returns (uint) { | |
uint _nominal = collateral[owner][asset]; | |
uint _backed = lookup(asset, _nominal); | |
uint _debt = credit[owner][asset]; | |
if (_backed < _debt) { | |
return gentleRepaymentCalculator(ltvs[asset], _debt, _backed); | |
} else { | |
return 0; | |
} | |
} | |
function paymentCalculator(address owner, address asset) external view returns (uint) { | |
uint _nominal = collateral[owner][asset]; | |
uint _backed = lookup(asset, _nominal); | |
uint _debt = credit[owner][asset]; | |
if (_backed < _debt) { | |
uint _repayment = gentleRepaymentCalculator(ltvs[asset], _debt, _backed); | |
return Math.min(_nominal * _repayment / lookupLiq(asset, _nominal), _nominal); | |
} else { | |
return 0; | |
} | |
} | |
function liquidate(address owner, address asset, uint max) external { | |
uint _nominal = collateral[owner][asset]; | |
uint _backed = _lookup(asset, _nominal); | |
uint _debt = credit[owner][asset]; | |
require(_backed < _debt); | |
uint _repayment = gentleRepaymentCalculator(_ltv(asset), _debt, _backed); | |
require(_repayment <= max); | |
uint _payment = Math.min(_nominal * _repayment / lookupLiq(asset, _nominal), _nominal); | |
_burn(msg.sender, _repayment); | |
credit[owner][asset] -= _repayment; | |
credits[asset] -= _repayment; | |
collateral[owner][asset] -= _payment; | |
collaterals[asset] -= _payment; | |
require(_lookup(asset, collateral[owner][asset]) >= credit[owner][asset]); | |
_safeTransfer(asset, msg.sender, _payment); | |
emit Liquidate(msg.sender, asset, owner, _repayment); | |
} | |
function _mint(address dst, uint amount) internal { | |
// mint the amount | |
totalSupply += amount; | |
// transfer the amount to the recipient | |
balances[dst] += amount; | |
emit Transfer(address(0), dst, amount); | |
} | |
function _burn(address dst, uint amount) internal { | |
// burn the amount | |
totalSupply -= amount; | |
// transfer the amount from the recipient | |
balances[dst] -= amount; | |
emit Transfer(dst, address(0), amount); | |
} | |
function allowance(address account, address spender) external view returns (uint) { | |
return allowances[account][spender]; | |
} | |
function approve(address spender, uint amount) external returns (bool) { | |
allowances[msg.sender][spender] = amount; | |
emit Approval(msg.sender, spender, amount); | |
return true; | |
} | |
function balanceOf(address account) external view returns (uint) { | |
return balances[account]; | |
} | |
function transfer(address dst, uint amount) external returns (bool) { | |
_transferTokens(msg.sender, dst, amount); | |
return true; | |
} | |
function transferFrom(address src, address dst, uint amount) external returns (bool) { | |
address spender = msg.sender; | |
uint spenderAllowance = allowances[src][spender]; | |
if (spender != src && spenderAllowance != type(uint).max) { | |
uint newAllowance = spenderAllowance - amount; | |
allowances[src][spender] = newAllowance; | |
emit Approval(src, spender, newAllowance); | |
} | |
_transferTokens(src, dst, amount); | |
return true; | |
} | |
function _transferTokens(address src, address dst, uint amount) internal { | |
balances[src] -= amount; | |
balances[dst] += amount; | |
emit Transfer(src, dst, amount); | |
} | |
function _safeTransfer( | |
address token, | |
address to, | |
uint256 value | |
) internal { | |
(bool success, bytes memory data) = | |
token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value)); | |
require(success && (data.length == 0 || abi.decode(data, (bool)))); | |
} | |
function _safeTransferFrom( | |
address token, | |
address from, | |
address to, | |
uint256 value | |
) internal { | |
(bool success, bytes memory data) = | |
token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value)); | |
require(success && (data.length == 0 || abi.decode(data, (bool)))); | |
} | |
address constant _aavev2 = address(0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9); | |
address constant _ib = address(0xAB1c342C7bf5Ec5F02ADEA1c2270670bCa144CbB); | |
address constant _unit = address(0x203153522B9EAef4aE17c6e99851EE7b2F7D312E); | |
address constant _weth = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); | |
uint256 constant _LTV_MASK = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000; | |
function _getParamsMemory(aavecore.ReserveConfigurationMap memory self) internal pure returns (uint256) { | |
return (self.data & ~_LTV_MASK); | |
} | |
function _lookupMarket(address _core, address _token) internal view returns (address) { | |
address[] memory _list = comptroller(_core).getAllMarkets(); | |
for (uint i = 0; i < _list.length; i++) { | |
if (_list[i] != address(0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5) && _list[i] != address(0xD06527D5e56A3495252A528C4987003b712860eE)) { | |
if (cToken(_list[i]).underlying() == _token) { | |
return _list[i]; | |
} | |
} | |
} | |
return address(0x0); | |
} | |
function _liquidityV(address token, uint amount) internal view returns (uint, bool) { | |
if (block.timestamp > _liquidityCaches[token]) { | |
if (token == _weth) { | |
return (_liquidityVWETH(amount), true); | |
} else { | |
address[] memory _path = new address[](3); | |
_path[0] = token; | |
_path[1] = _weth; | |
_path[2] = _stable; | |
uint _liq = SushiswapV2Router02(_router).getAmountsOut(amount, _path)[2]; | |
uint _liquid = liquidities[token]; | |
if (_liq > _liquid) { | |
_liquid += _liquid * _LIQUIDITY_THRESHOLD / _BASE; | |
} | |
return (_liquid, true); | |
} | |
} else { | |
return (liquidities[token], false); | |
} | |
} | |
function _liquidityVWETH(uint amount) internal view returns (uint) { | |
address[] memory _path = new address[](2); | |
_path[0] = _weth; | |
_path[1] = _stable; | |
uint _liq = SushiswapV2Router02(_router).getAmountsOut(amount, _path)[1]; | |
uint _liquid = liquidities[_weth]; | |
if (_liq > _liquid) { | |
_liquid += _liquid * _LIQUIDITY_THRESHOLD / _BASE; | |
} | |
return _liquid; | |
} | |
function _liquidity(address token, uint amount) internal returns (uint) { | |
if (_liquidityCaches[token] == 0) { | |
liquidities[token] = _minLiquidity; | |
} | |
(uint _val, bool _updated) = _liquidityV(token, amount); | |
if (_updated) { | |
_liquidityCaches[token] = block.timestamp + _CACHE; | |
liquidities[token] = _val; | |
} | |
return _val; | |
} | |
function liquidity(address token, uint amount) external view returns (uint val) { | |
(val,) = _liquidityV(token, amount); | |
} | |
function _getLTVIB(address token) internal view returns (uint ib) { | |
(,ib) = ibtroller(_ib).markets(_lookupMarket(_ib, token)); | |
ib = ib / 1e16; | |
} | |
function _getLTVUnit(address _token) internal view returns (uint unit) { | |
unit = vaultparams(_unit).initialCollateralRatio(_token); | |
} | |
function _getLTVAaveV2(address token) internal view returns (uint aavev2) { | |
(aavev2) = _getParamsMemory(aavecore(_aavev2).getConfiguration(token)); | |
aavev2 = aavev2 / 1e2; | |
} | |
function _ltv(address token) internal returns (uint) { | |
(uint _val, bool _updated) = _ltvV(token); | |
if (_updated) { | |
_ltvCaches[token] = block.timestamp + _CACHE; | |
ltvs[token] = _val; | |
} | |
return _val; | |
} | |
function ltv(address token) external view returns (uint val) { | |
(val,) = _ltvV(token); | |
} | |
function _ltvV(address token) internal view returns (uint, bool) { | |
if (block.timestamp > _ltvCaches[token]) { | |
uint _max = 0; | |
uint _tmp = _getLTVIB(token); | |
if (_tmp > _max) { | |
_max = _tmp; | |
} | |
_tmp = _getLTVAaveV2(token); | |
if (_tmp > _max) { | |
_max = _tmp; | |
} | |
_tmp = _getLTVUnit(token); | |
if (_tmp > _max) { | |
_max = _tmp; | |
} | |
_max = _max / 5 * 5; | |
if (_max < 60) { | |
_max = 0; | |
} | |
return (_max, true); | |
} else { | |
return (ltvs[token], false); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment