/*
____ __ __ __ _
/ __/__ __ ___ / /_ / / ___ / /_ (_)__ __
_\ \ / // // _ \/ __// _ \/ -_)/ __// / \ \ /
/___/ \_, //_//_/\__//_//_/\__/ \__//_/ /_\_\
/___/
* Synthetix: EtherCollateralsUSD.sol
*
* Latest source (may be newer): https://github.com/Synthetixio/synthetix/blob/master/contracts/EtherCollateralsUSD.sol
* Docs: https://docs.synthetix.io/contracts/EtherCollateralsUSD
*
* Contract Dependencies:
* - IAddressResolver
* - IEtherCollateralsUSD
* - MixinResolver
* - Owned
* - Pausable
* - ReentrancyGuard
* Libraries:
* - SafeDecimalMath
* - SafeMath
*
* MIT License
* ===========
*
* Copyright (c) 2020 Synthetix
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
*/
pragma solidity ^0.5.16;
// https://docs.synthetix.io/contracts/Owned
contract Owned {
address public owner;
address public nominatedOwner;
constructor(address _owner) public {
require(_owner != address(0), "Owner address cannot be 0");
owner = _owner;
emit OwnerChanged(address(0), _owner);
}
function nominateNewOwner(address _owner) external onlyOwner {
nominatedOwner = _owner;
emit OwnerNominated(_owner);
}
function acceptOwnership() external {
require(msg.sender == nominatedOwner, "You must be nominated before you can accept ownership");
emit OwnerChanged(owner, nominatedOwner);
owner = nominatedOwner;
nominatedOwner = address(0);
}
modifier onlyOwner {
_onlyOwner();
_;
}
function _onlyOwner() private view {
require(msg.sender == owner, "Only the contract owner may perform this action");
}
event OwnerNominated(address newOwner);
event OwnerChanged(address oldOwner, address newOwner);
}
// Inheritance
// https://docs.synthetix.io/contracts/Pausable
contract Pausable is Owned {
uint public lastPauseTime;
bool public paused;
constructor() internal {
// This contract is abstract, and thus cannot be instantiated directly
require(owner != address(0), "Owner must be set");
// Paused will be false, and lastPauseTime will be 0 upon initialisation
}
/**
* @notice Change the paused state of the contract
* @dev Only the contract owner may call this.
*/
function setPaused(bool _paused) external onlyOwner {
// Ensure we're actually changing the state before we do anything
if (_paused == paused) {
return;
}
// Set our paused state.
paused = _paused;
// If applicable, set the last pause time.
if (paused) {
lastPauseTime = now;
}
// Let everyone know that our pause state has changed.
emit PauseChanged(paused);
}
event PauseChanged(bool isPaused);
modifier notPaused {
require(!paused, "This action cannot be performed while the contract is paused");
_;
}
}
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the `nonReentrant` modifier
* available, which can be aplied 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.
*/
contract ReentrancyGuard {
/// @dev counter to allow mutex lock with only one SSTORE operation
uint256 private _guardCounter;
constructor () internal {
// The counter starts at one to prevent changing it from zero to a non-zero
// value, which is a more expensive operation.
_guardCounter = 1;
}
/**
* @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() {
_guardCounter += 1;
uint256 localCounter = _guardCounter;
_;
require(localCounter == _guardCounter, "ReentrancyGuard: reentrant call");
}
}
interface IAddressResolver {
function getAddress(bytes32 name) external view returns (address);
function getSynth(bytes32 key) external view returns (address);
function requireAndGetAddress(bytes32 name, string calldata reason) external view returns (address);
}
interface ISynth {
// Views
function currencyKey() external view returns (bytes32);
function transferableSynths(address account) external view returns (uint);
// Mutative functions
function transferAndSettle(address to, uint value) external returns (bool);
function transferFromAndSettle(
address from,
address to,
uint value
) external returns (bool);
// Restricted: used internally to Synthetix
function burn(address account, uint amount) external;
function issue(address account, uint amount) external;
}
interface IIssuer {
// Views
function anySynthOrSNXRateIsInvalid() external view returns (bool anyRateInvalid);
function availableCurrencyKeys() external view returns (bytes32[] memory);
function availableSynthCount() external view returns (uint);
function availableSynths(uint index) external view returns (ISynth);
function canBurnSynths(address account) external view returns (bool);
function collateral(address account) external view returns (uint);
function collateralisationRatio(address issuer) external view returns (uint);
function collateralisationRatioAndAnyRatesInvalid(address _issuer)
external
view
returns (uint cratio, bool anyRateIsInvalid);
function debtBalanceOf(address issuer, bytes32 currencyKey) external view returns (uint debtBalance);
function issuanceRatio() external view returns (uint);
function lastIssueEvent(address account) external view returns (uint);
function maxIssuableSynths(address issuer) external view returns (uint maxIssuable);
function minimumStakeTime() external view returns (uint);
function remainingIssuableSynths(address issuer)
external
view
returns (
uint maxIssuable,
uint alreadyIssued,
uint totalSystemDebt
);
function synths(bytes32 currencyKey) external view returns (ISynth);
function synthsByAddress(address synthAddress) external view returns (bytes32);
function totalIssuedSynths(bytes32 currencyKey, bool excludeEtherCollateral) external view returns (uint);
function transferableSynthetixAndAnyRateIsInvalid(address account, uint balance)
external
view
returns (uint transferable, bool anyRateIsInvalid);
// Restricted: used internally to Synthetix
function issueSynths(address from, uint amount) external;
function issueSynthsOnBehalf(
address issueFor,
address from,
uint amount
) external;
function issueMaxSynths(address from) external;
function issueMaxSynthsOnBehalf(address issueFor, address from) external;
function burnSynths(address from, uint amount) external;
function burnSynthsOnBehalf(
address burnForAddress,
address from,
uint amount
) external;
function burnSynthsToTarget(address from) external;
function burnSynthsToTargetOnBehalf(address burnForAddress, address from) external;
function liquidateDelinquentAccount(
address account,
uint susdAmount,
address liquidator
) external returns (uint totalRedeemed, uint amountToLiquidate);
}
// Inheritance
// https://docs.synthetix.io/contracts/AddressResolver
contract AddressResolver is Owned, IAddressResolver {
mapping(bytes32 => address) public repository;
constructor(address _owner) public Owned(_owner) {}
/* ========== MUTATIVE FUNCTIONS ========== */
function importAddresses(bytes32[] calldata names, address[] calldata destinations) external onlyOwner {
require(names.length == destinations.length, "Input lengths must match");
for (uint i = 0; i < names.length; i++) {
repository[names[i]] = destinations[i];
}
}
/* ========== VIEWS ========== */
function getAddress(bytes32 name) external view returns (address) {
return repository[name];
}
function requireAndGetAddress(bytes32 name, string calldata reason) external view returns (address) {
address _foundAddress = repository[name];
require(_foundAddress != address(0), reason);
return _foundAddress;
}
function getSynth(bytes32 key) external view returns (address) {
IIssuer issuer = IIssuer(repository["Issuer"]);
require(address(issuer) != address(0), "Cannot find Issuer address");
return address(issuer.synths(key));
}
}
// Inheritance
// Internal references
// https://docs.synthetix.io/contracts/MixinResolver
contract MixinResolver is Owned {
AddressResolver public resolver;
mapping(bytes32 => address) private addressCache;
bytes32[] public resolverAddressesRequired;
uint public constant MAX_ADDRESSES_FROM_RESOLVER = 24;
constructor(address _resolver, bytes32[MAX_ADDRESSES_FROM_RESOLVER] memory _addressesToCache) internal {
// This contract is abstract, and thus cannot be instantiated directly
require(owner != address(0), "Owner must be set");
for (uint i = 0; i < _addressesToCache.length; i++) {
if (_addressesToCache[i] != bytes32(0)) {
resolverAddressesRequired.push(_addressesToCache[i]);
} else {
// End early once an empty item is found - assumes there are no empty slots in
// _addressesToCache
break;
}
}
resolver = AddressResolver(_resolver);
// Do not sync the cache as addresses may not be in the resolver yet
}
/* ========== SETTERS ========== */
function setResolverAndSyncCache(AddressResolver _resolver) external onlyOwner {
resolver = _resolver;
for (uint i = 0; i < resolverAddressesRequired.length; i++) {
bytes32 name = resolverAddressesRequired[i];
// Note: can only be invoked once the resolver has all the targets needed added
addressCache[name] = resolver.requireAndGetAddress(name, "Resolver missing target");
}
}
/* ========== VIEWS ========== */
function requireAndGetAddress(bytes32 name, string memory reason) internal view returns (address) {
address _foundAddress = addressCache[name];
require(_foundAddress != address(0), reason);
return _foundAddress;
}
// Note: this could be made external in a utility contract if addressCache was made public
// (used for deployment)
function isResolverCached(AddressResolver _resolver) external view returns (bool) {
if (resolver != _resolver) {
return false;
}
// otherwise, check everything
for (uint i = 0; i < resolverAddressesRequired.length; i++) {
bytes32 name = resolverAddressesRequired[i];
// false if our cache is invalid or if the resolver doesn't have the required address
if (resolver.getAddress(name) != addressCache[name] || addressCache[name] == address(0)) {
return false;
}
}
return true;
}
// Note: can be made external into a utility contract (used for deployment)
function getResolverAddressesRequired()
external
view
returns (bytes32[MAX_ADDRESSES_FROM_RESOLVER] memory addressesRequired)
{
for (uint i = 0; i < resolverAddressesRequired.length; i++) {
addressesRequired[i] = resolverAddressesRequired[i];
}
}
/* ========== INTERNAL FUNCTIONS ========== */
function appendToAddressCache(bytes32 name) internal {
resolverAddressesRequired.push(name);
require(resolverAddressesRequired.length < MAX_ADDRESSES_FROM_RESOLVER, "Max resolver cache size met");
// Because this is designed to be called internally in constructors, we don't
// check the address exists already in the resolver
addressCache[name] = resolver.getAddress(name);
}
}
interface IEtherCollateralsUSD {
// Views
function totalIssuedSynths() external view returns (uint256);
function totalLoansCreated() external view returns (uint256);
function totalOpenLoanCount() external view returns (uint256);
// Mutative functions
function openLoan(uint256 _loanAmount) external payable returns (uint256 loanID);
function closeLoan(uint256 loanID) external;
function liquidateUnclosedLoan(address _loanCreatorsAddress, uint256 _loanID) external;
function depositCollateral(address account, uint256 loanID) external payable;
function withdrawCollateral(uint256 loanID, uint256 withdrawAmount) external;
function repayLoan(
address _loanCreatorsAddress,
uint256 _loanID,
uint256 _repayAmount
) external;
}
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/
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-solidity/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, "SafeMath: division by zero");
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0, "SafeMath: modulo by zero");
return a % b;
}
}
// Libraries
// https://docs.synthetix.io/contracts/SafeDecimalMath
library SafeDecimalMath {
using SafeMath for uint;
/* Number of decimal places in the representations. */
uint8 public constant decimals = 18;
uint8 public constant highPrecisionDecimals = 27;
/* The number representing 1.0. */
uint public constant UNIT = 10**uint(decimals);
/* The number representing 1.0 for higher fidelity numbers. */
uint public constant PRECISE_UNIT = 10**uint(highPrecisionDecimals);
uint private constant UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR = 10**uint(highPrecisionDecimals - decimals);
/**
* @return Provides an interface to UNIT.
*/
function unit() external pure returns (uint) {
return UNIT;
}
/**
* @return Provides an interface to PRECISE_UNIT.
*/
function preciseUnit() external pure returns (uint) {
return PRECISE_UNIT;
}
/**
* @return The result of multiplying x and y, interpreting the operands as fixed-point
* decimals.
*
* @dev A unit factor is divided out after the product of x and y is evaluated,
* so that product must be less than 2**256. As this is an integer division,
* the internal division always rounds down. This helps save on gas. Rounding
* is more expensive on gas.
*/
function multiplyDecimal(uint x, uint y) internal pure returns (uint) {
/* Divide by UNIT to remove the extra factor introduced by the product. */
return x.mul(y) / UNIT;
}
/**
* @return The result of safely multiplying x and y, interpreting the operands
* as fixed-point decimals of the specified precision unit.
*
* @dev The operands should be in the form of a the specified unit factor which will be
* divided out after the product of x and y is evaluated, so that product must be
* less than 2**256.
*
* Unlike multiplyDecimal, this function rounds the result to the nearest increment.
* Rounding is useful when you need to retain fidelity for small decimal numbers
* (eg. small fractions or percentages).
*/
function _multiplyDecimalRound(
uint x,
uint y,
uint precisionUnit
) private pure returns (uint) {
/* Divide by UNIT to remove the extra factor introduced by the product. */
uint quotientTimesTen = x.mul(y) / (precisionUnit / 10);
if (quotientTimesTen % 10 >= 5) {
quotientTimesTen += 10;
}
return quotientTimesTen / 10;
}
/**
* @return The result of safely multiplying x and y, interpreting the operands
* as fixed-point decimals of a precise unit.
*
* @dev The operands should be in the precise unit factor which will be
* divided out after the product of x and y is evaluated, so that product must be
* less than 2**256.
*
* Unlike multiplyDecimal, this function rounds the result to the nearest increment.
* Rounding is useful when you need to retain fidelity for small decimal numbers
* (eg. small fractions or percentages).
*/
function multiplyDecimalRoundPrecise(uint x, uint y) internal pure returns (uint) {
return _multiplyDecimalRound(x, y, PRECISE_UNIT);
}
/**
* @return The result of safely multiplying x and y, interpreting the operands
* as fixed-point decimals of a standard unit.
*
* @dev The operands should be in the standard unit factor which will be
* divided out after the product of x and y is evaluated, so that product must be
* less than 2**256.
*
* Unlike multiplyDecimal, this function rounds the result to the nearest increment.
* Rounding is useful when you need to retain fidelity for small decimal numbers
* (eg. small fractions or percentages).
*/
function multiplyDecimalRound(uint x, uint y) internal pure returns (uint) {
return _multiplyDecimalRound(x, y, UNIT);
}
/**
* @return The result of safely dividing x and y. The return value is a high
* precision decimal.
*
* @dev y is divided after the product of x and the standard precision unit
* is evaluated, so the product of x and UNIT must be less than 2**256. As
* this is an integer division, the result is always rounded down.
* This helps save on gas. Rounding is more expensive on gas.
*/
function divideDecimal(uint x, uint y) internal pure returns (uint) {
/* Reintroduce the UNIT factor that will be divided out by y. */
return x.mul(UNIT).div(y);
}
/**
* @return The result of safely dividing x and y. The return value is as a rounded
* decimal in the precision unit specified in the parameter.
*
* @dev y is divided after the product of x and the specified precision unit
* is evaluated, so the product of x and the specified precision unit must
* be less than 2**256. The result is rounded to the nearest increment.
*/
function _divideDecimalRound(
uint x,
uint y,
uint precisionUnit
) private pure returns (uint) {
uint resultTimesTen = x.mul(precisionUnit * 10).div(y);
if (resultTimesTen % 10 >= 5) {
resultTimesTen += 10;
}
return resultTimesTen / 10;
}
/**
* @return The result of safely dividing x and y. The return value is as a rounded
* standard precision decimal.
*
* @dev y is divided after the product of x and the standard precision unit
* is evaluated, so the product of x and the standard precision unit must
* be less than 2**256. The result is rounded to the nearest increment.
*/
function divideDecimalRound(uint x, uint y) internal pure returns (uint) {
return _divideDecimalRound(x, y, UNIT);
}
/**
* @return The result of safely dividing x and y. The return value is as a rounded
* high precision decimal.
*
* @dev y is divided after the product of x and the high precision unit
* is evaluated, so the product of x and the high precision unit must
* be less than 2**256. The result is rounded to the nearest increment.
*/
function divideDecimalRoundPrecise(uint x, uint y) internal pure returns (uint) {
return _divideDecimalRound(x, y, PRECISE_UNIT);
}
/**
* @dev Convert a standard decimal representation to a high precision one.
*/
function decimalToPreciseDecimal(uint i) internal pure returns (uint) {
return i.mul(UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR);
}
/**
* @dev Convert a high precision decimal to a standard decimal representation.
*/
function preciseDecimalToDecimal(uint i) internal pure returns (uint) {
uint quotientTimesTen = i / (UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR / 10);
if (quotientTimesTen % 10 >= 5) {
quotientTimesTen += 10;
}
return quotientTimesTen / 10;
}
}
interface ISystemStatus {
struct Status {
bool canSuspend;
bool canResume;
}
struct Suspension {
bool suspended;
// reason is an integer code,
// 0 => no reason, 1 => upgrading, 2+ => defined by system usage
uint248 reason;
}
// Views
function accessControl(bytes32 section, address account) external view returns (bool canSuspend, bool canResume);
function requireSystemActive() external view;
function requireIssuanceActive() external view;
function requireExchangeActive() external view;
function requireSynthActive(bytes32 currencyKey) external view;
function requireSynthsActive(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) external view;
function synthSuspension(bytes32 currencyKey) external view returns (bool suspended, uint248 reason);
// Restricted functions
function suspendSynth(bytes32 currencyKey, uint256 reason) external;
function updateAccessControl(
bytes32 section,
address account,
bool canSuspend,
bool canResume
) external;
}
interface IFeePool {
// Views
// solhint-disable-next-line func-name-mixedcase
function FEE_ADDRESS() external view returns (address);
function feesAvailable(address account) external view returns (uint, uint);
function feePeriodDuration() external view returns (uint);
function isFeesClaimable(address account) external view returns (bool);
function targetThreshold() external view returns (uint);
function totalFeesAvailable() external view returns (uint);
function totalRewardsAvailable() external view returns (uint);
// Mutative Functions
function claimFees() external returns (bool);
function claimOnBehalf(address claimingForAddress) external returns (bool);
function closeCurrentFeePeriod() external;
// Restricted: used internally to Synthetix
function appendAccountIssuanceRecord(
address account,
uint lockedAmount,
uint debtEntryIndex
) external;
function recordFeePaid(uint sUSDAmount) external;
function setRewardsToDistribute(uint amount) external;
}
interface IERC20 {
// ERC20 Optional Views
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
// Views
function totalSupply() external view returns (uint);
function balanceOf(address owner) external view returns (uint);
function allowance(address owner, address spender) external view returns (uint);
// Mutative functions
function transfer(address to, uint value) external returns (bool);
function approve(address spender, uint value) external returns (bool);
function transferFrom(
address from,
address to,
uint value
) external returns (bool);
// Events
event Transfer(address indexed from, address indexed to, uint value);
event Approval(address indexed owner, address indexed spender, uint value);
}
// https://docs.synthetix.io/contracts/source/interfaces/IExchangeRates
interface IExchangeRates {
// Structs
struct RateAndUpdatedTime {
uint216 rate;
uint40 time;
}
struct InversePricing {
uint entryPoint;
uint upperLimit;
uint lowerLimit;
bool frozenAtUpperLimit;
bool frozenAtLowerLimit;
}
// Views
function aggregators(bytes32 currencyKey) external view returns (address);
function aggregatorWarningFlags() external view returns (address);
function anyRateIsInvalid(bytes32[] calldata currencyKeys) external view returns (bool);
function canFreezeRate(bytes32 currencyKey) external view returns (bool);
function currentRoundForRate(bytes32 currencyKey) external view returns (uint);
function currenciesUsingAggregator(address aggregator) external view returns (bytes32[] memory);
function effectiveValue(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey
) external view returns (uint value);
function effectiveValueAndRates(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey
)
external
view
returns (
uint value,
uint sourceRate,
uint destinationRate
);
function effectiveValueAtRound(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
uint roundIdForSrc,
uint roundIdForDest
) external view returns (uint value);
function getCurrentRoundId(bytes32 currencyKey) external view returns (uint);
function getLastRoundIdBeforeElapsedSecs(
bytes32 currencyKey,
uint startingRoundId,
uint startingTimestamp,
uint timediff
) external view returns (uint);
function inversePricing(bytes32 currencyKey)
external
view
returns (
uint entryPoint,
uint upperLimit,
uint lowerLimit,
bool frozenAtUpperLimit,
bool frozenAtLowerLimit
);
function lastRateUpdateTimes(bytes32 currencyKey) external view returns (uint256);
function oracle() external view returns (address);
function rateAndTimestampAtRound(bytes32 currencyKey, uint roundId) external view returns (uint rate, uint time);
function rateAndUpdatedTime(bytes32 currencyKey) external view returns (uint rate, uint time);
function rateForCurrency(bytes32 currencyKey) external view returns (uint);
function rateIsFlagged(bytes32 currencyKey) external view returns (bool);
function rateIsFrozen(bytes32 currencyKey) external view returns (bool);
function rateIsInvalid(bytes32 currencyKey) external view returns (bool);
function rateIsStale(bytes32 currencyKey) external view returns (bool);
function rateStalePeriod() external view returns (uint);
function ratesAndUpdatedTimeForCurrencyLastNRounds(bytes32 currencyKey, uint numRounds)
external
view
returns (uint[] memory rates, uint[] memory times);
function ratesAndInvalidForCurrencies(bytes32[] calldata currencyKeys)
external
view
returns (uint[] memory rates, bool anyRateInvalid);
function ratesForCurrencies(bytes32[] calldata currencyKeys) external view returns (uint[] memory);
// Mutative functions
function freezeRate(bytes32 currencyKey) external;
}
// Inheritance
// Libraries
// Internal references
// ETH Collateral v0.3 (sUSD)
// https://docs.synthetix.io/contracts/EtherCollateralsUSD
contract EtherCollateralsUSD is Owned, Pausable, ReentrancyGuard, MixinResolver, IEtherCollateralsUSD {
using SafeMath for uint256;
using SafeDecimalMath for uint256;
bytes32 internal constant ETH = "ETH";
// ========== CONSTANTS ==========
uint256 internal constant ONE_THOUSAND = 1e18 * 1000;
uint256 internal constant ONE_HUNDRED = 1e18 * 100;
uint256 internal constant SECONDS_IN_A_YEAR = 31536000; // Common Year
// Where fees are pooled in sUSD.
address internal constant FEE_ADDRESS = 0xfeEFEEfeefEeFeefEEFEEfEeFeefEEFeeFEEFEeF;
uint256 internal constant ACCOUNT_LOAN_LIMIT_CAP = 1000;
bytes32 private constant sUSD = "sUSD";
bytes32 public constant COLLATERAL = "ETH";
// ========== SETTER STATE VARIABLES ==========
// The ratio of Collateral to synths issued
uint256 public collateralizationRatio = SafeDecimalMath.unit() * 150;
// If updated, all outstanding loans will pay this interest rate in on closure of the loan. Default 5%
uint256 public interestRate = (5 * SafeDecimalMath.unit()) / 100;
uint256 public interestPerSecond = interestRate.div(SECONDS_IN_A_YEAR);
// Minting fee for issuing the synths. Default 50 bips.
uint256 public issueFeeRate = (5 * SafeDecimalMath.unit()) / 1000;
// Maximum amount of sUSD that can be issued by the EtherCollateral contract. Default 10MM
uint256 public issueLimit = SafeDecimalMath.unit() * 10000000;
// Minimum amount of ETH to create loan preventing griefing and gas consumption. Min 1ETH
uint256 public minLoanCollateralSize = SafeDecimalMath.unit() * 1;
// Maximum number of loans an account can create
uint256 public accountLoanLimit = 50;
// If true then any wallet addres can close a loan not just the loan creator.
bool public loanLiquidationOpen = false;
// Time when remaining loans can be liquidated
uint256 public liquidationDeadline;
// Liquidation ratio when loans can be liquidated
uint256 public liquidationRatio = (150 * SafeDecimalMath.unit()) / 100; // 1.5 ratio
// Liquidation penalty when loans are liquidated. default 10%
uint256 public liquidationPenalty = SafeDecimalMath.unit() / 10;
// ========== STATE VARIABLES ==========
// The total number of synths issued by the collateral in this contract
uint256 public totalIssuedSynths;
// Total number of loans ever created
uint256 public totalLoansCreated;
// Total number of open loans
uint256 public totalOpenLoanCount;
// Synth loan storage struct
struct SynthLoanStruct {
// Acccount that created the loan
address payable account;
// Amount (in collateral token ) that they deposited
uint256 collateralAmount;
// Amount (in synths) that they issued to borrow
uint256 loanAmount;
// Minting Fee
uint256 mintingFee;
// When the loan was created
uint256 timeCreated;
// ID for the loan
uint256 loanID;
// When the loan was paidback (closed)
uint256 timeClosed;
// Applicable Interest rate
uint256 loanInterestRate;
// interest amounts accrued
uint256 accruedInterest;
// last timestamp interest amounts accrued
uint40 lastInterestAccrued;
}
// Users Loans by address
mapping(address => SynthLoanStruct[]) public accountsSynthLoans;
// Account Open Loan Counter
mapping(address => uint256) public accountOpenLoanCounter;
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus";
bytes32 private constant CONTRACT_SYNTHSUSD = "SynthsUSD";
bytes32 private constant CONTRACT_EXRATES = "ExchangeRates";
bytes32 private constant CONTRACT_FEEPOOL = "FeePool";
bytes32[24] private addressesToCache = [CONTRACT_SYSTEMSTATUS, CONTRACT_SYNTHSUSD, CONTRACT_EXRATES, CONTRACT_FEEPOOL];
// ========== CONSTRUCTOR ==========
constructor(address _owner, address _resolver)
public
Owned(_owner)
Pausable()
MixinResolver(_resolver, addressesToCache)
{
liquidationDeadline = block.timestamp + 92 days; // Time before loans can be open for liquidation to end the trial contract
}
// ========== SETTERS ==========
function setCollateralizationRatio(uint256 ratio) external onlyOwner {
require(ratio <= ONE_THOUSAND, "Too high");
require(ratio >= ONE_HUNDRED, "Too low");
collateralizationRatio = ratio;
emit CollateralizationRatioUpdated(ratio);
}
function setInterestRate(uint256 _interestRate) external onlyOwner {
require(_interestRate > SECONDS_IN_A_YEAR, "Interest rate cannot be less that the SECONDS_IN_A_YEAR");
require(_interestRate <= SafeDecimalMath.unit(), "Interest cannot be more than 100% APR");
interestRate = _interestRate;
interestPerSecond = _interestRate.div(SECONDS_IN_A_YEAR);
emit InterestRateUpdated(interestRate);
}
function setIssueFeeRate(uint256 _issueFeeRate) external onlyOwner {
issueFeeRate = _issueFeeRate;
emit IssueFeeRateUpdated(issueFeeRate);
}
function setIssueLimit(uint256 _issueLimit) external onlyOwner {
issueLimit = _issueLimit;
emit IssueLimitUpdated(issueLimit);
}
function setMinLoanCollateralSize(uint256 _minLoanCollateralSize) external onlyOwner {
minLoanCollateralSize = _minLoanCollateralSize;
emit MinLoanCollateralSizeUpdated(minLoanCollateralSize);
}
function setAccountLoanLimit(uint256 _loanLimit) external onlyOwner {
require(_loanLimit < ACCOUNT_LOAN_LIMIT_CAP, "Owner cannot set higher than ACCOUNT_LOAN_LIMIT_CAP");
accountLoanLimit = _loanLimit;
emit AccountLoanLimitUpdated(accountLoanLimit);
}
function setLoanLiquidationOpen(bool _loanLiquidationOpen) external onlyOwner {
require(block.timestamp > liquidationDeadline, "Before liquidation deadline");
loanLiquidationOpen = _loanLiquidationOpen;
emit LoanLiquidationOpenUpdated(loanLiquidationOpen);
}
function setLiquidationRatio(uint256 _liquidationRatio) external onlyOwner {
require(_liquidationRatio > SafeDecimalMath.unit(), "Ratio less than 100%");
liquidationRatio = _liquidationRatio;
emit LiquidationRatioUpdated(liquidationRatio);
}
// ========== PUBLIC VIEWS ==========
function getContractInfo()
external
view
returns (
uint256 _collateralizationRatio,
uint256 _issuanceRatio,
uint256 _interestRate,
uint256 _interestPerSecond,
uint256 _issueFeeRate,
uint256 _issueLimit,
uint256 _minLoanCollateralSize,
uint256 _totalIssuedSynths,
uint256 _totalLoansCreated,
uint256 _totalOpenLoanCount,
uint256 _ethBalance,
uint256 _liquidationDeadline,
bool _loanLiquidationOpen
)
{
_collateralizationRatio = collateralizationRatio;
_issuanceRatio = issuanceRatio();
_interestRate = interestRate;
_interestPerSecond = interestPerSecond;
_issueFeeRate = issueFeeRate;
_issueLimit = issueLimit;
_minLoanCollateralSize = minLoanCollateralSize;
_totalIssuedSynths = totalIssuedSynths;
_totalLoansCreated = totalLoansCreated;
_totalOpenLoanCount = totalOpenLoanCount;
_ethBalance = address(this).balance;
_liquidationDeadline = liquidationDeadline;
_loanLiquidationOpen = loanLiquidationOpen;
}
// returns value of 100 / collateralizationRatio.
// e.g. 100/150 = 0.6666666667
function issuanceRatio() public view returns (uint256) {
// this rounds so you get slightly more rather than slightly less
return ONE_HUNDRED.divideDecimalRound(collateralizationRatio);
}
function loanAmountFromCollateral(uint256 collateralAmount) public view returns (uint256) {
// a fraction more is issued due to rounding
return collateralAmount.multiplyDecimal(issuanceRatio()).multiplyDecimal(exchangeRates().rateForCurrency(ETH));
}
function collateralAmountForLoan(uint256 loanAmount) external view returns (uint256) {
return
loanAmount
.multiplyDecimal(collateralizationRatio.divideDecimalRound(exchangeRates().rateForCurrency(ETH)))
.divideDecimalRound(ONE_HUNDRED);
}
// compound accrued interest with remaining loanAmount * (now - lastTimestampInterestPaid)
function currentInterestOnLoan(address _account, uint256 _loanID) external view returns (uint256) {
// Get the loan from storage
SynthLoanStruct memory synthLoan = _getLoanFromStorage(_account, _loanID);
uint256 currentInterest = accruedInterestOnLoan(
synthLoan.loanAmount.add(synthLoan.accruedInterest),
_timeSinceInterestAccrual(synthLoan)
);
return synthLoan.accruedInterest.add(currentInterest);
}
function accruedInterestOnLoan(uint256 _loanAmount, uint256 _seconds) public view returns (uint256 interestAmount) {
// Simple interest calculated per second
// Interest = Principal * rate * time
interestAmount = _loanAmount.multiplyDecimalRound(interestPerSecond.mul(_seconds));
}
function totalFeesOnLoan(address _account, uint256 _loanID)
external
view
returns (uint256 interestAmount, uint256 mintingFee)
{
SynthLoanStruct memory synthLoan = _getLoanFromStorage(_account, _loanID);
uint256 loanAmountWithAccruedInterest = synthLoan.loanAmount.add(synthLoan.accruedInterest);
interestAmount = synthLoan.accruedInterest.add(
accruedInterestOnLoan(loanAmountWithAccruedInterest, _timeSinceInterestAccrual(synthLoan))
);
mintingFee = synthLoan.mintingFee;
}
function getMintingFee(address _account, uint256 _loanID) external view returns (uint256) {
// Get the loan from storage
SynthLoanStruct memory synthLoan = _getLoanFromStorage(_account, _loanID);
return synthLoan.mintingFee;
}
/**
* r = target issuance ratio
* D = debt balance
* V = Collateral
* P = liquidation penalty
* Calculates amount of synths = (D - V * r) / (1 - (1 + P) * r)
*/
function calculateAmountToLiquidate(uint debtBalance, uint collateral) public view returns (uint) {
uint unit = SafeDecimalMath.unit();
uint ratio = liquidationRatio;
uint dividend = debtBalance.sub(collateral.divideDecimal(ratio));
uint divisor = unit.sub(unit.add(liquidationPenalty).divideDecimal(ratio));
return dividend.divideDecimal(divisor);
}
function openLoanIDsByAccount(address _account) external view returns (uint256[] memory) {
SynthLoanStruct[] memory synthLoans = accountsSynthLoans[_account];
uint256[] memory _openLoanIDs = new uint256[](synthLoans.length);
uint256 _counter = 0;
for (uint256 i = 0; i < synthLoans.length; i++) {
if (synthLoans[i].timeClosed == 0) {
_openLoanIDs[_counter] = synthLoans[i].loanID;
_counter++;
}
}
// Create the fixed size array to return
uint256[] memory _result = new uint256[](_counter);
// Copy loanIDs from dynamic array to fixed array
for (uint256 j = 0; j < _counter; j++) {
_result[j] = _openLoanIDs[j];
}
// Return an array with list of open Loan IDs
return _result;
}
function getLoan(address _account, uint256 _loanID)
external
view
returns (
address account,
uint256 collateralAmount,
uint256 loanAmount,
uint256 timeCreated,
uint256 loanID,
uint256 timeClosed,
uint256 accruedInterest,
uint256 totalFees
)
{
SynthLoanStruct memory synthLoan = _getLoanFromStorage(_account, _loanID);
account = synthLoan.account;
collateralAmount = synthLoan.collateralAmount;
loanAmount = synthLoan.loanAmount;
timeCreated = synthLoan.timeCreated;
loanID = synthLoan.loanID;
timeClosed = synthLoan.timeClosed;
accruedInterest = synthLoan.accruedInterest.add(
accruedInterestOnLoan(synthLoan.loanAmount.add(synthLoan.accruedInterest), _timeSinceInterestAccrual(synthLoan))
);
totalFees = accruedInterest.add(synthLoan.mintingFee);
}
function getLoanCollateralRatio(address _account, uint256 _loanID) external view returns (uint256 loanCollateralRatio) {
// Get the loan from storage
SynthLoanStruct memory synthLoan = _getLoanFromStorage(_account, _loanID);
(loanCollateralRatio, , ) = _loanCollateralRatio(synthLoan);
}
function _loanCollateralRatio(SynthLoanStruct memory _loan)
internal
view
returns (
uint256 loanCollateralRatio,
uint256 collateralValue,
uint256 interestAmount
)
{
// Any interest accrued prior is rolled up into loan amount
uint256 loanAmountWithAccruedInterest = _loan.loanAmount.add(_loan.accruedInterest);
interestAmount = accruedInterestOnLoan(loanAmountWithAccruedInterest, _timeSinceInterestAccrual(_loan));
collateralValue = _loan.collateralAmount.multiplyDecimal(exchangeRates().rateForCurrency(COLLATERAL));
loanCollateralRatio = collateralValue.divideDecimal(loanAmountWithAccruedInterest.add(interestAmount));
}
function timeSinceInterestAccrualOnLoan(address _account, uint256 _loanID) external view returns (uint256) {
// Get the loan from storage
SynthLoanStruct memory synthLoan = _getLoanFromStorage(_account, _loanID);
return _timeSinceInterestAccrual(synthLoan);
}
// ========== PUBLIC FUNCTIONS ==========
function openLoan(uint256 _loanAmount)
external
payable
notPaused
nonReentrant
ETHRateNotInvalid
returns (uint256 loanID)
{
systemStatus().requireIssuanceActive();
// Require ETH sent to be greater than minLoanCollateralSize
require(
msg.value >= minLoanCollateralSize,
"Not enough ETH to create this loan. Please see the minLoanCollateralSize"
);
// Require loanLiquidationOpen to be false or we are in liquidation phase
require(loanLiquidationOpen == false, "Loans are now being liquidated");
// Each account is limited to creating 50 (accountLoanLimit) loans
require(accountsSynthLoans[msg.sender].length < accountLoanLimit, "Each account is limited to 50 loans");
// Calculate issuance amount based on issuance ratio
uint256 maxLoanAmount = loanAmountFromCollateral(msg.value);
// Require requested _loanAmount to be less than maxLoanAmount
// Issuance ratio caps collateral to loan value at 150%
require(_loanAmount <= maxLoanAmount, "Loan amount exceeds max borrowing power");
uint256 mintingFee = _calculateMintingFee(_loanAmount);
uint256 loanAmountMinusFee = _loanAmount.sub(mintingFee);
// Require sUSD loan to mint does not exceed cap
require(totalIssuedSynths.add(_loanAmount) <= issueLimit, "Loan Amount exceeds the supply cap.");
// Get a Loan ID
loanID = _incrementTotalLoansCounter();
// Create Loan storage object
SynthLoanStruct memory synthLoan = SynthLoanStruct({
account: msg.sender,
collateralAmount: msg.value,
loanAmount: _loanAmount,
mintingFee: mintingFee,
timeCreated: block.timestamp,
loanID: loanID,
timeClosed: 0,
loanInterestRate: interestRate,
accruedInterest: 0,
lastInterestAccrued: 0
});
// Fee distribution. Mint the sUSD fees into the FeePool and record fees paid
if (mintingFee > 0) {
synthsUSD().issue(FEE_ADDRESS, mintingFee);
feePool().recordFeePaid(mintingFee);
}
// Record loan in mapping to account in an array of the accounts open loans
accountsSynthLoans[msg.sender].push(synthLoan);
// Increment totalIssuedSynths
totalIssuedSynths = totalIssuedSynths.add(_loanAmount);
// Issue the synth (less fee)
synthsUSD().issue(msg.sender, loanAmountMinusFee);
// Tell the Dapps a loan was created
emit LoanCreated(msg.sender, loanID, _loanAmount);
}
function closeLoan(uint256 loanID) external nonReentrant ETHRateNotInvalid {
_closeLoan(msg.sender, loanID, false);
}
// Add ETH collateral to an open loan
function depositCollateral(address account, uint256 loanID) external payable notPaused {
require(msg.value > 0, "Deposit amount must be greater than 0");
systemStatus().requireIssuanceActive();
// Require loanLiquidationOpen to be false or we are in liquidation phase
require(loanLiquidationOpen == false, "Loans are now being liquidated");
// Get the loan from storage
SynthLoanStruct memory synthLoan = _getLoanFromStorage(account, loanID);
// Check loan exists and is open
_checkLoanIsOpen(synthLoan);
uint256 totalCollateral = synthLoan.collateralAmount.add(msg.value);
_updateLoanCollateral(synthLoan, totalCollateral);
// Tell the Dapps collateral was added to loan
emit CollateralDeposited(account, loanID, msg.value, totalCollateral);
}
// Withdraw ETH collateral from an open loan
function withdrawCollateral(uint256 loanID, uint256 withdrawAmount) external notPaused nonReentrant ETHRateNotInvalid {
require(withdrawAmount > 0, "Amount to withdraw must be greater than 0");
systemStatus().requireIssuanceActive();
// Require loanLiquidationOpen to be false or we are in liquidation phase
require(loanLiquidationOpen == false, "Loans are now being liquidated");
// Get the loan from storage
SynthLoanStruct memory synthLoan = _getLoanFromStorage(msg.sender, loanID);
// Check loan exists and is open
_checkLoanIsOpen(synthLoan);
uint256 collateralAfter = synthLoan.collateralAmount.sub(withdrawAmount);
SynthLoanStruct memory loanAfter = _updateLoanCollateral(synthLoan, collateralAfter);
// require collateral ratio after to be above the liquidation ratio
(uint256 collateralRatioAfter, , ) = _loanCollateralRatio(loanAfter);
require(collateralRatioAfter > liquidationRatio, "Collateral ratio below liquidation after withdraw");
// transfer ETH to msg.sender
msg.sender.transfer(withdrawAmount);
// Tell the Dapps collateral was added to loan
emit CollateralWithdrawn(msg.sender, loanID, withdrawAmount, loanAfter.collateralAmount);
}
function repayLoan(
address _loanCreatorsAddress,
uint256 _loanID,
uint256 _repayAmount
) external ETHRateNotInvalid {
systemStatus().requireSystemActive();
// check msg.sender has sufficient sUSD to pay
require(IERC20(address(synthsUSD())).balanceOf(msg.sender) >= _repayAmount, "Not enough sUSD balance");
SynthLoanStruct memory synthLoan = _getLoanFromStorage(_loanCreatorsAddress, _loanID);
// Check loan exists and is open
_checkLoanIsOpen(synthLoan);
// Any interest accrued prior is rolled up into loan amount
uint256 loanAmountWithAccruedInterest = synthLoan.loanAmount.add(synthLoan.accruedInterest);
uint256 interestAmount = accruedInterestOnLoan(loanAmountWithAccruedInterest, _timeSinceInterestAccrual(synthLoan));
// repay any accrued interests first
// and repay principal loan amount with remaining amounts
uint256 accruedInterest = synthLoan.accruedInterest.add(interestAmount);
(
uint256 interestPaid,
uint256 loanAmountPaid,
uint256 accruedInterestAfter,
uint256 loanAmountAfter
) = _splitInterestLoanPayment(_repayAmount, accruedInterest, synthLoan.loanAmount);
// burn sUSD from msg.sender for repaid amount
synthsUSD().burn(msg.sender, _repayAmount);
// Send interest paid to fee pool and record loan amount paid
_processInterestAndLoanPayment(interestPaid, loanAmountPaid);
// update loan with new total loan amount, record accrued interests
_updateLoan(synthLoan, loanAmountAfter, accruedInterestAfter, block.timestamp);
emit LoanRepaid(_loanCreatorsAddress, _loanID, _repayAmount, loanAmountAfter);
}
// Liquidate loans at or below issuance ratio
function liquidateLoan(
address _loanCreatorsAddress,
uint256 _loanID,
uint256 _debtToCover
) external nonReentrant ETHRateNotInvalid {
systemStatus().requireSystemActive();
// check msg.sender (liquidator's wallet) has sufficient sUSD
require(IERC20(address(synthsUSD())).balanceOf(msg.sender) >= _debtToCover, "Not enough sUSD balance");
SynthLoanStruct memory synthLoan = _getLoanFromStorage(_loanCreatorsAddress, _loanID);
// Check loan exists and is open
_checkLoanIsOpen(synthLoan);
(uint256 collateralRatio, uint256 collateralValue, uint256 interestAmount) = _loanCollateralRatio(synthLoan);
require(collateralRatio < liquidationRatio, "Collateral ratio above liquidation ratio");
// calculate amount to liquidate to fix ratio including accrued interest
uint256 liquidationAmount = calculateAmountToLiquidate(
synthLoan.loanAmount.add(synthLoan.accruedInterest).add(interestAmount),
collateralValue
);
// cap debt to liquidate
uint256 amountToLiquidate = liquidationAmount < _debtToCover ? liquidationAmount : _debtToCover;
// burn sUSD from msg.sender for amount to liquidate
synthsUSD().burn(msg.sender, amountToLiquidate);
(uint256 interestPaid, uint256 loanAmountPaid, uint256 accruedInterestAfter, ) = _splitInterestLoanPayment(
amountToLiquidate,
synthLoan.accruedInterest.add(interestAmount),
synthLoan.loanAmount
);
// Send interests paid to fee pool and record loan amount paid
_processInterestAndLoanPayment(interestPaid, loanAmountPaid);
// Collateral value to redeem
uint256 collateralRedeemed = exchangeRates().effectiveValue(sUSD, amountToLiquidate, COLLATERAL);
// Add penalty
uint256 totalCollateralLiquidated = collateralRedeemed.multiplyDecimal(
SafeDecimalMath.unit().add(liquidationPenalty)
);
// update remaining loanAmount less amount paid and update accrued interests less interest paid
_updateLoan(synthLoan, synthLoan.loanAmount.sub(loanAmountPaid), accruedInterestAfter, block.timestamp);
// update remaining collateral on loan
_updateLoanCollateral(synthLoan, synthLoan.collateralAmount.sub(totalCollateralLiquidated));
// Send liquidated ETH collateral to msg.sender
msg.sender.transfer(totalCollateralLiquidated);
// emit loan liquidation event
emit LoanPartiallyLiquidated(
_loanCreatorsAddress,
_loanID,
msg.sender,
amountToLiquidate,
totalCollateralLiquidated
);
}
function _splitInterestLoanPayment(
uint256 _paymentAmount,
uint256 _accruedInterest,
uint256 _loanAmount
)
internal
pure
returns (
uint256 interestPaid,
uint256 loanAmountPaid,
uint256 accruedInterestAfter,
uint256 loanAmountAfter
)
{
uint256 remainingPayment = _paymentAmount;
// repay any accrued interests first
accruedInterestAfter = _accruedInterest;
if (remainingPayment > 0 && _accruedInterest > 0) {
// Max repay is the accruedInterest amount
interestPaid = remainingPayment > _accruedInterest ? _accruedInterest : remainingPayment;
accruedInterestAfter = accruedInterestAfter.sub(interestPaid);
remainingPayment = remainingPayment.sub(interestPaid);
}
// Remaining amounts - pay down loan amount
loanAmountAfter = _loanAmount;
if (remainingPayment > 0) {
loanAmountAfter = loanAmountAfter.sub(remainingPayment);
loanAmountPaid = remainingPayment;
}
}
function _processInterestAndLoanPayment(uint256 interestPaid, uint256 loanAmountPaid) internal {
// Fee distribution. Mint the sUSD fees into the FeePool and record fees paid
if (interestPaid > 0) {
synthsUSD().issue(FEE_ADDRESS, interestPaid);
feePool().recordFeePaid(interestPaid);
}
// Decrement totalIssuedSynths
if (loanAmountPaid > 0) {
totalIssuedSynths = totalIssuedSynths.sub(loanAmountPaid);
}
}
// Liquidation of an open loan available for anyone
function liquidateUnclosedLoan(address _loanCreatorsAddress, uint256 _loanID) external nonReentrant ETHRateNotInvalid {
require(loanLiquidationOpen, "Liquidation is not open");
// Close the creators loan and send collateral to the closer.
_closeLoan(_loanCreatorsAddress, _loanID, true);
// Tell the Dapps this loan was liquidated
emit LoanLiquidated(_loanCreatorsAddress, _loanID, msg.sender);
}
// ========== PRIVATE FUNCTIONS ==========
function _closeLoan(
address account,
uint256 loanID,
bool liquidation
) private {
systemStatus().requireIssuanceActive();
// Get the loan from storage
SynthLoanStruct memory synthLoan = _getLoanFromStorage(account, loanID);
// Check loan exists and is open
_checkLoanIsOpen(synthLoan);
// Calculate and deduct accrued interest (5%) for fee pool
// Accrued interests (captured in loanAmount) + new interests
uint256 interestAmount = accruedInterestOnLoan(
synthLoan.loanAmount.add(synthLoan.accruedInterest),
_timeSinceInterestAccrual(synthLoan)
);
uint256 repayAmount = synthLoan.loanAmount.add(interestAmount);
uint256 totalAccruedInterest = synthLoan.accruedInterest.add(interestAmount);
require(
IERC20(address(synthsUSD())).balanceOf(msg.sender) >= repayAmount,
"You do not have the required Synth balance to close this loan."
);
// Record loan as closed
_recordLoanClosure(synthLoan);
// Decrement totalIssuedSynths
// subtract the accrued interest from the loanAmount
totalIssuedSynths = totalIssuedSynths.sub(synthLoan.loanAmount.sub(synthLoan.accruedInterest));
// Burn all Synths issued for the loan + the fees
synthsUSD().burn(msg.sender, repayAmount);
// Fee distribution. Mint the sUSD fees into the FeePool and record fees paid
synthsUSD().issue(FEE_ADDRESS, totalAccruedInterest);
feePool().recordFeePaid(totalAccruedInterest);
uint256 remainingCollateral = synthLoan.collateralAmount;
if (liquidation) {
// Send liquidator redeemed collateral + 10% penalty
uint256 collateralRedeemed = exchangeRates().effectiveValue(sUSD, repayAmount, COLLATERAL);
// add penalty
uint256 totalCollateralLiquidated = collateralRedeemed.multiplyDecimal(
SafeDecimalMath.unit().add(liquidationPenalty)
);
// ensure remaining ETH collateral sufficient to cover collateral liquidated
// will revert if the liquidated collateral + penalty is more than remaining collateral
remainingCollateral = remainingCollateral.sub(totalCollateralLiquidated);
// Send liquidator CollateralLiquidated
msg.sender.transfer(totalCollateralLiquidated);
}
// Send remaining collateral to loan creator
synthLoan.account.transfer(remainingCollateral);
// Tell the Dapps
emit LoanClosed(account, loanID, totalAccruedInterest);
}
function _getLoanFromStorage(address account, uint256 loanID) private view returns (SynthLoanStruct memory) {
SynthLoanStruct[] memory synthLoans = accountsSynthLoans[account];
for (uint256 i = 0; i < synthLoans.length; i++) {
if (synthLoans[i].loanID == loanID) {
return synthLoans[i];
}
}
}
function _updateLoan(
SynthLoanStruct memory _synthLoan,
uint256 _newLoanAmount,
uint256 _newAccruedInterest,
uint256 _lastInterestAccrued
) private {
// Get storage pointer to the accounts array of loans
SynthLoanStruct[] storage synthLoans = accountsSynthLoans[_synthLoan.account];
for (uint256 i = 0; i < synthLoans.length; i++) {
if (synthLoans[i].loanID == _synthLoan.loanID) {
synthLoans[i].loanAmount = _newLoanAmount;
synthLoans[i].accruedInterest = _newAccruedInterest;
synthLoans[i].lastInterestAccrued = uint40(_lastInterestAccrued);
}
}
}
function _updateLoanCollateral(SynthLoanStruct memory _synthLoan, uint256 _newCollateralAmount)
private
returns (SynthLoanStruct memory)
{
// Get storage pointer to the accounts array of loans
SynthLoanStruct[] storage synthLoans = accountsSynthLoans[_synthLoan.account];
for (uint256 i = 0; i < synthLoans.length; i++) {
if (synthLoans[i].loanID == _synthLoan.loanID) {
synthLoans[i].collateralAmount = _newCollateralAmount;
return synthLoans[i];
}
}
}
function _recordLoanClosure(SynthLoanStruct memory synthLoan) private {
// Get storage pointer to the accounts array of loans
SynthLoanStruct[] storage synthLoans = accountsSynthLoans[synthLoan.account];
for (uint256 i = 0; i < synthLoans.length; i++) {
if (synthLoans[i].loanID == synthLoan.loanID) {
// Record the time the loan was closed
synthLoans[i].timeClosed = block.timestamp;
}
}
// Reduce Total Open Loans Count
totalOpenLoanCount = totalOpenLoanCount.sub(1);
}
function _incrementTotalLoansCounter() private returns (uint256) {
// Increase the total Open loan count
totalOpenLoanCount = totalOpenLoanCount.add(1);
// Increase the total Loans Created count
totalLoansCreated = totalLoansCreated.add(1);
// Return total count to be used as a unique ID.
return totalLoansCreated;
}
function _calculateMintingFee(uint256 _loanAmount) private view returns (uint256 mintingFee) {
mintingFee = _loanAmount.multiplyDecimalRound(issueFeeRate);
}
function _timeSinceInterestAccrual(SynthLoanStruct memory _synthLoan) private view returns (uint256 timeSinceAccrual) {
// The last interest accrued timestamp for the loan
// If lastInterestAccrued timestamp is not set (0), use loan timeCreated
uint256 lastInterestAccrual = _synthLoan.lastInterestAccrued > 0
? uint256(_synthLoan.lastInterestAccrued)
: _synthLoan.timeCreated;
// diff between last interested accrued and now
// use loan's timeClosed if loan is closed
timeSinceAccrual = _synthLoan.timeClosed > 0
? _synthLoan.timeClosed.sub(lastInterestAccrual)
: block.timestamp.sub(lastInterestAccrual);
}
function _checkLoanIsOpen(SynthLoanStruct memory _synthLoan) internal pure {
require(_synthLoan.loanID > 0, "Loan does not exist");
require(_synthLoan.timeClosed == 0, "Loan already closed");
}
/* ========== INTERNAL VIEWS ========== */
function systemStatus() internal view returns (ISystemStatus) {
return ISystemStatus(requireAndGetAddress(CONTRACT_SYSTEMSTATUS, "Missing SystemStatus address"));
}
function synthsUSD() internal view returns (ISynth) {
return ISynth(requireAndGetAddress(CONTRACT_SYNTHSUSD, "Missing SynthsUSD address"));
}
function exchangeRates() internal view returns (IExchangeRates) {
return IExchangeRates(requireAndGetAddress(CONTRACT_EXRATES, "Missing ExchangeRates address"));
}
function feePool() internal view returns (IFeePool) {
return IFeePool(requireAndGetAddress(CONTRACT_FEEPOOL, "Missing FeePool address"));
}
/* ========== MODIFIERS ========== */
modifier ETHRateNotInvalid() {
require(!exchangeRates().rateIsInvalid(COLLATERAL), "Blocked as ETH rate is invalid");
_;
}
// ========== EVENTS ==========
event CollateralizationRatioUpdated(uint256 ratio);
event LiquidationRatioUpdated(uint256 ratio);
event InterestRateUpdated(uint256 interestRate);
event IssueFeeRateUpdated(uint256 issueFeeRate);
event IssueLimitUpdated(uint256 issueLimit);
event MinLoanCollateralSizeUpdated(uint256 minLoanCollateralSize);
event AccountLoanLimitUpdated(uint256 loanLimit);
event LoanLiquidationOpenUpdated(bool loanLiquidationOpen);
event LoanCreated(address indexed account, uint256 loanID, uint256 amount);
event LoanClosed(address indexed account, uint256 loanID, uint256 feesPaid);
event LoanLiquidated(address indexed account, uint256 loanID, address liquidator);
event LoanPartiallyLiquidated(
address indexed account,
uint256 loanID,
address liquidator,
uint256 liquidatedAmount,
uint256 liquidatedCollateral
);
event CollateralDeposited(address indexed account, uint256 loanID, uint256 collateralAmount, uint256 collateralAfter);
event CollateralWithdrawn(address indexed account, uint256 loanID, uint256 amountWithdrawn, uint256 collateralAfter);
event LoanRepaid(address indexed account, uint256 loanID, uint256 repaidAmount, uint256 newLoanAmount);
}
{
"compilationTarget": {
"EtherCollateralsUSD.sol": "EtherCollateralsUSD"
},
"evmVersion": "istanbul",
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_resolver","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"loanLimit","type":"uint256"}],"name":"AccountLoanLimitUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"loanID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"collateralAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"collateralAfter","type":"uint256"}],"name":"CollateralDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"loanID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountWithdrawn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"collateralAfter","type":"uint256"}],"name":"CollateralWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"ratio","type":"uint256"}],"name":"CollateralizationRatioUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"interestRate","type":"uint256"}],"name":"InterestRateUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"issueFeeRate","type":"uint256"}],"name":"IssueFeeRateUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"issueLimit","type":"uint256"}],"name":"IssueLimitUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"ratio","type":"uint256"}],"name":"LiquidationRatioUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"loanID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"feesPaid","type":"uint256"}],"name":"LoanClosed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"loanID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"LoanCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"loanID","type":"uint256"},{"indexed":false,"internalType":"address","name":"liquidator","type":"address"}],"name":"LoanLiquidated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"loanLiquidationOpen","type":"bool"}],"name":"LoanLiquidationOpenUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"loanID","type":"uint256"},{"indexed":false,"internalType":"address","name":"liquidator","type":"address"},{"indexed":false,"internalType":"uint256","name":"liquidatedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"liquidatedCollateral","type":"uint256"}],"name":"LoanPartiallyLiquidated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"loanID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"repaidAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newLoanAmount","type":"uint256"}],"name":"LoanRepaid","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"minLoanCollateralSize","type":"uint256"}],"name":"MinLoanCollateralSizeUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":false,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerNominated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"isPaused","type":"bool"}],"name":"PauseChanged","type":"event"},{"constant":true,"inputs":[],"name":"COLLATERAL","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MAX_ADDRESSES_FROM_RESOLVER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"acceptOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"accountLoanLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"accountOpenLoanCounter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"accountsSynthLoans","outputs":[{"internalType":"address payable","name":"account","type":"address"},{"internalType":"uint256","name":"collateralAmount","type":"uint256"},{"internalType":"uint256","name":"loanAmount","type":"uint256"},{"internalType":"uint256","name":"mintingFee","type":"uint256"},{"internalType":"uint256","name":"timeCreated","type":"uint256"},{"internalType":"uint256","name":"loanID","type":"uint256"},{"internalType":"uint256","name":"timeClosed","type":"uint256"},{"internalType":"uint256","name":"loanInterestRate","type":"uint256"},{"internalType":"uint256","name":"accruedInterest","type":"uint256"},{"internalType":"uint40","name":"lastInterestAccrued","type":"uint40"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"_loanAmount","type":"uint256"},{"internalType":"uint256","name":"_seconds","type":"uint256"}],"name":"accruedInterestOnLoan","outputs":[{"internalType":"uint256","name":"interestAmount","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"debtBalance","type":"uint256"},{"internalType":"uint256","name":"collateral","type":"uint256"}],"name":"calculateAmountToLiquidate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"loanID","type":"uint256"}],"name":"closeLoan","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"loanAmount","type":"uint256"}],"name":"collateralAmountForLoan","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"collateralizationRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_loanID","type":"uint256"}],"name":"currentInterestOnLoan","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"loanID","type":"uint256"}],"name":"depositCollateral","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"getContractInfo","outputs":[{"internalType":"uint256","name":"_collateralizationRatio","type":"uint256"},{"internalType":"uint256","name":"_issuanceRatio","type":"uint256"},{"internalType":"uint256","name":"_interestRate","type":"uint256"},{"internalType":"uint256","name":"_interestPerSecond","type":"uint256"},{"internalType":"uint256","name":"_issueFeeRate","type":"uint256"},{"internalType":"uint256","name":"_issueLimit","type":"uint256"},{"internalType":"uint256","name":"_minLoanCollateralSize","type":"uint256"},{"internalType":"uint256","name":"_totalIssuedSynths","type":"uint256"},{"internalType":"uint256","name":"_totalLoansCreated","type":"uint256"},{"internalType":"uint256","name":"_totalOpenLoanCount","type":"uint256"},{"internalType":"uint256","name":"_ethBalance","type":"uint256"},{"internalType":"uint256","name":"_liquidationDeadline","type":"uint256"},{"internalType":"bool","name":"_loanLiquidationOpen","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_loanID","type":"uint256"}],"name":"getLoan","outputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"collateralAmount","type":"uint256"},{"internalType":"uint256","name":"loanAmount","type":"uint256"},{"internalType":"uint256","name":"timeCreated","type":"uint256"},{"internalType":"uint256","name":"loanID","type":"uint256"},{"internalType":"uint256","name":"timeClosed","type":"uint256"},{"internalType":"uint256","name":"accruedInterest","type":"uint256"},{"internalType":"uint256","name":"totalFees","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_loanID","type":"uint256"}],"name":"getLoanCollateralRatio","outputs":[{"internalType":"uint256","name":"loanCollateralRatio","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_loanID","type":"uint256"}],"name":"getMintingFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getResolverAddressesRequired","outputs":[{"internalType":"bytes32[24]","name":"addressesRequired","type":"bytes32[24]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"interestPerSecond","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"interestRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"contract AddressResolver","name":"_resolver","type":"address"}],"name":"isResolverCached","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"issuanceRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"issueFeeRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"issueLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"lastPauseTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_loanCreatorsAddress","type":"address"},{"internalType":"uint256","name":"_loanID","type":"uint256"},{"internalType":"uint256","name":"_debtToCover","type":"uint256"}],"name":"liquidateLoan","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_loanCreatorsAddress","type":"address"},{"internalType":"uint256","name":"_loanID","type":"uint256"}],"name":"liquidateUnclosedLoan","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"liquidationDeadline","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"liquidationPenalty","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"liquidationRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"collateralAmount","type":"uint256"}],"name":"loanAmountFromCollateral","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"loanLiquidationOpen","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"minLoanCollateralSize","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"nominateNewOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"nominatedOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_loanAmount","type":"uint256"}],"name":"openLoan","outputs":[{"internalType":"uint256","name":"loanID","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"openLoanIDsByAccount","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_loanCreatorsAddress","type":"address"},{"internalType":"uint256","name":"_loanID","type":"uint256"},{"internalType":"uint256","name":"_repayAmount","type":"uint256"}],"name":"repayLoan","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"resolver","outputs":[{"internalType":"contract AddressResolver","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"resolverAddressesRequired","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_loanLimit","type":"uint256"}],"name":"setAccountLoanLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"ratio","type":"uint256"}],"name":"setCollateralizationRatio","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_interestRate","type":"uint256"}],"name":"setInterestRate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_issueFeeRate","type":"uint256"}],"name":"setIssueFeeRate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_issueLimit","type":"uint256"}],"name":"setIssueLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_liquidationRatio","type":"uint256"}],"name":"setLiquidationRatio","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bool","name":"_loanLiquidationOpen","type":"bool"}],"name":"setLoanLiquidationOpen","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_minLoanCollateralSize","type":"uint256"}],"name":"setMinLoanCollateralSize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bool","name":"_paused","type":"bool"}],"name":"setPaused","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"contract AddressResolver","name":"_resolver","type":"address"}],"name":"setResolverAndSyncCache","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_loanID","type":"uint256"}],"name":"timeSinceInterestAccrualOnLoan","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_loanID","type":"uint256"}],"name":"totalFeesOnLoan","outputs":[{"internalType":"uint256","name":"interestAmount","type":"uint256"},{"internalType":"uint256","name":"mintingFee","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalIssuedSynths","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalLoansCreated","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalOpenLoanCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"loanID","type":"uint256"},{"internalType":"uint256","name":"withdrawAmount","type":"uint256"}],"name":"withdrawCollateral","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]