Contract Name:
CDPManager01_Fallback
Contract Source Code:
// SPDX-License-Identifier: bsl-1.1
/*
Copyright 2020 Unit Protocol: Artem Zakharov ([email protected]).
*/
pragma solidity 0.7.6;
pragma abicoder v2;
import '../interfaces/IOracleRegistry.sol';
import '../interfaces/IKeydonixOracleUsd.sol';
import '../interfaces/IToken.sol';
import '../interfaces/IVault.sol';
import '../interfaces/ICDPRegistry.sol';
import '../interfaces/IVaultManagerParameters.sol';
import '../interfaces/IVaultParameters.sol';
import '../helpers/ReentrancyGuard.sol';
import '../helpers/SafeMath.sol';
/**
* @title CDPManager01_Fallback
**/
contract CDPManager01_Fallback is ReentrancyGuard {
using SafeMath for uint;
IVault public immutable vault;
IVaultManagerParameters public immutable vaultManagerParameters;
IOracleRegistry public immutable oracleRegistry;
ICDPRegistry public immutable cdpRegistry;
uint public constant Q112 = 2 ** 112;
uint public constant DENOMINATOR_1E5 = 1e5;
/**
* @dev Trigger when joins are happened
**/
event Join(address indexed asset, address indexed owner, uint main, uint usdp);
/**
* @dev Trigger when exits are happened
**/
event Exit(address indexed asset, address indexed owner, uint main, uint usdp);
/**
* @dev Trigger when liquidations are initiated
**/
event LiquidationTriggered(address indexed asset, address indexed owner);
modifier checkpoint(address asset, address owner) {
_;
cdpRegistry.checkpoint(asset, owner);
}
/**
* @param _vaultManagerParameters The address of the contract with Vault manager parameters
* @param _oracleRegistry The address of the oracle registry
* @param _cdpRegistry The address of the CDP registry
**/
constructor(address _vaultManagerParameters, address _oracleRegistry, address _cdpRegistry) {
require(
_vaultManagerParameters != address(0) &&
_oracleRegistry != address(0) &&
_cdpRegistry != address(0),
"Unit Protocol: INVALID_ARGS"
);
vaultManagerParameters = IVaultManagerParameters(_vaultManagerParameters);
vault = IVault(IVaultParameters(IVaultManagerParameters(_vaultManagerParameters).vaultParameters()).vault());
oracleRegistry = IOracleRegistry(_oracleRegistry);
cdpRegistry = ICDPRegistry(_cdpRegistry);
}
/**
* @notice Depositing tokens must be pre-approved to Vault address
* @notice position actually considered as spawned only when debt > 0
* @dev Deposits collateral and/or borrows USDP
* @param asset The address of the collateral
* @param assetAmount The amount of the collateral to deposit
* @param usdpAmount The amount of USDP token to borrow
**/
function join(address asset, uint assetAmount, uint usdpAmount, IKeydonixOracleUsd.ProofDataStruct calldata proofData) public nonReentrant checkpoint(asset, msg.sender) {
require(usdpAmount != 0 || assetAmount != 0, "Unit Protocol: USELESS_TX");
require(IToken(asset).decimals() <= 18, "Unit Protocol: NOT_SUPPORTED_DECIMALS");
if (usdpAmount == 0) {
vault.depositMain(asset, msg.sender, assetAmount);
} else {
uint oracleType = _selectOracleType(asset);
bool spawned = vault.debts(asset, msg.sender) != 0;
if (!spawned) {
// spawn a position
vault.spawn(asset, msg.sender, oracleType);
}
if (assetAmount != 0) {
vault.depositMain(asset, msg.sender, assetAmount);
}
// mint USDP to owner
vault.borrow(asset, msg.sender, usdpAmount);
// check collateralization
_ensurePositionCollateralization(asset, msg.sender, proofData);
}
// fire an event
emit Join(asset, msg.sender, assetAmount, usdpAmount);
}
/**
* @notice Tx sender must have a sufficient USDP balance to pay the debt
* @dev Withdraws collateral and repays specified amount of debt
* @param asset The address of the collateral
* @param assetAmount The amount of the collateral to withdraw
* @param usdpAmount The amount of USDP to repay
**/
function exit(address asset, uint assetAmount, uint usdpAmount, IKeydonixOracleUsd.ProofDataStruct calldata proofData) public nonReentrant checkpoint(asset, msg.sender) returns (uint) {
// check usefulness of tx
require(assetAmount != 0 || usdpAmount != 0, "Unit Protocol: USELESS_TX");
uint debt = vault.debts(asset, msg.sender);
// catch full repayment
if (usdpAmount > debt) { usdpAmount = debt; }
if (assetAmount == 0) {
_repay(asset, msg.sender, usdpAmount);
} else {
if (debt == usdpAmount) {
vault.withdrawMain(asset, msg.sender, assetAmount);
if (usdpAmount != 0) {
_repay(asset, msg.sender, usdpAmount);
}
} else {
// withdraw collateral to the owner address
vault.withdrawMain(asset, msg.sender, assetAmount);
if (usdpAmount != 0) {
_repay(asset, msg.sender, usdpAmount);
}
vault.update(asset, msg.sender);
_ensurePositionCollateralization(asset, msg.sender, proofData);
}
}
// fire an event
emit Exit(asset, msg.sender, assetAmount, usdpAmount);
return usdpAmount;
}
/**
* @notice Repayment is the sum of the principal and interest
* @dev Withdraws collateral and repays specified amount of debt
* @param asset The address of the collateral
* @param assetAmount The amount of the collateral to withdraw
* @param repayment The target repayment amount
**/
function exit_targetRepayment(address asset, uint assetAmount, uint repayment, IKeydonixOracleUsd.ProofDataStruct calldata proofData) external returns (uint) {
uint usdpAmount = _calcPrincipal(asset, msg.sender, repayment);
return exit(asset, assetAmount, usdpAmount, proofData);
}
// decreases debt
function _repay(address asset, address owner, uint usdpAmount) internal {
uint fee = vault.calculateFee(asset, owner, usdpAmount);
vault.chargeFee(vault.usdp(), owner, fee);
// burn USDP from the owner's balance
uint debtAfter = vault.repay(asset, owner, usdpAmount);
if (debtAfter == 0) {
// clear unused storage
vault.destroy(asset, owner);
}
}
function _ensurePositionCollateralization(address asset, address owner, IKeydonixOracleUsd.ProofDataStruct calldata proofData) internal view {
// collateral value of the position in USD
uint usdValue_q112 = getCollateralUsdValue_q112(asset, owner, proofData);
// USD limit of the position
uint usdLimit = usdValue_q112 * vaultManagerParameters.initialCollateralRatio(asset) / Q112 / 100;
// revert if collateralization is not enough
require(vault.getTotalDebt(asset, owner) <= usdLimit, "Unit Protocol: UNDERCOLLATERALIZED");
}
// Liquidation Trigger
/**
* @dev Triggers liquidation of a position
* @param asset The address of the collateral token of a position
* @param owner The owner of the position
**/
function triggerLiquidation(address asset, address owner, IKeydonixOracleUsd.ProofDataStruct calldata proofData) external nonReentrant {
// USD value of the collateral
uint usdValue_q112 = getCollateralUsdValue_q112(asset, owner, proofData);
// reverts if a position is not liquidatable
require(_isLiquidatablePosition(asset, owner, usdValue_q112), "Unit Protocol: SAFE_POSITION");
uint liquidationDiscount_q112 = usdValue_q112.mul(
vaultManagerParameters.liquidationDiscount(asset)
).div(DENOMINATOR_1E5);
uint initialLiquidationPrice = usdValue_q112.sub(liquidationDiscount_q112).div(Q112);
// sends liquidation command to the Vault
vault.triggerLiquidation(asset, owner, initialLiquidationPrice);
// fire an liquidation event
emit LiquidationTriggered(asset, owner);
}
function getCollateralUsdValue_q112(address asset, address owner, IKeydonixOracleUsd.ProofDataStruct calldata proofData) public view returns (uint) {
uint oracleType = _selectOracleType(asset);
return IKeydonixOracleUsd(oracleRegistry.oracleByType(oracleType)).assetToUsd(asset, vault.collaterals(asset, owner), proofData);
}
/**
* @dev Determines whether a position is liquidatable
* @param asset The address of the collateral
* @param owner The owner of the position
* @param usdValue_q112 Q112-encoded USD value of the collateral
* @return boolean value, whether a position is liquidatable
**/
function _isLiquidatablePosition(
address asset,
address owner,
uint usdValue_q112
) internal view returns (bool) {
uint debt = vault.getTotalDebt(asset, owner);
// position is collateralized if there is no debt
if (debt == 0) return false;
return debt.mul(100).mul(Q112).div(usdValue_q112) >= vaultManagerParameters.liquidationRatio(asset);
}
function _selectOracleType(address asset) internal view returns (uint oracleType) {
oracleType = _getOracleType(asset);
require(oracleType != 0, "Unit Protocol: INVALID_ORACLE_TYPE");
address oracle = oracleRegistry.oracleByType(oracleType);
require(oracle != address(0), "Unit Protocol: DISABLED_ORACLE");
}
/**
* @dev Determines whether a position is liquidatable
* @param asset The address of the collateral
* @param owner The owner of the position
* @return boolean value, whether a position is liquidatable
**/
function isLiquidatablePosition(
address asset,
address owner,
IKeydonixOracleUsd.ProofDataStruct calldata proofData
) external view returns (bool) {
uint usdValue_q112 = getCollateralUsdValue_q112(asset, owner, proofData);
return _isLiquidatablePosition(asset, owner, usdValue_q112);
}
/**
* @dev Calculates current utilization ratio
* @param asset The address of the collateral
* @param owner The owner of the position
* @return utilization ratio
**/
function utilizationRatio(
address asset,
address owner,
IKeydonixOracleUsd.ProofDataStruct calldata proofData
) public view returns (uint) {
uint debt = vault.getTotalDebt(asset, owner);
if (debt == 0) return uint(0);
uint usdValue_q112 = getCollateralUsdValue_q112(asset, owner, proofData);
return debt.mul(100).mul(Q112).div(usdValue_q112);
}
/**
* @dev Calculates liquidation price
* @param asset The address of the collateral
* @param owner The owner of the position
* @return Q112-encoded liquidation price
**/
function liquidationPrice_q112(
address asset,
address owner
) external view returns (uint) {
uint debt = vault.getTotalDebt(asset, owner);
if (debt == 0) return uint(-1);
uint collateralLiqPrice = debt.mul(100).mul(Q112).div(vaultManagerParameters.liquidationRatio(asset));
require(IToken(asset).decimals() <= 18, "Unit Protocol: NOT_SUPPORTED_DECIMALS");
return collateralLiqPrice / vault.collaterals(asset, owner) / 10 ** (18 - IToken(asset).decimals());
}
function _calcPrincipal(address asset, address owner, uint repayment) internal view returns (uint) {
uint fee = vault.stabilityFee(asset, owner) * (block.timestamp - vault.lastUpdate(asset, owner)) / 365 days;
return repayment * DENOMINATOR_1E5 / (DENOMINATOR_1E5 + fee);
}
function _getOracleType(address asset) internal view returns (uint) {
uint[] memory keydonixOracleTypes = oracleRegistry.getKeydonixOracleTypes();
for (uint i = 0; i < keydonixOracleTypes.length; i++) {
if (IVaultParameters(vaultManagerParameters.vaultParameters()).isOracleTypeEnabled(keydonixOracleTypes[i], asset)) {
return keydonixOracleTypes[i];
}
}
revert("Unit Protocol: NO_ORACLE_FOUND");
}
}
pragma abicoder v2;
interface IOracleRegistry {
struct Oracle {
uint oracleType;
address oracleAddress;
}
function WETH ( ) external view returns ( address );
function getKeydonixOracleTypes ( ) external view returns ( uint256[] memory );
function getOracles ( ) external view returns ( Oracle[] memory foundOracles );
function keydonixOracleTypes ( uint256 ) external view returns ( uint256 );
function maxOracleType ( ) external view returns ( uint256 );
function oracleByAsset ( address asset ) external view returns ( address );
function oracleByType ( uint256 ) external view returns ( address );
function oracleTypeByAsset ( address ) external view returns ( uint256 );
function oracleTypeByOracle ( address ) external view returns ( uint256 );
function setKeydonixOracleTypes ( uint256[] memory _keydonixOracleTypes ) external;
function setOracle ( uint256 oracleType, address oracle ) external;
function setOracleTypeForAsset ( address asset, uint256 oracleType ) external;
function setOracleTypeForAssets ( address[] memory assets, uint256 oracleType ) external;
function unsetOracle ( uint256 oracleType ) external;
function unsetOracleForAsset ( address asset ) external;
function unsetOracleForAssets ( address[] memory assets ) external;
function vaultParameters ( ) external view returns ( address );
}
pragma abicoder v2;
interface IKeydonixOracleUsd {
struct ProofDataStruct {
bytes block;
bytes accountProofNodesRlp;
bytes reserveAndTimestampProofNodesRlp;
bytes priceAccumulatorProofNodesRlp;
}
// returns Q112-encoded value
function assetToUsd(address asset, uint amount, ProofDataStruct calldata proofData) external view returns (uint);
}
interface IToken {
function decimals() external view returns (uint8);
function symbol() external view returns (string memory);
function balanceOf(address) external view returns (uint);
}
interface IVault {
function DENOMINATOR_1E2 ( ) external view returns ( uint256 );
function DENOMINATOR_1E5 ( ) external view returns ( uint256 );
function borrow ( address asset, address user, uint256 amount ) external returns ( uint256 );
function calculateFee ( address asset, address user, uint256 amount ) external view returns ( uint256 );
function changeOracleType ( address asset, address user, uint256 newOracleType ) external;
function chargeFee ( address asset, address user, uint256 amount ) external;
function col ( ) external view returns ( address );
function colToken ( address, address ) external view returns ( uint256 );
function collaterals ( address, address ) external view returns ( uint256 );
function debts ( address, address ) external view returns ( uint256 );
function depositCol ( address asset, address user, uint256 amount ) external;
function depositEth ( address user ) external payable;
function depositMain ( address asset, address user, uint256 amount ) external;
function destroy ( address asset, address user ) external;
function getTotalDebt ( address asset, address user ) external view returns ( uint256 );
function lastUpdate ( address, address ) external view returns ( uint256 );
function liquidate ( address asset, address positionOwner, uint256 mainAssetToLiquidator, uint256 colToLiquidator, uint256 mainAssetToPositionOwner, uint256 colToPositionOwner, uint256 repayment, uint256 penalty, address liquidator ) external;
function liquidationBlock ( address, address ) external view returns ( uint256 );
function liquidationFee ( address, address ) external view returns ( uint256 );
function liquidationPrice ( address, address ) external view returns ( uint256 );
function oracleType ( address, address ) external view returns ( uint256 );
function repay ( address asset, address user, uint256 amount ) external returns ( uint256 );
function spawn ( address asset, address user, uint256 _oracleType ) external;
function stabilityFee ( address, address ) external view returns ( uint256 );
function tokenDebts ( address ) external view returns ( uint256 );
function triggerLiquidation ( address asset, address positionOwner, uint256 initialPrice ) external;
function update ( address asset, address user ) external;
function usdp ( ) external view returns ( address );
function vaultParameters ( ) external view returns ( address );
function weth ( ) external view returns ( address payable );
function withdrawCol ( address asset, address user, uint256 amount ) external;
function withdrawEth ( address user, uint256 amount ) external;
function withdrawMain ( address asset, address user, uint256 amount ) external;
}
pragma experimental ABIEncoderV2;
interface ICDPRegistry {
struct CDP {
address asset;
address owner;
}
function batchCheckpoint ( address[] calldata assets, address[] calldata owners ) external;
function batchCheckpointForAsset ( address asset, address[] calldata owners ) external;
function checkpoint ( address asset, address owner ) external;
function cr ( ) external view returns ( address );
function getAllCdps ( ) external view returns ( CDP[] memory r );
function getCdpsByCollateral ( address asset ) external view returns ( CDP[] memory cdps );
function getCdpsByOwner ( address owner ) external view returns ( CDP[] memory r );
function getCdpsCount ( ) external view returns ( uint256 totalCdpCount );
function getCdpsCountForCollateral ( address asset ) external view returns ( uint256 );
function isAlive ( address asset, address owner ) external view returns ( bool );
function isListed ( address asset, address owner ) external view returns ( bool );
function vault ( ) external view returns ( address );
}
interface IVaultManagerParameters {
function devaluationPeriod ( address ) external view returns ( uint256 );
function initialCollateralRatio ( address ) external view returns ( uint256 );
function liquidationDiscount ( address ) external view returns ( uint256 );
function liquidationRatio ( address ) external view returns ( uint256 );
function maxColPercent ( address ) external view returns ( uint256 );
function minColPercent ( address ) external view returns ( uint256 );
function setColPartRange ( address asset, uint256 min, uint256 max ) external;
function setCollateral (
address asset,
uint256 stabilityFeeValue,
uint256 liquidationFeeValue,
uint256 initialCollateralRatioValue,
uint256 liquidationRatioValue,
uint256 liquidationDiscountValue,
uint256 devaluationPeriodValue,
uint256 usdpLimit,
uint256[] calldata oracles,
uint256 minColP,
uint256 maxColP
) external;
function setDevaluationPeriod ( address asset, uint256 newValue ) external;
function setInitialCollateralRatio ( address asset, uint256 newValue ) external;
function setLiquidationDiscount ( address asset, uint256 newValue ) external;
function setLiquidationRatio ( address asset, uint256 newValue ) external;
function vaultParameters ( ) external view returns ( address );
}
interface IVaultParameters {
function canModifyVault ( address ) external view returns ( bool );
function foundation ( ) external view returns ( address );
function isManager ( address ) external view returns ( bool );
function isOracleTypeEnabled ( uint256, address ) external view returns ( bool );
function liquidationFee ( address ) external view returns ( uint256 );
function setCollateral ( address asset, uint256 stabilityFeeValue, uint256 liquidationFeeValue, uint256 usdpLimit, uint256[] calldata oracles ) external;
function setFoundation ( address newFoundation ) external;
function setLiquidationFee ( address asset, uint256 newValue ) external;
function setManager ( address who, bool permit ) external;
function setOracleType ( uint256 _type, address asset, bool enabled ) external;
function setStabilityFee ( address asset, uint256 newValue ) external;
function setTokenDebtLimit ( address asset, uint256 limit ) external;
function setVaultAccess ( address who, bool permit ) external;
function stabilityFee ( address ) external view returns ( uint256 );
function tokenDebtLimit ( address ) external view returns ( uint256 );
function vault ( ) external view returns ( address );
function vaultParameters ( ) external view returns ( address );
}
// SPDX-License-Identifier: MIT
pragma solidity 0.7.6;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor () {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and make it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
}
// SPDX-License-Identifier: bsl-1.1
/*
Copyright 2020 Unit Protocol: Artem Zakharov ([email protected]).
*/
pragma solidity 0.7.6;
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
/**
* @dev Multiplies two numbers, throws on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {
if (a == 0) {
return 0;
}
c = a * b;
assert(c / a == b);
return c;
}
/**
* @dev Integer division of two numbers, truncating the quotient.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0, "SafeMath: division by zero");
return a / b;
}
/**
* @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
c = a + b;
assert(c >= a);
return c;
}
}