/**
* Copyright 2017-2021, bZeroX, LLC. All Rights Reserved.
* Licensed under the Apache License, Version 2.0.
*/
pragma solidity 0.5.17;
pragma experimental ABIEncoderV2;
interface IPriceFeeds {
function queryRate(address sourceToken, address destToken) external view returns (uint256 rate, uint256 precision);
function queryPrecision(address sourceToken, address destToken) external view returns (uint256 precision);
function queryReturn(
address sourceToken,
address destToken,
uint256 sourceAmount
) external view returns (uint256 destAmount);
function checkPriceDisagreement(
address sourceToken,
address destToken,
uint256 sourceAmount,
uint256 destAmount,
uint256 maxSlippage
) external view returns (uint256 sourceToDestSwapRate);
function amountInEth(address Token, uint256 amount) external view returns (uint256 ethAmount);
function getMaxDrawdown(
address loanToken,
address collateralToken,
uint256 loanAmount,
uint256 collateralAmount,
uint256 maintenanceMargin
) external view returns (uint256);
function getCurrentMarginAndCollateralSize(
address loanToken,
address collateralToken,
uint256 loanAmount,
uint256 collateralAmount
) external view returns (uint256 currentMargin, uint256 collateralInEthAmount);
function getCurrentMargin(
address loanToken,
address collateralToken,
uint256 loanAmount,
uint256 collateralAmount
) external view returns (uint256 currentMargin, uint256 collateralToLoanRate);
function shouldLiquidate(
address loanToken,
address collateralToken,
uint256 loanAmount,
uint256 collateralAmount,
uint256 maintenanceMargin
) external view returns (bool);
function getFastGasPrice(address payToken) external view returns (uint256);
}
interface ProtocolAffiliatesInterface {
function setAffiliatesReferrer(address user, address referrer) external;
function setUserNotFirstTradeFlag(address user_) external;
function getUserNotFirstTradeFlag(address user_) external returns (bool);
function payTradingFeeToAffiliatesReferrer(
address affiliate,
address trader,
address token,
uint256 amount
) external returns (uint256 affiliatesBonusSOVAmount, uint256 affiliatesBonusTokenAmount);
}
contract IERC20 {
string public name;
uint8 public decimals;
string public symbol;
function totalSupply() public view returns (uint256);
function balanceOf(address _who) public view returns (uint256);
function allowance(address _owner, address _spender) public view returns (uint256);
function approve(address _spender, uint256 _value) public returns (bool);
function transfer(address _to, uint256 _value) public returns (bool);
function transferFrom(
address _from,
address _to,
uint256 _value
) public returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
interface IWrbtc {
function deposit() external payable;
function withdraw(uint256 wad) 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) {
return sub(a, b, "SafeMath: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*
* _Available since v2.4.0._
*/
function sub(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
require(b <= a, errorMessage);
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-contracts/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) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message 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.
*
* _Available since v2.4.0._
*/
function div(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b != 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Integer division of two numbers, rounding up and truncating the quotient
*/
function divCeil(uint256 a, uint256 b) internal pure returns (uint256) {
return divCeil(a, b, "SafeMath: division by zero");
}
/**
* @dev Integer division of two numbers, rounding up and truncating the quotient
*/
function divCeil(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b != 0, errorMessage);
if (a == 0) {
return 0;
}
uint256 c = ((a - 1) / b) + 1;
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) {
return mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message 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.
*
* _Available since v2.4.0._
*/
function mod(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
function min256(uint256 _a, uint256 _b) internal pure returns (uint256) {
return _a < _b ? _a : _b;
}
}
interface ISwapsImpl {
function internalSwap(
address sourceTokenAddress,
address destTokenAddress,
address receiverAddress,
address returnToSenderAddress,
uint256 minSourceTokenAmount,
uint256 maxSourceTokenAmount,
uint256 requiredDestTokenAmount
) external returns (uint256 destTokenAmountReceived, uint256 sourceTokenAmountUsed);
function internalExpectedRate(
address sourceTokenAddress,
address destTokenAddress,
uint256 sourceTokenAmount,
address optionalContractAddress
) external view returns (uint256);
function internalExpectedReturn(
address sourceTokenAddress,
address destTokenAddress,
uint256 sourceTokenAmount,
address sovrynSwapContractRegistryAddress
) external view returns (uint256 expectedReturn);
}
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
// for accounts without code, i.e. `keccak256('')`
bytes32 codehash;
bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
// solhint-disable-next-line no-inline-assembly
assembly {
codehash := extcodehash(account)
}
return (codehash != accountHash && codehash != 0x0);
}
/**
* @dev Converts an `address` into `address payable`. Note that this is
* simply a type cast: the actual underlying value is not changed.
*
* _Available since v2.4.0._
*/
function toPayable(address account) internal pure returns (address payable) {
return address(uint160(account));
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html
* #use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*
* _Available since v2.4.0._
*/
function sendValue(address recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
// solhint-disable-next-line avoid-call-value
(bool success, ) = recipient.call.value(amount)("");
require(success, "Address: unable to send value, recipient may have reverted");
}
}
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using SafeMath for uint256;
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
// solhint-disable-next-line max-line-length
require((value == 0) || (token.allowance(address(this), spender) == 0), "SafeERC20: approve from non-zero to non-zero allowance");
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender).add(value);
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves.
// A Solidity high level call has three parts:
// 1. The target address is checked to verify it contains contract code
// 2. The call itself is made, and success asserted
// 3. The return value is decoded, which in turn checks the size of the returned data.
// solhint-disable-next-line max-line-length
require(address(token).isContract(), "SafeERC20: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = address(token).call(data);
require(success, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
// solhint-disable-next-line max-line-length
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
interface ILoanPool {
function tokenPrice() external view returns (uint256 price);
function borrowInterestRate() external view returns (uint256);
function totalAssetSupply() external view returns (uint256);
}
/**
* @title The Loan Object.
* @notice This contract code comes from bZx. bZx is a protocol for tokenized
* margin trading and lending https://bzx.network similar to the dYdX protocol.
*
* This contract contains the storage structure of the Loan Object.
* */
contract LoanStruct {
struct Loan {
bytes32 id; /// ID of the loan.
bytes32 loanParamsId; /// The linked loan params ID.
bytes32 pendingTradesId; /// The linked pending trades ID.
bool active; /// If false, the loan has been fully closed.
uint256 principal; /// Total borrowed amount outstanding.
uint256 collateral; /// Total collateral escrowed for the loan.
uint256 startTimestamp; /// Loan start time.
uint256 endTimestamp; /// For active loans, this is the expected loan end time, for in-active loans, is the actual (past) end time.
uint256 startMargin; /// Initial margin when the loan opened.
uint256 startRate; /// Reference rate when the loan opened for converting collateralToken to loanToken.
address borrower; /// Borrower of this loan.
address lender; /// Lender of this loan.
}
}
/**
* @title The Loan Parameters.
* @notice This contract code comes from bZx. bZx is a protocol for tokenized
* margin trading and lending https://bzx.network similar to the dYdX protocol.
*
* This contract contains the storage structure of the Loan Parameters.
* */
contract LoanParamsStruct {
struct LoanParams {
/// @dev ID of loan params object.
bytes32 id;
/// @dev If false, this object has been disabled by the owner and can't
/// be used for future loans.
bool active;
/// @dev Owner of this object.
address owner;
/// @dev The token being loaned.
address loanToken;
/// @dev The required collateral token.
address collateralToken;
/// @dev The minimum allowed initial margin.
uint256 minInitialMargin;
/// @dev An unhealthy loan when current margin is at or below this value.
uint256 maintenanceMargin;
/// @dev The maximum term for new loans (0 means there's no max term).
uint256 maxLoanTerm;
}
}
/**
* @title The Loan Order.
* @notice This contract code comes from bZx. bZx is a protocol for tokenized
* margin trading and lending https://bzx.network similar to the dYdX protocol.
*
* This contract contains the storage structure of the Loan Order.
* */
contract OrderStruct {
struct Order {
uint256 lockedAmount; /// Escrowed amount waiting for a counterparty.
uint256 interestRate; /// Interest rate defined by the creator of this order.
uint256 minLoanTerm; /// Minimum loan term allowed.
uint256 maxLoanTerm; /// Maximum loan term allowed.
uint256 createdTimestamp; /// Timestamp when this order was created.
uint256 expirationTimestamp; /// Timestamp when this order expires.
}
}
/**
* @title The Lender Interest.
* @notice This contract code comes from bZx. bZx is a protocol for tokenized
* margin trading and lending https://bzx.network similar to the dYdX protocol.
*
* This contract contains the storage structure of the Lender Interest.
* */
contract LenderInterestStruct {
struct LenderInterest {
uint256 principalTotal; /// Total borrowed amount outstanding of asset.
uint256 owedPerDay; /// Interest owed per day for all loans of asset.
uint256 owedTotal; /// Total interest owed for all loans of asset (assuming they go to full term).
uint256 paidTotal; /// Total interest paid so far for asset.
uint256 updatedTimestamp; /// Last update.
}
}
/**
* @title The Loan Interest.
* @notice This contract code comes from bZx. bZx is a protocol for tokenized
* margin trading and lending https://bzx.network similar to the dYdX protocol.
*
* This contract contains the storage structure of the Loan Interest.
* */
contract LoanInterestStruct {
struct LoanInterest {
uint256 owedPerDay; /// Interest owed per day for loan.
uint256 depositTotal; /// Total escrowed interest for loan.
uint256 updatedTimestamp; /// Last update.
}
}
/**
* @title Objects contract.
* @notice This contract code comes from bZx. bZx is a protocol for tokenized
* margin trading and lending https://bzx.network similar to the dYdX protocol.
*
* This contract inherints and aggregates several structures needed to handle
* loans on the protocol.
* */
contract Objects is LoanStruct, LoanParamsStruct, OrderStruct, LenderInterestStruct, LoanInterestStruct {
}
/**
* @dev Based on Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* As of v2.5.0, only `address` sets are supported.
*
* Include with `using EnumerableSet for EnumerableSet.AddressSet;`.
*
* _Available since v2.5.0._
*/
library EnumerableAddressSet {
struct AddressSet {
// Position of the value in the `values` array, plus 1 because index 0
// means a value is not in the set.
mapping(address => uint256) index;
address[] values;
}
/**
* @dev Add a value to a set. O(1).
* Returns false if the value was already in the set.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
if (!contains(set, value)) {
set.index[value] = set.values.push(value);
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
* Returns false if the value was not present in the set.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
if (contains(set, value)) {
uint256 toDeleteIndex = set.index[value] - 1;
uint256 lastIndex = set.values.length - 1;
// If the element we're deleting is the last one, we can just remove it without doing a swap
if (lastIndex != toDeleteIndex) {
address lastValue = set.values[lastIndex];
// Move the last value to the index where the deleted value is
set.values[toDeleteIndex] = lastValue;
// Update the index for the moved value
set.index[lastValue] = toDeleteIndex + 1; // All indexes are 1-based
}
// Delete the index entry for the deleted value
delete set.index[value];
// Delete the old entry for the moved value
set.values.pop();
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return set.index[value] != 0;
}
/**
* @dev Returns an array with all values in the set. O(N).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
* WARNING: This function may run out of gas on large sets: use {length} and
* {get} instead in these cases.
*/
function enumerate(AddressSet storage set) internal view returns (address[] memory) {
address[] memory output = new address[](set.values.length);
for (uint256 i; i < set.values.length; i++) {
output[i] = set.values[i];
}
return output;
}
/**
* @dev Returns a chunk of array as recommended in enumerate() to avoid running of gas.
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
* WARNING: This function may run out of gas on large sets: use {length} and
* {get} instead in these cases.
* @param start start index of chunk
* @param count num of element to return; if count == 0 then returns all the elements from the @param start
*/
function enumerateChunk(
AddressSet storage set,
uint256 start,
uint256 count
) internal view returns (address[] memory output) {
uint256 end = start + count;
require(end >= start, "addition overflow");
end = (set.values.length < end || count == 0) ? set.values.length : end;
if (end == 0 || start >= end) {
return output;
}
output = new address[](end - start);
for (uint256 i; i < end - start; i++) {
output[i] = set.values[i + start];
}
return output;
}
/**
* @dev Returns the number of elements on the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return set.values.length;
}
/** @dev Returns the element stored at position `index` in the set. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function get(AddressSet storage set, uint256 index) internal view returns (address) {
return set.values[index];
}
}
/**
* @title Library for managing loan sets.
*
* @notice Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* Include with `using EnumerableBytes32Set for EnumerableBytes32Set.Bytes32Set;`.
* */
library EnumerableBytes32Set {
struct Bytes32Set {
/// Position of the value in the `values` array, plus 1 because index 0
/// means a value is not in the set.
mapping(bytes32 => uint256) index;
bytes32[] values;
}
/**
* @notice Add an address value to a set. O(1).
*
* @param set The set of values.
* @param addrvalue The address to add.
*
* @return False if the value was already in the set.
*/
function addAddress(Bytes32Set storage set, address addrvalue) internal returns (bool) {
bytes32 value;
assembly {
value := addrvalue
}
return addBytes32(set, value);
}
/**
* @notice Add a value to a set. O(1).
*
* @param set The set of values.
* @param value The new value to add.
*
* @return False if the value was already in the set.
*/
function addBytes32(Bytes32Set storage set, bytes32 value) internal returns (bool) {
if (!contains(set, value)) {
set.index[value] = set.values.push(value);
return true;
} else {
return false;
}
}
/**
* @notice Remove an address value from a set. O(1).
*
* @param set The set of values.
* @param addrvalue The address to remove.
*
* @return False if the address was not present in the set.
*/
function removeAddress(Bytes32Set storage set, address addrvalue) internal returns (bool) {
bytes32 value;
assembly {
value := addrvalue
}
return removeBytes32(set, value);
}
/**
* @notice Remove a value from a set. O(1).
*
* @param set The set of values.
* @param value The value to remove.
*
* @return False if the value was not present in the set.
*/
function removeBytes32(Bytes32Set storage set, bytes32 value) internal returns (bool) {
if (contains(set, value)) {
uint256 toDeleteIndex = set.index[value] - 1;
uint256 lastIndex = set.values.length - 1;
/// If the element we're deleting is the last one,
/// we can just remove it without doing a swap.
if (lastIndex != toDeleteIndex) {
bytes32 lastValue = set.values[lastIndex];
/// Move the last value to the index where the deleted value is.
set.values[toDeleteIndex] = lastValue;
/// Update the index for the moved value.
set.index[lastValue] = toDeleteIndex + 1; // All indexes are 1-based
}
/// Delete the index entry for the deleted value.
delete set.index[value];
/// Delete the old entry for the moved value.
set.values.pop();
return true;
} else {
return false;
}
}
/**
* @notice Find out whether a value exists in the set.
*
* @param set The set of values.
* @param value The value to find.
*
* @return True if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return set.index[value] != 0;
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function containsAddress(Bytes32Set storage set, address addrvalue) internal view returns (bool) {
bytes32 value;
assembly {
value := addrvalue
}
return set.index[value] != 0;
}
/**
* @notice Get all set values.
*
* @param set The set of values.
* @param start The offset of the returning set.
* @param count The limit of number of values to return.
*
* @return An array with all values in the set. O(N).
*
* @dev Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* WARNING: This function may run out of gas on large sets: use {length} and
* {get} instead in these cases.
*/
function enumerate(
Bytes32Set storage set,
uint256 start,
uint256 count
) internal view returns (bytes32[] memory output) {
uint256 end = start + count;
require(end >= start, "addition overflow");
end = set.values.length < end ? set.values.length : end;
if (end == 0 || start >= end) {
return output;
}
output = new bytes32[](end - start);
for (uint256 i; i < end - start; i++) {
output[i] = set.values[i + start];
}
return output;
}
/**
* @notice Get the legth of the set.
*
* @param set The set of values.
*
* @return the number of elements on the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return set.values.length;
}
/**
* @notice Get an item from the set by its index.
*
* @dev Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*
* @param set The set of values.
* @param index The index of the value to return.
*
* @return the element stored at position `index` in the set. O(1).
*/
function get(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return set.values[index];
}
}
/**
* @title Helps contracts guard against reentrancy attacks.
* @author Remco Bloemen <remco@2π.com>, Eenae <[email protected]>
* @dev If you mark a function `nonReentrant`, you should also
* mark it `external`.
*/
contract ReentrancyGuard {
/// @dev Constant for unlocked guard state - non-zero to prevent extra gas costs.
/// See: https://github.com/OpenZeppelin/openzeppelin-solidity/issues/1056
uint256 internal constant REENTRANCY_GUARD_FREE = 1;
/// @dev Constant for locked guard state
uint256 internal constant REENTRANCY_GUARD_LOCKED = 2;
/**
* @dev We use a single lock for the whole contract.
*/
uint256 internal reentrancyLock = REENTRANCY_GUARD_FREE;
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* If you mark a function `nonReentrant`, you should also
* mark it `external`. Calling one `nonReentrant` function from
* another is not supported. Instead, you can implement a
* `private` function doing the actual work, and an `external`
* wrapper marked as `nonReentrant`.
*/
modifier nonReentrant() {
require(reentrancyLock == REENTRANCY_GUARD_FREE, "nonReentrant");
reentrancyLock = REENTRANCY_GUARD_LOCKED;
_;
reentrancyLock = REENTRANCY_GUARD_FREE;
}
}
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with GSN meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
contract Context {
// Empty internal constructor, to prevent people from mistakenly deploying
// an instance of this contract, which should be used via inheritance.
constructor() internal {}
// solhint-disable-previous-line no-empty-blocks
function _msgSender() internal view returns (address payable) {
return msg.sender;
}
function _msgData() internal view returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() internal {
address msgSender = _msgSender();
_owner = msgSender;
emit OwnershipTransferred(address(0), msgSender);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(isOwner(), "unauthorized");
_;
}
/**
* @dev Returns true if the caller is the current owner.
*/
function isOwner() public view returns (bool) {
return _msgSender() == _owner;
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public onlyOwner {
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
*/
function _transferOwnership(address newOwner) internal {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
contract IWrbtcERC20 is IWrbtc, IERC20 {}
/**
* @title State contract.
* @notice This contract code comes from bZx. bZx is a protocol for tokenized
* margin trading and lending https://bzx.network similar to the dYdX protocol.
*
* This contract contains the storage values of the Protocol.
* */
contract State is Objects, ReentrancyGuard, Ownable {
using SafeMath for uint256;
using EnumerableAddressSet for EnumerableAddressSet.AddressSet; // enumerable map of addresses
using EnumerableBytes32Set for EnumerableBytes32Set.Bytes32Set; // enumerable map of bytes32 or addresses
/// Handles asset reference price lookups.
address public priceFeeds;
/// Handles asset swaps using dex liquidity.
address public swapsImpl;
/// Contract registry address of the Sovryn swap network.
address public sovrynSwapContractRegistryAddress;
/// Implementations of protocol functions.
mapping(bytes4 => address) public logicTargets;
/// Loans: loanId => Loan
mapping(bytes32 => Loan) public loans;
/// Loan parameters: loanParamsId => LoanParams
mapping(bytes32 => LoanParams) public loanParams;
/// lender => orderParamsId => Order
mapping(address => mapping(bytes32 => Order)) public lenderOrders;
/// borrower => orderParamsId => Order
mapping(address => mapping(bytes32 => Order)) public borrowerOrders;
/// loanId => delegated => approved
mapping(bytes32 => mapping(address => bool)) public delegatedManagers;
/**
*** Interest ***
**/
/// lender => loanToken => LenderInterest object
mapping(address => mapping(address => LenderInterest)) public lenderInterest;
/// loanId => LoanInterest object
mapping(bytes32 => LoanInterest) public loanInterest;
/**
*** Internals ***
**/
/// Implementations set.
EnumerableBytes32Set.Bytes32Set internal logicTargetsSet;
/// Active loans set.
EnumerableBytes32Set.Bytes32Set internal activeLoansSet;
/// Lender loans set.
mapping(address => EnumerableBytes32Set.Bytes32Set) internal lenderLoanSets;
/// Borrow loans set.
mapping(address => EnumerableBytes32Set.Bytes32Set) internal borrowerLoanSets;
/// User loan params set.
mapping(address => EnumerableBytes32Set.Bytes32Set) internal userLoanParamSets;
/// Address controlling fee withdrawals.
address public feesController;
/// 10% fee /// Fee taken from lender interest payments.
uint256 public lendingFeePercent = 10**19;
/// Total interest fees received and not withdrawn per asset.
mapping(address => uint256) public lendingFeeTokensHeld;
/// Total interest fees withdraw per asset.
/// lifetime fees = lendingFeeTokensHeld + lendingFeeTokensPaid
mapping(address => uint256) public lendingFeeTokensPaid;
/// 0.15% fee /// Fee paid for each trade.
uint256 public tradingFeePercent = 15 * 10**16;
/// Total trading fees received and not withdrawn per asset.
mapping(address => uint256) public tradingFeeTokensHeld;
/// Total trading fees withdraw per asset
/// lifetime fees = tradingFeeTokensHeld + tradingFeeTokensPaid
mapping(address => uint256) public tradingFeeTokensPaid;
/// 0.09% fee /// Origination fee paid for each loan.
uint256 public borrowingFeePercent = 9 * 10**16;
/// Total borrowing fees received and not withdrawn per asset.
mapping(address => uint256) public borrowingFeeTokensHeld;
/// Total borrowing fees withdraw per asset.
/// lifetime fees = borrowingFeeTokensHeld + borrowingFeeTokensPaid
mapping(address => uint256) public borrowingFeeTokensPaid;
/// Current protocol token deposit balance.
uint256 public protocolTokenHeld;
/// Lifetime total payout of protocol token.
uint256 public protocolTokenPaid;
/// 5% fee share in form of SOV /// Fee share for affiliate program.
uint256 public affiliateFeePercent = 5 * 10**18;
/// 5% collateral discount /// Discount on collateral for liquidators.
uint256 public liquidationIncentivePercent = 5 * 10**18;
/// loanPool => underlying
mapping(address => address) public loanPoolToUnderlying;
/// underlying => loanPool
mapping(address => address) public underlyingToLoanPool;
/// Loan pools set.
EnumerableBytes32Set.Bytes32Set internal loanPoolsSet;
/// Supported tokens for swaps.
mapping(address => bool) public supportedTokens;
/// % disagreement between swap rate and reference rate.
uint256 public maxDisagreement = 5 * 10**18;
/// Used as buffer for swap source amount estimations.
uint256 public sourceBuffer = 10000;
/// Maximum support swap size in rBTC
uint256 public maxSwapSize = 50 ether;
/// Nonce per borrower. Used for loan id creation.
mapping(address => uint256) public borrowerNonce;
// Rollover transaction costs around 0.0000168 rBTC, it is denominated in wrBTC.
uint256 public rolloverBaseReward = 16800000000000;
uint256 public rolloverFlexFeePercent = 0.1 ether; /// 0.1%
IWrbtcERC20 public wrbtcToken;
address public protocolTokenAddress;
/// 50% fee rebate /// potocolToken reward to user, it is worth % of trading/borrowing fee.
uint256 public feeRebatePercent = 50 * 10**18;
address public admin;
address public protocolAddress; // for modules interaction
mapping(address => bool) public userNotFirstTradeFlag; // The flag is set on the user's first trade or borrowing
mapping(address => address) public affiliatesUserReferrer; // User => referrer (affiliate)
mapping(address => EnumerableAddressSet.AddressSet) internal referralsList; // list of referral addresses that owned by the referrer
uint256 public minReferralsToPayout = 3;
mapping(address => uint256) public affiliateRewardsHeld; // Total affiliate SOV rewards that held in the protocol (Because the minimum referrals is less than the rule)
address public sovTokenAddress; // For affiliates SOV Bonus proccess
address public lockedSOVAddress;
/// 20% fee share of trading token fee /// Fee share of trading token fee for affiliate program.
uint256 public affiliateTradingTokenFeePercent = 20 * 10**18;
mapping(address => EnumerableAddressSet.AddressSet) internal affiliatesReferrerTokensList; // addresses of tokens in which commissions were paid to referrers
mapping(address => mapping(address => uint256)) public affiliatesReferrerBalances; // [referrerAddress][tokenAddress] is a referrer's token balance of accrued fees
/**
* @notice Add signature and target to storage.
* */
function _setTarget(bytes4 sig, address target) internal {
logicTargets[sig] = target;
if (target != address(0)) {
logicTargetsSet.addBytes32(bytes32(sig));
} else {
logicTargetsSet.removeBytes32(bytes32(sig));
}
}
}
/**
* @title The Loan Closing Events contract.
* @notice This contract code comes from bZx. bZx is a protocol for tokenized
* margin trading and lending https://bzx.network similar to the dYdX protocol.
*
* This contract contains the events for loan closing operations.
* */
contract LoanClosingsEvents {
/// topic0: 0x6349c1a02ec126f7f4fc6e6837e1859006e90e9901635c442d29271e77b96fb6
event CloseWithDeposit(
address indexed user,
address indexed lender,
bytes32 indexed loanId,
address closer,
address loanToken,
address collateralToken,
uint256 repayAmount,
uint256 collateralWithdrawAmount,
uint256 collateralToLoanRate,
uint256 currentMargin
);
/// topic0: 0x2ed7b29b4ca95cf3bb9a44f703872a66e6aa5e8f07b675fa9a5c124a1e5d7352
event CloseWithSwap(
address indexed user,
address indexed lender,
bytes32 indexed loanId,
address collateralToken,
address loanToken,
address closer,
uint256 positionCloseSize,
uint256 loanCloseAmount,
uint256 exitPrice, // one unit of collateralToken, denominated in loanToken
uint256 currentLeverage
);
/// topic0: 0x46fa03303782eb2f686515f6c0100f9a62dabe587b0d3f5a4fc0c822d6e532d3
event Liquidate(
address indexed user,
address indexed liquidator,
bytes32 indexed loanId,
address lender,
address loanToken,
address collateralToken,
uint256 repayAmount,
uint256 collateralWithdrawAmount,
uint256 collateralToLoanRate,
uint256 currentMargin
);
event swapExcess(bool shouldRefund, uint256 amount, uint256 amountInRbtc, uint256 threshold);
}
/**
* @title The Vault Controller contract.
* @notice This contract code comes from bZx. bZx is a protocol for tokenized margin
* trading and lending https://bzx.network similar to the dYdX protocol.
*
* This contract implements functionality to deposit and withdraw wrBTC and
* other tokens from the vault.
* */
contract VaultController is State {
using SafeERC20 for IERC20;
event VaultDeposit(address indexed asset, address indexed from, uint256 amount);
event VaultWithdraw(address indexed asset, address indexed to, uint256 amount);
/**
* @notice Deposit wrBTC into the vault.
*
* @param from The address of the account paying the deposit.
* @param value The amount of wrBTC tokens to transfer.
*/
function vaultEtherDeposit(address from, uint256 value) internal {
IWrbtcERC20 _wrbtcToken = wrbtcToken;
_wrbtcToken.deposit.value(value)();
emit VaultDeposit(address(_wrbtcToken), from, value);
}
/**
* @notice Withdraw wrBTC from the vault.
*
* @param to The address of the recipient.
* @param value The amount of wrBTC tokens to transfer.
*/
function vaultEtherWithdraw(address to, uint256 value) internal {
if (value != 0) {
IWrbtcERC20 _wrbtcToken = wrbtcToken;
uint256 balance = address(this).balance;
if (value > balance) {
_wrbtcToken.withdraw(value - balance);
}
Address.sendValue(to, value);
emit VaultWithdraw(address(_wrbtcToken), to, value);
}
}
/**
* @notice Deposit tokens into the vault.
*
* @param token The address of the token instance.
* @param from The address of the account paying the deposit.
* @param value The amount of tokens to transfer.
*/
function vaultDeposit(
address token,
address from,
uint256 value
) internal {
if (value != 0) {
IERC20(token).safeTransferFrom(from, address(this), value);
emit VaultDeposit(token, from, value);
}
}
/**
* @notice Withdraw tokens from the vault.
*
* @param token The address of the token instance.
* @param to The address of the recipient.
* @param value The amount of tokens to transfer.
*/
function vaultWithdraw(
address token,
address to,
uint256 value
) internal {
if (value != 0) {
IERC20(token).safeTransfer(to, value);
emit VaultWithdraw(token, to, value);
}
}
/**
* @notice Transfer tokens from an account into another one.
*
* @param token The address of the token instance.
* @param from The address of the account paying.
* @param to The address of the recipient.
* @param value The amount of tokens to transfer.
*/
function vaultTransfer(
address token,
address from,
address to,
uint256 value
) internal {
if (value != 0) {
if (from == address(this)) {
IERC20(token).safeTransfer(to, value);
} else {
IERC20(token).safeTransferFrom(from, to, value);
}
}
}
/**
* @notice Approve an allowance of tokens to be spent by an account.
*
* @param token The address of the token instance.
* @param to The address of the spender.
* @param value The amount of tokens to allow.
*/
function vaultApprove(
address token,
address to,
uint256 value
) internal {
if (value != 0 && IERC20(token).allowance(address(this), to) != 0) {
IERC20(token).safeApprove(to, 0);
}
IERC20(token).safeApprove(to, value);
}
}
/**
* @title The Fees Events contract.
* @notice This contract code comes from bZx. bZx is a protocol for tokenized
* margin trading and lending https://bzx.network similar to the dYdX protocol.
*
* This contract contains the events for fee payments.
* */
contract FeesEvents {
event PayLendingFee(address indexed payer, address indexed token, uint256 amount);
event PayTradingFee(address indexed payer, address indexed token, bytes32 indexed loanId, uint256 amount);
event PayBorrowingFee(address indexed payer, address indexed token, bytes32 indexed loanId, uint256 amount);
event EarnReward(address indexed receiver, address indexed token, bytes32 indexed loanId, uint256 amount);
}
/**
* @title The Protocol Token User contract.
* @notice This contract code comes from bZx. bZx is a protocol for tokenized margin
* trading and lending https://bzx.network similar to the dYdX protocol.
*
* This contract implements functionality to withdraw protocol tokens.
* */
contract ProtocolTokenUser is State {
using SafeERC20 for IERC20;
/**
* @notice Internal function to withdraw an amount of protocol tokens from this contract.
*
* @param receiver The address of the recipient.
* @param amount The amount of tokens to withdraw.
*
* @return The protocol token address.
* @return Withdrawal success (true/false).
* */
function _withdrawProtocolToken(address receiver, uint256 amount) internal returns (address, bool) {
uint256 withdrawAmount = amount;
uint256 tokenBalance = protocolTokenHeld;
if (withdrawAmount > tokenBalance) {
withdrawAmount = tokenBalance;
}
if (withdrawAmount == 0) {
return (protocolTokenAddress, false);
}
protocolTokenHeld = tokenBalance.sub(withdrawAmount);
IERC20(protocolTokenAddress).safeTransfer(receiver, withdrawAmount);
return (protocolTokenAddress, true);
}
}
/**
* @title The Fees Helper contract.
* @notice This contract code comes from bZx. bZx is a protocol for tokenized margin
* trading and lending https://bzx.network similar to the dYdX protocol.
*
* This contract calculates and pays lending/borrow fees and rewards.
* */
contract FeesHelper is State, ProtocolTokenUser, FeesEvents {
using SafeERC20 for IERC20;
/**
* @notice Calculate trading fee.
* @param feeTokenAmount The amount of tokens to trade.
* @return The fee of the trade.
* */
function _getTradingFee(uint256 feeTokenAmount) internal view returns (uint256) {
return feeTokenAmount.mul(tradingFeePercent).divCeil(10**20);
}
/*
// p3.9 from bzx peckshield-audit-report-bZxV2-v1.0rc1.pdf
// cannot be applied solely nor with LoanOpenings.sol as it drives to some other tests failure
function _getTradingFee(uint256 feeTokenAmount) internal view returns (uint256) {
uint256 collateralAmountRequired =
feeTokenAmount.mul(10**20).divCeil(
10**20 - tradingFeePercent // never will overflow
);
return collateralAmountRequired.sub(feeTokenAmount);
}*/
/**
* @notice Calculate the loan origination fee.
* @param feeTokenAmount The amount of tokens to borrow.
* @return The fee of the loan.
* */
function _getBorrowingFee(uint256 feeTokenAmount) internal view returns (uint256) {
return feeTokenAmount.mul(borrowingFeePercent).divCeil(10**20);
/*
// p3.9 from bzx peckshield-audit-report-bZxV2-v1.0rc1.pdf
// cannot be applied solely nor with LoanOpenings.sol as it drives to some other tests failure
uint256 collateralAmountRequired =
feeTokenAmount.mul(10**20).divCeil(
10**20 - borrowingFeePercent // never will overflow
);
return collateralAmountRequired.sub(feeTokenAmount);*/
}
/**
* @dev settles the trading fee and pays the token reward to the user.
* @param referrer the affiliate referrer address to send the reward to
* @param feeToken the address of the token in which the trading fee is paid
* @return affiliatesBonusSOVAmount the total SOV amount that is distributed to the referrer
* @return affiliatesBonusTokenAmount the total Token Base on the trading fee pairs that is distributed to the referrer
* */
function _payTradingFeeToAffiliate(
address referrer,
address trader,
address feeToken,
uint256 tradingFee
) internal returns (uint256 affiliatesBonusSOVAmount, uint256 affiliatesBonusTokenAmount) {
(affiliatesBonusSOVAmount, affiliatesBonusTokenAmount) = ProtocolAffiliatesInterface(protocolAddress)
.payTradingFeeToAffiliatesReferrer(referrer, trader, feeToken, tradingFee);
}
/**
* @notice Settle the trading fee and pay the token reward to the user.
* @param user The address to send the reward to.
* @param loanId The Id of the associated loan - used for logging only.
* @param feeToken The address of the token in which the trading fee is paid.
* */
function _payTradingFee(
address user,
bytes32 loanId,
address feeToken,
uint256 tradingFee
) internal {
uint256 protocolTradingFee = tradingFee; //trading fee paid to protocol
if (tradingFee != 0) {
if (affiliatesUserReferrer[user] != address(0)) {
_payTradingFeeToAffiliate(affiliatesUserReferrer[user], user, feeToken, protocolTradingFee);
protocolTradingFee = (protocolTradingFee.sub(protocolTradingFee.mul(affiliateFeePercent).div(10**20))).sub(
protocolTradingFee.mul(affiliateTradingTokenFeePercent).div(10**20)
);
}
/// Increase the storage variable keeping track of the accumulated fees.
tradingFeeTokensHeld[feeToken] = tradingFeeTokensHeld[feeToken].add(protocolTradingFee);
emit PayTradingFee(user, feeToken, loanId, protocolTradingFee);
/// Pay the token reward to the user.
_payFeeReward(user, loanId, feeToken, tradingFee);
}
}
/**
* @notice Settle the borrowing fee and pay the token reward to the user.
* @param user The address to send the reward to.
* @param loanId The Id of the associated loan - used for logging only.
* @param feeToken The address of the token in which the borrowig fee is paid.
* @param borrowingFee The height of the fee.
* */
function _payBorrowingFee(
address user,
bytes32 loanId,
address feeToken,
uint256 borrowingFee
) internal {
if (borrowingFee != 0) {
/// Increase the storage variable keeping track of the accumulated fees.
borrowingFeeTokensHeld[feeToken] = borrowingFeeTokensHeld[feeToken].add(borrowingFee);
emit PayBorrowingFee(user, feeToken, loanId, borrowingFee);
/// Pay the token reward to the user.
_payFeeReward(user, loanId, feeToken, borrowingFee);
}
}
/**
* @notice Settle the lending fee (based on the interest). Pay no token reward to the user.
* @param user The address to send the reward to.
* @param feeToken The address of the token in which the lending fee is paid.
* @param lendingFee The height of the fee.
* */
function _payLendingFee(
address user,
address feeToken,
uint256 lendingFee
) internal {
if (lendingFee != 0) {
/// Increase the storage variable keeping track of the accumulated fees.
lendingFeeTokensHeld[feeToken] = lendingFeeTokensHeld[feeToken].add(lendingFee);
emit PayLendingFee(user, feeToken, lendingFee);
//// NOTE: Lenders do not receive a fee reward ////
}
}
/// Settle and pay borrowers based on the fees generated by their interest payments.
function _settleFeeRewardForInterestExpense(
LoanInterest storage loanInterestLocal,
bytes32 loanId,
address feeToken,
address user,
uint256 interestTime
) internal {
/// This represents the fee generated by a borrower's interest payment.
uint256 interestExpenseFee =
interestTime.sub(loanInterestLocal.updatedTimestamp).mul(loanInterestLocal.owedPerDay).mul(lendingFeePercent).div(
1 days * 10**20
);
loanInterestLocal.updatedTimestamp = interestTime;
if (interestExpenseFee != 0) {
_payFeeReward(user, loanId, feeToken, interestExpenseFee);
}
}
/**
* @notice Pay the potocolToken reward to user. The reward is worth 50% of the trading/borrowing fee.
* @param user The address to send the reward to.
* @param loanId The Id of the associeated loan - used for logging only.
* @param feeToken The address of the token in which the trading/borrowing fee was paid.
* @param feeAmount The height of the fee.
* */
function _payFeeReward(
address user,
bytes32 loanId,
address feeToken,
uint256 feeAmount
) internal {
uint256 rewardAmount;
address _priceFeeds = priceFeeds;
/// Note: this should be refactored.
/// Calculate the reward amount, querying the price feed.
(bool success, bytes memory data) =
_priceFeeds.staticcall(
abi.encodeWithSelector(
IPriceFeeds(_priceFeeds).queryReturn.selector,
feeToken,
protocolTokenAddress, /// Price rewards using BZRX price rather than vesting token price.
feeAmount.mul(feeRebatePercent).div(10**20)
)
);
assembly {
if eq(success, 1) {
rewardAmount := mload(add(data, 32))
}
}
if (rewardAmount != 0) {
address rewardToken;
(rewardToken, success) = _withdrawProtocolToken(user, rewardAmount);
if (success) {
protocolTokenPaid = protocolTokenPaid.add(rewardAmount);
emit EarnReward(user, rewardToken, loanId, rewardAmount);
}
}
}
}
/**
* @title The Interest User contract.
* @notice This contract code comes from bZx. bZx is a protocol for tokenized margin
* trading and lending https://bzx.network similar to the dYdX protocol.
*
* This contract pays loan interests.
* */
contract InterestUser is VaultController, FeesHelper {
using SafeERC20 for IERC20;
/**
* @notice Internal function to pay interest of a loan.
* @dev Calls _payInterestTransfer internal function to transfer tokens.
* @param lender The account address of the lender.
* @param interestToken The token address to pay interest with.
* */
function _payInterest(address lender, address interestToken) internal {
LenderInterest storage lenderInterestLocal = lenderInterest[lender][interestToken];
uint256 interestOwedNow = 0;
if (lenderInterestLocal.owedPerDay != 0 && lenderInterestLocal.updatedTimestamp != 0) {
interestOwedNow = block.timestamp.sub(lenderInterestLocal.updatedTimestamp).mul(lenderInterestLocal.owedPerDay).div(1 days);
lenderInterestLocal.updatedTimestamp = block.timestamp;
if (interestOwedNow > lenderInterestLocal.owedTotal) interestOwedNow = lenderInterestLocal.owedTotal;
if (interestOwedNow != 0) {
lenderInterestLocal.paidTotal = lenderInterestLocal.paidTotal.add(interestOwedNow);
lenderInterestLocal.owedTotal = lenderInterestLocal.owedTotal.sub(interestOwedNow);
_payInterestTransfer(lender, interestToken, interestOwedNow);
}
} else {
lenderInterestLocal.updatedTimestamp = block.timestamp;
}
}
/**
* @notice Internal function to transfer tokens for the interest of a loan.
* @param lender The account address of the lender.
* @param interestToken The token address to pay interest with.
* @param interestOwedNow The amount of interest to pay.
* */
function _payInterestTransfer(
address lender,
address interestToken,
uint256 interestOwedNow
) internal {
uint256 lendingFee = interestOwedNow.mul(lendingFeePercent).div(10**20);
//TODO: refactor: data incapsulation violation and DRY design principles
//uint256 lendingFee = interestOwedNow.mul(lendingFeePercent).divCeil(10**20); is better but produces errors in tests because of this
_payLendingFee(lender, interestToken, lendingFee);
/// Transfers the interest to the lender, less the interest fee.
vaultWithdraw(interestToken, lender, interestOwedNow.sub(lendingFee));
}
}
/**
* @title The Liquidation Helper contract.
* @notice This contract code comes from bZx. bZx is a protocol for tokenized margin
* trading and lending https://bzx.network similar to the dYdX protocol.
*
* This contract computes the liquidation amount.
* */
contract LiquidationHelper is State {
/**
* @notice Compute how much needs to be liquidated in order to restore the
* desired margin (maintenance + 5%).
*
* @param principal The total borrowed amount (in loan tokens).
* @param collateral The collateral (in collateral tokens).
* @param currentMargin The current margin.
* @param maintenanceMargin The maintenance (minimum) margin.
* @param collateralToLoanRate The exchange rate from collateral to loan
* tokens.
*
* @return maxLiquidatable The collateral you can get liquidating.
* @return maxSeizable The loan you available for liquidation.
* @return incentivePercent The discount on collateral.
* */
function _getLiquidationAmounts(
uint256 principal,
uint256 collateral,
uint256 currentMargin,
uint256 maintenanceMargin,
uint256 collateralToLoanRate
)
internal
view
returns (
uint256 maxLiquidatable,
uint256 maxSeizable,
uint256 incentivePercent
)
{
incentivePercent = liquidationIncentivePercent;
if (currentMargin > maintenanceMargin || collateralToLoanRate == 0) {
return (maxLiquidatable, maxSeizable, incentivePercent);
} else if (currentMargin <= incentivePercent) {
return (principal, collateral, currentMargin);
}
/// 5 percentage points above maintenance.
uint256 desiredMargin = maintenanceMargin.add(5 ether);
/// maxLiquidatable = ((1 + desiredMargin)*principal - collateralToLoanRate*collateral) / (desiredMargin - 0.05)
maxLiquidatable = desiredMargin.add(10**20).mul(principal).div(10**20);
maxLiquidatable = maxLiquidatable.sub(collateral.mul(collateralToLoanRate).div(10**18));
maxLiquidatable = maxLiquidatable.mul(10**20).div(desiredMargin.sub(incentivePercent));
if (maxLiquidatable > principal) {
maxLiquidatable = principal;
}
/// maxSeizable = maxLiquidatable * (1 + incentivePercent) / collateralToLoanRate
maxSeizable = maxLiquidatable.mul(incentivePercent.add(10**20));
maxSeizable = maxSeizable.div(collateralToLoanRate).div(100);
if (maxSeizable > collateral) {
maxSeizable = collateral;
}
return (maxLiquidatable, maxSeizable, incentivePercent);
}
}
/**
* @title The Swaps Events contract.
* @notice This contract code comes from bZx. bZx is a protocol for tokenized
* margin trading and lending https://bzx.network similar to the dYdX protocol.
*
* This contract contains the events for swap operations.
* */
contract SwapsEvents {
event LoanSwap(
bytes32 indexed loanId,
address indexed sourceToken,
address indexed destToken,
address borrower,
uint256 sourceAmount,
uint256 destAmount
);
event ExternalSwap(
address indexed user,
address indexed sourceToken,
address indexed destToken,
uint256 sourceAmount,
uint256 destAmount
);
}
/**
* @title Perform token swaps for loans and trades.
* */
contract SwapsUser is State, SwapsEvents, FeesHelper {
/**
* @notice Internal loan swap.
*
* @param loanId The ID of the loan.
* @param sourceToken The address of the source tokens.
* @param destToken The address of destiny tokens.
* @param user The user address.
* @param minSourceTokenAmount The minimum amount of source tokens to swap.
* @param maxSourceTokenAmount The maximum amount of source tokens to swap.
* @param requiredDestTokenAmount The required amount of destination tokens.
* @param bypassFee To bypass or not the fee.
* @param loanDataBytes The payload for the call. These loan DataBytes are
* additional loan data (not in use for token swaps).
*
* @return destTokenAmountReceived
* @return sourceTokenAmountUsed
* @return sourceToDestSwapRate
* */
function _loanSwap(
bytes32 loanId,
address sourceToken,
address destToken,
address user,
uint256 minSourceTokenAmount,
uint256 maxSourceTokenAmount,
uint256 requiredDestTokenAmount,
bool bypassFee,
bytes memory loanDataBytes
)
internal
returns (
uint256 destTokenAmountReceived,
uint256 sourceTokenAmountUsed,
uint256 sourceToDestSwapRate
)
{
(destTokenAmountReceived, sourceTokenAmountUsed) = _swapsCall(
[
sourceToken,
destToken,
address(this), // receiver
address(this), // returnToSender
user
],
[minSourceTokenAmount, maxSourceTokenAmount, requiredDestTokenAmount],
loanId,
bypassFee,
loanDataBytes
);
/// Will revert if swap size too large.
_checkSwapSize(sourceToken, sourceTokenAmountUsed);
/// Will revert if disagreement found.
sourceToDestSwapRate = IPriceFeeds(priceFeeds).checkPriceDisagreement(
sourceToken,
destToken,
sourceTokenAmountUsed,
destTokenAmountReceived,
maxDisagreement
);
emit LoanSwap(loanId, sourceToken, destToken, user, sourceTokenAmountUsed, destTokenAmountReceived);
}
/**
* @notice Calculate amount of source and destiny tokens.
*
* @dev Wrapper for _swapsCall_internal function.
*
* @param addrs The array of addresses.
* @param vals The array of values.
* @param loanId The Id of the associated loan.
* @param miscBool True/false to bypassFee.
* @param loanDataBytes Additional loan data (not in use yet).
*
* @return destTokenAmountReceived The amount of destiny tokens received.
* @return sourceTokenAmountUsed The amount of source tokens used.
* */
function _swapsCall(
address[5] memory addrs,
uint256[3] memory vals,
bytes32 loanId,
bool miscBool, /// bypassFee
bytes memory loanDataBytes
) internal returns (uint256, uint256) {
/// addrs[0]: sourceToken
/// addrs[1]: destToken
/// addrs[2]: receiver
/// addrs[3]: returnToSender
/// addrs[4]: user
/// vals[0]: minSourceTokenAmount
/// vals[1]: maxSourceTokenAmount
/// vals[2]: requiredDestTokenAmount
require(vals[0] != 0 || vals[1] != 0, "min or max source token amount needs to be set");
if (vals[1] == 0) {
vals[1] = vals[0];
}
require(vals[0] <= vals[1], "sourceAmount larger than max");
uint256 destTokenAmountReceived;
uint256 sourceTokenAmountUsed;
uint256 tradingFee;
if (!miscBool) {
/// bypassFee
if (vals[2] == 0) {
/// condition: vals[0] will always be used as sourceAmount
tradingFee = _getTradingFee(vals[0]);
if (tradingFee != 0) {
_payTradingFee(
addrs[4], /// user
loanId,
addrs[0], /// sourceToken
tradingFee
);
vals[0] = vals[0].sub(tradingFee);
}
} else {
/// Condition: unknown sourceAmount will be used.
tradingFee = _getTradingFee(vals[2]);
if (tradingFee != 0) {
vals[2] = vals[2].add(tradingFee);
}
}
}
require(loanDataBytes.length == 0, "invalid state");
(destTokenAmountReceived, sourceTokenAmountUsed) = _swapsCall_internal(addrs, vals);
if (vals[2] == 0) {
/// There's no minimum destTokenAmount, but all of vals[0]
/// (minSourceTokenAmount) must be spent.
require(sourceTokenAmountUsed == vals[0], "swap too large to fill");
if (tradingFee != 0) {
sourceTokenAmountUsed = sourceTokenAmountUsed.add(tradingFee);
}
} else {
/// There's a minimum destTokenAmount required, but
/// sourceTokenAmountUsed won't be greater
/// than vals[1] (maxSourceTokenAmount)
require(sourceTokenAmountUsed <= vals[1], "swap fill too large");
require(destTokenAmountReceived >= vals[2], "insufficient swap liquidity");
if (tradingFee != 0) {
_payTradingFee(
addrs[4], /// user
loanId, /// loanId,
addrs[1], /// destToken
tradingFee
);
destTokenAmountReceived = destTokenAmountReceived.sub(tradingFee);
}
}
return (destTokenAmountReceived, sourceTokenAmountUsed);
}
/**
* @notice Calculate amount of source and destiny tokens.
*
* @dev Calls swapsImpl::internalSwap
*
* @param addrs The array of addresses.
* @param vals The array of values.
*
* @return destTokenAmountReceived The amount of destiny tokens received.
* @return sourceTokenAmountUsed The amount of source tokens used.
* */
function _swapsCall_internal(address[5] memory addrs, uint256[3] memory vals)
internal
returns (uint256 destTokenAmountReceived, uint256 sourceTokenAmountUsed)
{
bytes memory data =
abi.encodeWithSelector(
ISwapsImpl(swapsImpl).internalSwap.selector,
addrs[0], /// sourceToken
addrs[1], /// destToken
addrs[2], /// receiverAddress
addrs[3], /// returnToSenderAddress
vals[0], /// minSourceTokenAmount
vals[1], /// maxSourceTokenAmount
vals[2] /// requiredDestTokenAmount
);
bool success;
(success, data) = swapsImpl.delegatecall(data);
require(success, "swap failed");
assembly {
destTokenAmountReceived := mload(add(data, 32))
sourceTokenAmountUsed := mload(add(data, 64))
}
}
/**
* @notice Calculate expected amount of destiny tokens.
*
* @dev Calls swapsImpl::internalExpectedReturn
*
* @param sourceToken The address of the source tokens.
* @param destToken The address of the destiny tokens.
* @param sourceTokenAmount The amount of the source tokens.
*
* @param destTokenAmount The amount of destiny tokens.
* */
function _swapsExpectedReturn(
address sourceToken,
address destToken,
uint256 sourceTokenAmount
) internal view returns (uint256 destTokenAmount) {
destTokenAmount = ISwapsImpl(swapsImpl).internalExpectedReturn(
sourceToken,
destToken,
sourceTokenAmount,
sovrynSwapContractRegistryAddress
);
}
/**
* @notice Verify that the amount of tokens are under the swap limit.
*
* @dev Calls priceFeeds::amountInEth
*
* @param tokenAddress The address of the token to calculate price.
* @param amount The amount of tokens to calculate price.
* */
function _checkSwapSize(address tokenAddress, uint256 amount) internal view {
uint256 _maxSwapSize = maxSwapSize;
if (_maxSwapSize != 0) {
uint256 amountInEth;
if (tokenAddress == address(wrbtcToken)) {
amountInEth = amount;
} else {
amountInEth = IPriceFeeds(priceFeeds).amountInEth(tokenAddress, amount);
}
require(amountInEth <= _maxSwapSize, "swap too large");
}
}
}
/**
* @title The Reward Helper contract.
* @notice This contract calculates the reward for rollover transactions.
*
* A rollover is a renewal of a deposit. Instead of liquidating a deposit
* on maturity, you can roll it over into a new deposit. The outstanding
* principal of the old deposit is rolled over with or without the interest
* outstanding on it.
* */
contract RewardHelper is State {
using SafeMath for uint256;
/**
* @notice Calculate the reward of a rollover transaction.
*
* @param collateralToken The address of the collateral token.
* @param loanToken The address of the loan token.
* @param positionSize The amount of value of the position.
*
* @return The base fee + the flex fee.
*/
function _getRolloverReward(
address collateralToken,
address loanToken,
uint256 positionSize
) internal view returns (uint256 reward) {
uint256 positionSizeInCollateralToken = IPriceFeeds(priceFeeds).queryReturn(loanToken, collateralToken, positionSize);
uint256 rolloverBaseRewardInCollateralToken =
IPriceFeeds(priceFeeds).queryReturn(address(wrbtcToken), collateralToken, rolloverBaseReward);
return
rolloverBaseRewardInCollateralToken
.mul(2) /// baseFee
.add(positionSizeInCollateralToken.mul(rolloverFlexFeePercent).div(10**20)); /// flexFee = 0.1% of position size
}
}
/**
* @title LoanClosingsWith contract.
* @notice Close a loan w/deposit, close w/swap. There are 2 functions for ending a loan on the
* protocol contract: closeWithSwap and closeWithDeposit. Margin trade
* positions are always closed with a swap.
*
* Loans are liquidated if the position goes below margin maintenance.
* */
contract LoanClosingsWith is
LoanClosingsEvents,
VaultController,
InterestUser,
SwapsUser, /*LiquidationHelper,*/
RewardHelper
{
//0.00001 BTC, would be nicer in State.sol, but would require a redeploy of the complete protocol, so adding it here instead
//because it's not shared state anyway and only used by this contract
uint256 public constant paySwapExcessToBorrowerThreshold = 10000000000000;
enum CloseTypes { Deposit, Swap, Liquidation }
constructor() public {}
function() external {
revert("fallback not allowed");
}
function initialize(address target) external onlyOwner {
_setTarget(this.closeWithDeposit.selector, target);
_setTarget(this.closeWithSwap.selector, target);
}
/**
* @notice Closes a loan by doing a deposit.
*
* @dev Public wrapper for _closeWithDeposit internal function.
*
* @param loanId The id of the loan.
* @param receiver The receiver of the remainder.
* @param depositAmount Defines how much of the position should be closed.
* It is denominated in loan tokens. (e.g. rBTC on a iSUSD contract).
* If depositAmount > principal, the complete loan will be closed
* else deposit amount (partial closure).
*
* @return loanCloseAmount The amount of the collateral token of the loan.
* @return withdrawAmount The withdraw amount in the collateral token.
* @return withdrawToken The loan token address.
* */
function closeWithDeposit(
bytes32 loanId,
address receiver,
uint256 depositAmount /// Denominated in loanToken.
)
public
payable
nonReentrant
returns (
uint256 loanCloseAmount,
uint256 withdrawAmount,
address withdrawToken
)
{
return _closeWithDeposit(loanId, receiver, depositAmount);
}
/**
* @notice Close a position by swapping the collateral back to loan tokens
* paying the lender and withdrawing the remainder.
*
* @dev Public wrapper for _closeWithSwap internal function.
*
* @param loanId The id of the loan.
* @param receiver The receiver of the remainder (unused collateral + profit).
* @param swapAmount Defines how much of the position should be closed and
* is denominated in collateral tokens.
* If swapAmount >= collateral, the complete position will be closed.
* Else if returnTokenIsCollateral, (swapAmount/collateral) * principal will be swapped (partial closure).
* Else coveredPrincipal
* @param returnTokenIsCollateral Defines if the remainder should be paid out
* in collateral tokens or underlying loan tokens.
*
* @return loanCloseAmount The amount of the collateral token of the loan.
* @return withdrawAmount The withdraw amount in the collateral token.
* @return withdrawToken The loan token address.
* */
function closeWithSwap(
bytes32 loanId,
address receiver,
uint256 swapAmount, // denominated in collateralToken
bool returnTokenIsCollateral, // true: withdraws collateralToken, false: withdraws loanToken
bytes memory // for future use /*loanDataBytes*/
)
public
nonReentrant
returns (
uint256 loanCloseAmount,
uint256 withdrawAmount,
address withdrawToken
)
{
return
_closeWithSwap(
loanId,
receiver,
swapAmount,
returnTokenIsCollateral,
"" /// loanDataBytes
);
}
/**
* @notice Internal function for closing a loan by doing a deposit.
*
* @param loanId The id of the loan.
* @param receiver The receiver of the remainder.
* @param depositAmount Defines how much of the position should be closed.
* It is denominated in loan tokens.
* If depositAmount > principal, the complete loan will be closed
* else deposit amount (partial closure).
*
* @return loanCloseAmount The amount of the collateral token of the loan.
* @return withdrawAmount The withdraw amount in the collateral token.
* @return withdrawToken The loan token address.
* */
function _closeWithDeposit(
bytes32 loanId,
address receiver,
uint256 depositAmount /// Denominated in loanToken.
)
internal
returns (
uint256 loanCloseAmount,
uint256 withdrawAmount,
address withdrawToken
)
{
require(depositAmount != 0, "depositAmount == 0");
Loan storage loanLocal = loans[loanId];
LoanParams storage loanParamsLocal = loanParams[loanLocal.loanParamsId];
_checkAuthorized(loanLocal, loanParamsLocal);
/// Can't close more than the full principal.
loanCloseAmount = depositAmount > loanLocal.principal ? loanLocal.principal : depositAmount;
uint256 loanCloseAmountLessInterest = _settleInterestToPrincipal(loanLocal, loanParamsLocal, loanCloseAmount, receiver);
if (loanCloseAmountLessInterest != 0) {
_returnPrincipalWithDeposit(loanParamsLocal.loanToken, loanLocal.lender, loanCloseAmountLessInterest);
}
if (loanCloseAmount == loanLocal.principal) {
withdrawAmount = loanLocal.collateral;
} else {
withdrawAmount = loanLocal.collateral.mul(loanCloseAmount).div(loanLocal.principal);
}
withdrawToken = loanParamsLocal.collateralToken;
if (withdrawAmount != 0) {
loanLocal.collateral = loanLocal.collateral.sub(withdrawAmount);
_withdrawAsset(withdrawToken, receiver, withdrawAmount);
}
_finalizeClose(
loanLocal,
loanParamsLocal,
loanCloseAmount,
withdrawAmount, /// collateralCloseAmount
0, /// collateralToLoanSwapRate
CloseTypes.Deposit
);
}
/**
* @notice Internal function for closing a position by swapping the
* collateral back to loan tokens, paying the lender and withdrawing
* the remainder.
*
* @param loanId The id of the loan.
* @param receiver The receiver of the remainder (unused collatral + profit).
* @param swapAmount Defines how much of the position should be closed and
* is denominated in collateral tokens.
* If swapAmount >= collateral, the complete position will be closed.
* Else if returnTokenIsCollateral, (swapAmount/collateral) * principal will be swapped (partial closure).
* Else coveredPrincipal
* @param returnTokenIsCollateral Defines if the remainder should be paid
* out in collateral tokens or underlying loan tokens.
*
* @return loanCloseAmount The amount of the collateral token of the loan.
* @return withdrawAmount The withdraw amount in the collateral token.
* @return withdrawToken The loan token address.
* */
function _closeWithSwap(
bytes32 loanId,
address receiver,
uint256 swapAmount,
bool returnTokenIsCollateral,
bytes memory loanDataBytes
)
internal
returns (
uint256 loanCloseAmount,
uint256 withdrawAmount,
address withdrawToken
)
{
require(swapAmount != 0, "swapAmount == 0");
Loan storage loanLocal = loans[loanId];
LoanParams storage loanParamsLocal = loanParams[loanLocal.loanParamsId];
_checkAuthorized(loanLocal, loanParamsLocal);
/// Can't swap more than collateral.
swapAmount = swapAmount > loanLocal.collateral ? loanLocal.collateral : swapAmount;
uint256 loanCloseAmountLessInterest;
if (swapAmount == loanLocal.collateral || returnTokenIsCollateral) {
/// loanCloseAmountLessInterest will be passed as required amount amount of destination tokens.
/// this means, the actual swapAmount passed to the swap contract does not matter at all.
/// the source token amount will be computed depending on the required amount amount of destination tokens.
loanCloseAmount = swapAmount == loanLocal.collateral
? loanLocal.principal
: loanLocal.principal.mul(swapAmount).div(loanLocal.collateral);
require(loanCloseAmount != 0, "loanCloseAmount == 0");
/// Computes the interest refund for the borrower and sends it to the lender to cover part of the principal.
loanCloseAmountLessInterest = _settleInterestToPrincipal(loanLocal, loanParamsLocal, loanCloseAmount, receiver);
} else {
/// loanCloseAmount is calculated after swap; for this case we want to swap the entire source amount
/// and determine the loanCloseAmount and withdraw amount based on that.
loanCloseAmountLessInterest = 0;
}
uint256 coveredPrincipal;
uint256 usedCollateral;
/// swapAmount repurposed for collateralToLoanSwapRate to avoid stack too deep error.
(coveredPrincipal, usedCollateral, withdrawAmount, swapAmount) = _coverPrincipalWithSwap(
loanLocal,
loanParamsLocal,
swapAmount, /// The amount of source tokens to swap (only matters if !returnTokenIsCollateral or loanCloseAmountLessInterest = 0)
loanCloseAmountLessInterest, /// This is the amount of destination tokens we want to receive (only matters if returnTokenIsCollateral)
returnTokenIsCollateral,
loanDataBytes
);
if (loanCloseAmountLessInterest == 0) {
/// Condition prior to swap: swapAmount != loanLocal.collateral && !returnTokenIsCollateral
/// Amounts that is closed.
loanCloseAmount = coveredPrincipal;
if (coveredPrincipal != loanLocal.principal) {
loanCloseAmount = loanCloseAmount.mul(usedCollateral).div(loanLocal.collateral);
}
require(loanCloseAmount != 0, "loanCloseAmount == 0");
/// Amount that is returned to the lender.
loanCloseAmountLessInterest = _settleInterestToPrincipal(loanLocal, loanParamsLocal, loanCloseAmount, receiver);
/// Remaining amount withdrawn to the receiver.
withdrawAmount = withdrawAmount.add(coveredPrincipal).sub(loanCloseAmountLessInterest);
} else {
/// Pay back the amount which was covered by the swap.
loanCloseAmountLessInterest = coveredPrincipal;
}
require(loanCloseAmountLessInterest != 0, "closeAmount is 0 after swap");
/// Reduce the collateral by the amount which was swapped for the closure.
if (usedCollateral != 0) {
loanLocal.collateral = loanLocal.collateral.sub(usedCollateral);
}
/// Repays principal to lender.
/// The lender always gets back an ERC20 (even wrbtc), so we call
/// withdraw directly rather than use the _withdrawAsset helper function.
vaultWithdraw(loanParamsLocal.loanToken, loanLocal.lender, loanCloseAmountLessInterest);
withdrawToken = returnTokenIsCollateral ? loanParamsLocal.collateralToken : loanParamsLocal.loanToken;
if (withdrawAmount != 0) {
_withdrawAsset(withdrawToken, receiver, withdrawAmount);
}
_finalizeClose(
loanLocal,
loanParamsLocal,
loanCloseAmount,
usedCollateral,
swapAmount, /// collateralToLoanSwapRate
CloseTypes.Swap
);
}
/**
* @notice Check sender is borrower or delegatee and loan id exists.
*
* @param loanLocal The loan object.
* @param loanParamsLocal The loan params.
* */
function _checkAuthorized(Loan memory loanLocal, LoanParams memory loanParamsLocal) internal view {
require(loanLocal.active, "loan is closed");
require(msg.sender == loanLocal.borrower || delegatedManagers[loanLocal.id][msg.sender], "unauthorized");
require(loanParamsLocal.id != 0, "loanParams not exists");
}
/**
* @notice Compute the interest which needs to be refunded to the borrower
* based on the amount he's closing and either subtracts it from the
* amount which still needs to be paid back (in case outstanding
* amount > interest) or withdraws the excess to the borrower
* (in case interest > outstanding).
*
* @param loanLocal The loan object.
* @param loanParamsLocal The loan params.
* @param loanCloseAmount The amount to be closed (base for the computation).
* @param receiver The address of the receiver (usually the borrower).
*
* @return loanCloseAmountLessInterest The outstanding loan.
* */
function _settleInterestToPrincipal(
Loan memory loanLocal,
LoanParams memory loanParamsLocal,
uint256 loanCloseAmount,
address receiver
) internal returns (uint256) {
uint256 loanCloseAmountLessInterest = loanCloseAmount;
/// Compute the interest which neeeds to be refunded to the borrower (because full interest is paid on loan).
uint256 interestRefundToBorrower = _settleInterest(loanParamsLocal, loanLocal, loanCloseAmountLessInterest);
uint256 interestAppliedToPrincipal;
/// If the outstanding loan is bigger than the interest to be refunded, reduce the amount to be paid back / closed by the interest.
if (loanCloseAmountLessInterest >= interestRefundToBorrower) {
/// Apply all of borrower interest refund torwards principal.
interestAppliedToPrincipal = interestRefundToBorrower;
/// Principal needed is reduced by this amount.
loanCloseAmountLessInterest -= interestRefundToBorrower;
/// No interest refund remaining.
interestRefundToBorrower = 0;
} else {
/// If the interest refund is bigger than the outstanding loan, the user needs to get back the interest.
/// Principal fully covered by excess interest.
interestAppliedToPrincipal = loanCloseAmountLessInterest;
/// Amount refunded is reduced by this amount.
interestRefundToBorrower -= loanCloseAmountLessInterest;
/// Principal fully covered by excess interest.
loanCloseAmountLessInterest = 0;
if (interestRefundToBorrower != 0) {
/// Refund overage.
_withdrawAsset(loanParamsLocal.loanToken, receiver, interestRefundToBorrower);
}
}
/// Pay the interest to the lender.
/// Note: this is a waste of gas, because the loanCloseAmountLessInterest
/// is withdrawn to the lender, too. It could be done at once.
if (interestAppliedToPrincipal != 0) {
/// The lender always gets back an ERC20 (even wrbtc),
/// so we call withdraw directly rather than
/// use the _withdrawAsset helper function.
vaultWithdraw(loanParamsLocal.loanToken, loanLocal.lender, interestAppliedToPrincipal);
}
return loanCloseAmountLessInterest;
}
/**
* @notice Transfer principal with deposit to receiver.
*
* @dev The receiver always gets back an ERC20 (even wrBTC).
*
* @param loanToken The address of the loan token.
* @param receiver The recipient address.
* @param principalNeeded The required amount of destination tokens in order to cover the principle.
* */
function _returnPrincipalWithDeposit(
address loanToken,
address receiver,
uint256 principalNeeded
) internal {
if (principalNeeded != 0) {
if (msg.value == 0) {
vaultTransfer(loanToken, msg.sender, receiver, principalNeeded);
} else {
require(loanToken == address(wrbtcToken), "wrong asset sent");
require(msg.value >= principalNeeded, "not enough ether");
wrbtcToken.deposit.value(principalNeeded)();
if (receiver != address(this)) {
vaultTransfer(loanToken, address(this), receiver, principalNeeded);
}
if (msg.value > principalNeeded) {
/// Refund overage.
Address.sendValue(msg.sender, msg.value - principalNeeded);
}
}
} else {
require(msg.value == 0, "wrong asset sent");
}
}
/**
* @notice Check if the amount of the asset to be transfered is worth the transfer fee.
* @param asset The asset to be transfered.
* @param amount The amount to be transfered.
* @return True if the amount is bigger than the threshold.
* */
function worthTheTransfer(address asset, uint256 amount) internal returns (bool) {
(uint256 rbtcRate, uint256 rbtcPrecision) = IPriceFeeds(priceFeeds).queryRate(asset, address(wrbtcToken));
uint256 amountInRbtc = amount.mul(rbtcRate).div(rbtcPrecision);
emit swapExcess(amountInRbtc > paySwapExcessToBorrowerThreshold, amount, amountInRbtc, paySwapExcessToBorrowerThreshold);
return amountInRbtc > paySwapExcessToBorrowerThreshold;
}
/**
* @notice Swaps a share of a loan's collateral or the complete collateral
* in order to cover the principle.
*
* @param loanLocal The loan object.
* @param loanParamsLocal The loan parameters.
* @param swapAmount In case principalNeeded == 0 or !returnTokenIsCollateral,
* this is the amount which is going to be swapped.
* Else, swapAmount doesn't matter, because the amount of source tokens
* needed for the swap is estimated by the connector.
* @param principalNeeded The required amount of destination tokens in order to
* cover the principle (only used if returnTokenIsCollateral).
* @param returnTokenIsCollateral Tells if the user wants to withdraw his
* remaining collateral + profit in collateral tokens.
*
* @return coveredPrincipal The amount of principal that is covered.
* @return usedCollateral The amount of collateral used.
* @return withdrawAmount The withdraw amount in the collateral token.
* @return collateralToLoanSwapRate The swap rate of collateral.
* */
function _coverPrincipalWithSwap(
Loan memory loanLocal,
LoanParams memory loanParamsLocal,
uint256 swapAmount,
uint256 principalNeeded,
bool returnTokenIsCollateral,
bytes memory loanDataBytes
)
internal
returns (
uint256 coveredPrincipal,
uint256 usedCollateral,
uint256 withdrawAmount,
uint256 collateralToLoanSwapRate
)
{
uint256 destTokenAmountReceived;
uint256 sourceTokenAmountUsed;
(destTokenAmountReceived, sourceTokenAmountUsed, collateralToLoanSwapRate) = _doCollateralSwap(
loanLocal,
loanParamsLocal,
swapAmount,
principalNeeded,
returnTokenIsCollateral,
loanDataBytes
);
if (returnTokenIsCollateral) {
coveredPrincipal = principalNeeded;
/// Better fill than expected.
if (destTokenAmountReceived > coveredPrincipal) {
/// Send excess to borrower if the amount is big enough to be
/// worth the gas fees.
if (worthTheTransfer(loanParamsLocal.loanToken, destTokenAmountReceived - coveredPrincipal)) {
_withdrawAsset(loanParamsLocal.loanToken, loanLocal.borrower, destTokenAmountReceived - coveredPrincipal);
}
/// Else, give the excess to the lender (if it goes to the
/// borrower, they're very confused. causes more trouble than it's worth)
else {
coveredPrincipal = destTokenAmountReceived;
}
}
withdrawAmount = swapAmount > sourceTokenAmountUsed ? swapAmount - sourceTokenAmountUsed : 0;
} else {
require(sourceTokenAmountUsed == swapAmount, "swap error");
if (swapAmount == loanLocal.collateral) {
/// sourceTokenAmountUsed == swapAmount == loanLocal.collateral
coveredPrincipal = principalNeeded;
withdrawAmount = destTokenAmountReceived - principalNeeded;
} else {
/// sourceTokenAmountUsed == swapAmount < loanLocal.collateral
if (destTokenAmountReceived >= loanLocal.principal) {
/// Edge case where swap covers full principal.
coveredPrincipal = loanLocal.principal;
withdrawAmount = destTokenAmountReceived - loanLocal.principal;
/// Excess collateral refunds to the borrower.
_withdrawAsset(loanParamsLocal.collateralToken, loanLocal.borrower, loanLocal.collateral - sourceTokenAmountUsed);
sourceTokenAmountUsed = loanLocal.collateral;
} else {
coveredPrincipal = destTokenAmountReceived;
withdrawAmount = 0;
}
}
}
usedCollateral = sourceTokenAmountUsed > swapAmount ? sourceTokenAmountUsed : swapAmount;
}
/**
* @notice Swap collateral tokens for loan tokens.
*
* @param loanLocal The loan object.
* @param loanParamsLocal The loan parameters.
* @param swapAmount The amount to be swapped.
* @param principalNeeded The required destination token amount.
* @param returnTokenIsCollateral if true -> required destination
* token amount will be passed on, else not
* note: quite dirty. should be refactored.
* @param loanDataBytes Additional loan data (not in use for token swaps).
* */
function _doCollateralSwap(
Loan memory loanLocal,
LoanParams memory loanParamsLocal,
uint256 swapAmount,
uint256 principalNeeded,
bool returnTokenIsCollateral,
bytes memory loanDataBytes
)
internal
returns (
uint256 destTokenAmountReceived,
uint256 sourceTokenAmountUsed,
uint256 collateralToLoanSwapRate
)
{
(destTokenAmountReceived, sourceTokenAmountUsed, collateralToLoanSwapRate) = _loanSwap(
loanLocal.id,
loanParamsLocal.collateralToken,
loanParamsLocal.loanToken,
loanLocal.borrower,
swapAmount, /// minSourceTokenAmount
loanLocal.collateral, /// maxSourceTokenAmount
returnTokenIsCollateral
? principalNeeded /// requiredDestTokenAmount
: 0,
false, // bypassFee
loanDataBytes
);
require(destTokenAmountReceived >= principalNeeded, "insufficient dest amount");
require(sourceTokenAmountUsed <= loanLocal.collateral, "excessive source amount");
}
/**
* @notice Withdraw asset to receiver.
*
* @param assetToken The loan token.
* @param receiver The address of the receiver.
* @param assetAmount The loan token amount.
* */
function _withdrawAsset(
address assetToken,
address receiver,
uint256 assetAmount
) internal {
if (assetAmount != 0) {
if (assetToken == address(wrbtcToken)) {
vaultEtherWithdraw(receiver, assetAmount);
} else {
vaultWithdraw(assetToken, receiver, assetAmount);
}
}
}
/**
* @notice Close a loan.
*
* @dev Wrapper for _closeLoan internal function.
*
* @param loanLocal The loan object.
* @param loanParamsLocal The loan params.
* @param loanCloseAmount The amount to close: principal or lower.
* @param collateralCloseAmount The amount of collateral to close.
* @param collateralToLoanSwapRate The price rate collateral/loan token.
* @param closeType The type of loan close.
* */
function _finalizeClose(
Loan storage loanLocal,
LoanParams storage loanParamsLocal,
uint256 loanCloseAmount,
uint256 collateralCloseAmount,
uint256 collateralToLoanSwapRate,
CloseTypes closeType
) internal {
_closeLoan(loanLocal, loanCloseAmount);
address _priceFeeds = priceFeeds;
uint256 currentMargin;
uint256 collateralToLoanRate;
/// This is still called even with full loan close to return collateralToLoanRate
(bool success, bytes memory data) =
_priceFeeds.staticcall(
abi.encodeWithSelector(
IPriceFeeds(_priceFeeds).getCurrentMargin.selector,
loanParamsLocal.loanToken,
loanParamsLocal.collateralToken,
loanLocal.principal,
loanLocal.collateral
)
);
assembly {
if eq(success, 1) {
currentMargin := mload(add(data, 32))
collateralToLoanRate := mload(add(data, 64))
}
}
/// Note: We can safely skip the margin check if closing
/// via closeWithDeposit or if closing the loan in full by any method.
require(
closeType == CloseTypes.Deposit ||
loanLocal.principal == 0 || /// loan fully closed
currentMargin > loanParamsLocal.maintenanceMargin,
"unhealthy position"
);
_emitClosingEvents(
loanParamsLocal,
loanLocal,
loanCloseAmount,
collateralCloseAmount,
collateralToLoanRate,
collateralToLoanSwapRate,
currentMargin,
closeType
);
}
/**
* @notice Internal function to close a loan.
*
* @param loanLocal The loan object.
* @param loanCloseAmount The amount to close: principal or lower.
*
* */
function _closeLoan(Loan storage loanLocal, uint256 loanCloseAmount) internal {
require(loanCloseAmount != 0, "nothing to close");
if (loanCloseAmount == loanLocal.principal) {
loanLocal.principal = 0;
loanLocal.active = false;
loanLocal.endTimestamp = block.timestamp;
loanLocal.pendingTradesId = 0;
activeLoansSet.removeBytes32(loanLocal.id);
lenderLoanSets[loanLocal.lender].removeBytes32(loanLocal.id);
borrowerLoanSets[loanLocal.borrower].removeBytes32(loanLocal.id);
} else {
loanLocal.principal = loanLocal.principal.sub(loanCloseAmount);
}
}
/**
* @notice Compute the interest which neeeds to be refunded to the borrower
* (because full interest is paid on loan).
*
* @param loanParamsLocal The loan params.
* @param loanLocal The loan object.
* @param closePrincipal The amount of principal to close.
*
* @return interestRefundToBorrower The interest refund to the borrower.
* */
function _settleInterest(
LoanParams memory loanParamsLocal,
Loan memory loanLocal,
uint256 closePrincipal
) internal returns (uint256) {
/// pay outstanding interest to lender
_payInterest(loanLocal.lender, loanParamsLocal.loanToken);
LoanInterest storage loanInterestLocal = loanInterest[loanLocal.id];
LenderInterest storage lenderInterestLocal = lenderInterest[loanLocal.lender][loanParamsLocal.loanToken];
uint256 interestTime = block.timestamp;
if (interestTime > loanLocal.endTimestamp) {
interestTime = loanLocal.endTimestamp;
}
_settleFeeRewardForInterestExpense(loanInterestLocal, loanLocal.id, loanParamsLocal.loanToken, loanLocal.borrower, interestTime);
uint256 owedPerDayRefund;
if (closePrincipal < loanLocal.principal) {
owedPerDayRefund = loanInterestLocal.owedPerDay.mul(closePrincipal).div(loanLocal.principal);
} else {
owedPerDayRefund = loanInterestLocal.owedPerDay;
}
/// update stored owedPerDay
loanInterestLocal.owedPerDay = loanInterestLocal.owedPerDay.sub(owedPerDayRefund);
lenderInterestLocal.owedPerDay = lenderInterestLocal.owedPerDay.sub(owedPerDayRefund);
/// update borrower interest
uint256 interestRefundToBorrower = loanLocal.endTimestamp.sub(interestTime);
interestRefundToBorrower = interestRefundToBorrower.mul(owedPerDayRefund);
interestRefundToBorrower = interestRefundToBorrower.div(1 days);
if (closePrincipal < loanLocal.principal) {
loanInterestLocal.depositTotal = loanInterestLocal.depositTotal.sub(interestRefundToBorrower);
} else {
loanInterestLocal.depositTotal = 0;
}
/// update remaining lender interest values
lenderInterestLocal.principalTotal = lenderInterestLocal.principalTotal.sub(closePrincipal);
uint256 owedTotal = lenderInterestLocal.owedTotal;
lenderInterestLocal.owedTotal = owedTotal > interestRefundToBorrower ? owedTotal - interestRefundToBorrower : 0;
return interestRefundToBorrower;
}
function _emitClosingEvents(
LoanParams memory loanParamsLocal,
Loan memory loanLocal,
uint256 loanCloseAmount,
uint256 collateralCloseAmount,
uint256 collateralToLoanRate,
uint256 collateralToLoanSwapRate,
uint256 currentMargin,
CloseTypes closeType
) internal {
if (closeType == CloseTypes.Deposit) {
emit CloseWithDeposit(
loanLocal.borrower, /// user (borrower)
loanLocal.lender, /// lender
loanLocal.id, /// loanId
msg.sender, /// closer
loanParamsLocal.loanToken, /// loanToken
loanParamsLocal.collateralToken, /// collateralToken
loanCloseAmount, /// loanCloseAmount
collateralCloseAmount, /// collateralCloseAmount
collateralToLoanRate, /// collateralToLoanRate
currentMargin /// currentMargin
);
} else if (closeType == CloseTypes.Swap) {
/// exitPrice = 1 / collateralToLoanSwapRate
if (collateralToLoanSwapRate != 0) {
collateralToLoanSwapRate = SafeMath.div(10**36, collateralToLoanSwapRate);
}
/// currentLeverage = 100 / currentMargin
if (currentMargin != 0) {
currentMargin = SafeMath.div(10**38, currentMargin);
}
emit CloseWithSwap(
loanLocal.borrower, /// user (trader)
loanLocal.lender, /// lender
loanLocal.id, /// loanId
loanParamsLocal.collateralToken, /// collateralToken
loanParamsLocal.loanToken, /// loanToken
msg.sender, /// closer
collateralCloseAmount, /// positionCloseSize
loanCloseAmount, /// loanCloseAmount
collateralToLoanSwapRate, /// exitPrice (1 / collateralToLoanSwapRate)
currentMargin /// currentLeverage
);
}
}
}