View Source: contracts/modules/LoanClosingsRollover.sol
↗ Extends: LoanClosingsShared, LiquidationHelper
Ways to close a loan: rollover. Margin trade positions are always closed with a swap. *
Constants & Variables
uint256 internal constant MONTH;
- constructor()
- constructor()
- initialize(address target)
- rollover(bytes32 loanId, bytes )
- _rollover(bytes32 loanId, bytes loanDataBytes)
- _swapBackExcess(struct LoanStruct.Loan loanLocal, struct LoanParamsStruct.LoanParams loanParamsLocal, uint256 swapAmount, bytes loanDataBytes)
function () public nonpayable
Source Code
constructor() public {}
function () external nonpayable
Source Code
function() external {
revert("fallback not allowed");
}
function initialize(address target) external nonpayable onlyOwner
Arguments
Name | Type | Description |
---|---|---|
target | address |
Source Code
function initialize(address target) external onlyOwner {
address prevModuleContractAddress = logicTargets[this.rollover.selector];
_setTarget(this.rollover.selector, target);
emit ProtocolModuleContractReplaced(
prevModuleContractAddress,
target,
"LoanClosingsRollover"
);
}
Roll over a loan. *
function rollover(bytes32 loanId, bytes ) external nonpayable nonReentrant globallyNonReentrant iTokenSupplyUnchanged whenNotPaused
Arguments
Name | Type | Description |
---|---|---|
loanId | bytes32 | The ID of the loan to roll over. // param calldata The payload for the call. These loan DataBytes are additional loan data (not in use for token swaps). |
bytes | loanId The ID of the loan to roll over. // param calldata The payload for the call. These loan DataBytes are additional loan data (not in use for token swaps). |
Source Code
function rollover(
bytes32 loanId,
bytes calldata // for future use /*loanDataBytes*/
) external nonReentrant globallyNonReentrant iTokenSupplyUnchanged(loanId) whenNotPaused {
// restrict to EOAs to prevent griefing attacks, during interest rate recalculation
require(msg.sender == tx.origin, "EOAs call");
return
_rollover(
loanId,
"" // loanDataBytes
);
}
Internal function for roll over a loan. * Each loan has a duration. In case of a margin trade it is set to 28 days, in case of borrowing, it can be set by the user. On loan openning, the user pays the interest for this duration in advance. If closing early, he gets the excess refunded. If it is not closed before the end date, it needs to be rolled over. On rollover the interest is paid for the next period. In case of margin trading it's 28 days, in case of borrowing it's a month. *
function _rollover(bytes32 loanId, bytes loanDataBytes) internal nonpayable
Arguments
Name | Type | Description |
---|---|---|
loanId | bytes32 | The ID of the loan to roll over. |
loanDataBytes | bytes | The payload for the call. These loan DataBytes are additional loan data (not in use for token swaps). |
Source Code
function _rollover(bytes32 loanId, bytes memory loanDataBytes) internal {
(Loan storage loanLocal, LoanParams storage loanParamsLocal) = _checkLoan(loanId);
require(block.timestamp > loanLocal.endTimestamp.sub(3600), "healthy position");
require(loanPoolToUnderlying[loanLocal.lender] != address(0), "invalid lender");
// pay outstanding interest to lender
_payInterest(loanLocal.lender, loanParamsLocal.loanToken);
LoanInterest storage loanInterestLocal = loanInterest[loanLocal.id];
LenderInterest storage lenderInterestLocal =
lenderInterest[loanLocal.lender][loanParamsLocal.loanToken];
_settleFeeRewardForInterestExpense(
loanInterestLocal,
loanLocal.id,
loanParamsLocal.loanToken, /// fee token
loanParamsLocal.collateralToken, /// pairToken (used to check if there is any special rebates or not) -- to pay fee reward
loanLocal.borrower,
block.timestamp
);
// Handle back interest: calculates interest owned since the loan endtime passed but the loan remained open
uint256 backInterestTime;
uint256 backInterestOwed;
if (block.timestamp > loanLocal.endTimestamp) {
backInterestTime = block.timestamp.sub(loanLocal.endTimestamp);
backInterestOwed = backInterestTime.mul(loanInterestLocal.owedPerDay);
backInterestOwed = backInterestOwed.div(1 days);
}
//note: to avoid code duplication, it would be nicer to store loanParamsLocal.maxLoanTerm in a local variable
//however, we've got stack too deep issues if we do so.
if (loanParamsLocal.maxLoanTerm != 0) {
// fixed-term loan, so need to query iToken for latest variable rate
uint256 owedPerDay =
loanLocal.principal.mul(ILoanPool(loanLocal.lender).borrowInterestRate()).div(
365 * 10**20
);
lenderInterestLocal.owedPerDay = lenderInterestLocal.owedPerDay.add(owedPerDay);
lenderInterestLocal.owedPerDay = lenderInterestLocal.owedPerDay.sub(
loanInterestLocal.owedPerDay
);
loanInterestLocal.owedPerDay = owedPerDay;
//if the loan has been open for longer than an additional period, add at least 1 additional day
if (backInterestTime >= loanParamsLocal.maxLoanTerm) {
loanLocal.endTimestamp = loanLocal.endTimestamp.add(backInterestTime).add(1 days);
}
//extend by the max loan term
else {
loanLocal.endTimestamp = loanLocal.endTimestamp.add(loanParamsLocal.maxLoanTerm);
}
} else {
// loanInterestLocal.owedPerDay doesn't change
if (backInterestTime >= MONTH) {
loanLocal.endTimestamp = loanLocal.endTimestamp.add(backInterestTime).add(1 days);
} else {
loanLocal.endTimestamp = loanLocal.endTimestamp.add(MONTH);
}
}
uint256 interestAmountRequired = loanLocal.endTimestamp.sub(block.timestamp);
interestAmountRequired = interestAmountRequired.mul(loanInterestLocal.owedPerDay);
interestAmountRequired = interestAmountRequired.div(1 days);
loanInterestLocal.depositTotal = loanInterestLocal.depositTotal.add(
interestAmountRequired
);
lenderInterestLocal.owedTotal = lenderInterestLocal.owedTotal.add(interestAmountRequired);
// add backInterestOwed
interestAmountRequired = interestAmountRequired.add(backInterestOwed);
// collect interest (needs to be converted from the collateral)
(uint256 destTokenAmountReceived, uint256 sourceTokenAmountUsed, ) =
_doCollateralSwap(
loanLocal,
loanParamsLocal,
0, //min swap 0 -> swap connector estimates the amount of source tokens to use
interestAmountRequired, //required destination tokens
true, // returnTokenIsCollateral
loanDataBytes
);
//received more tokens than needed to pay the interest
if (destTokenAmountReceived > interestAmountRequired) {
// swap rest back to collateral, if the amount is big enough to cover gas cost
if (
worthTheTransfer(
loanParamsLocal.loanToken,
destTokenAmountReceived - interestAmountRequired
)
) {
(destTokenAmountReceived, , ) = _swapBackExcess(
loanLocal,
loanParamsLocal,
destTokenAmountReceived - interestAmountRequired, //amount to be swapped
loanDataBytes
);
sourceTokenAmountUsed = sourceTokenAmountUsed.sub(destTokenAmountReceived);
}
//else give it to the protocol as a lending fee
else {
_payLendingFee(
loanLocal.borrower,
loanParamsLocal.loanToken,
destTokenAmountReceived - interestAmountRequired
);
}
}
//subtract the interest from the collateral
loanLocal.collateral = loanLocal.collateral.sub(sourceTokenAmountUsed);
if (backInterestOwed != 0) {
// pay out backInterestOwed
_payInterestTransfer(loanLocal.lender, loanParamsLocal.loanToken, backInterestOwed);
}
uint256 rolloverReward =
_getRolloverReward(
loanParamsLocal.collateralToken,
loanParamsLocal.loanToken,
loanLocal.principal
);
if (rolloverReward != 0) {
// if the reward > collateral:
if (rolloverReward > loanLocal.collateral) {
// 1. pay back the remaining loan to the lender
// 2. pay the remaining collateral to msg.sender
// 3. close the position & emit close event
_closeWithSwap(
loanLocal.id,
msg.sender,
loanLocal.collateral,
false,
"" // loanDataBytes
);
} else {
// pay out reward to caller
loanLocal.collateral = loanLocal.collateral.sub(rolloverReward);
_withdrawAsset(loanParamsLocal.collateralToken, msg.sender, rolloverReward);
}
}
if (loanLocal.collateral > 0) {
//close whole loan if tiny position will remain
if (_getAmountInRbtc(loanParamsLocal.loanToken, loanLocal.principal) <= TINY_AMOUNT) {
_closeWithSwap(
loanLocal.id,
loanLocal.borrower,
loanLocal.collateral, // swap all collaterals
false,
"" /// loanDataBytes
);
} else {
(uint256 currentMargin, ) =
IPriceFeeds(priceFeeds).getCurrentMargin(
loanParamsLocal.loanToken,
loanParamsLocal.collateralToken,
loanLocal.principal,
loanLocal.collateral
);
require(
currentMargin > 3 ether, // ensure there's more than 3% margin remaining
"unhealthy position"
);
}
}
if (loanLocal.active) {
emit Rollover(
loanLocal.borrower, // user (borrower)
loanLocal.lender, // lender
loanLocal.id, // loanId
loanLocal.principal, // principal
loanLocal.collateral, // collateral
loanLocal.endTimestamp, // endTimestamp
msg.sender, // rewardReceiver
rolloverReward // reward
);
}
}
Swap back excessive loan tokens to collateral tokens. *
function _swapBackExcess(struct LoanStruct.Loan loanLocal, struct LoanParamsStruct.LoanParams loanParamsLocal, uint256 swapAmount, bytes loanDataBytes) internal nonpayable
returns(destTokenAmountReceived uint256, sourceTokenAmountUsed uint256, collateralToLoanSwapRate uint256)
Arguments
Name | Type | Description |
---|---|---|
loanLocal | struct LoanStruct.Loan | The loan object. |
loanParamsLocal | struct LoanParamsStruct.LoanParams | The loan parameters. |
swapAmount | uint256 | The amount to be swapped. |
loanDataBytes | bytes | Additional loan data (not in use for token swaps). * |
Returns
destTokenAmountReceived The amount of destiny tokens received.
Source Code
function _swapBackExcess(
Loan memory loanLocal,
LoanParams memory loanParamsLocal,
uint256 swapAmount,
bytes memory loanDataBytes
)
internal
returns (
uint256 destTokenAmountReceived,
uint256 sourceTokenAmountUsed,
uint256 collateralToLoanSwapRate
)
{
(destTokenAmountReceived, sourceTokenAmountUsed, collateralToLoanSwapRate) = _loanSwap(
loanLocal.id,
loanParamsLocal.loanToken,
loanParamsLocal.collateralToken,
loanLocal.borrower,
swapAmount, // minSourceTokenAmount
swapAmount, // maxSourceTokenAmount
0, // requiredDestTokenAmount
false, // bypassFee
loanDataBytes
);
require(sourceTokenAmountUsed <= swapAmount, "excessive source amount");
}
- Address
- Administered
- AdminRole
- AdvancedToken
- AdvancedTokenStorage
- Affiliates
- AffiliatesEvents
- ApprovalReceiver
- BProPriceFeed
- CheckpointsShared
- Constants
- Context
- DevelopmentFund
- DummyContract
- EnumerableAddressSet
- EnumerableBytes32Set
- EnumerableBytes4Set
- ERC20
- ERC20Detailed
- ErrorDecoder
- Escrow
- EscrowReward
- FeedsLike
- FeesEvents
- FeeSharingCollector
- FeeSharingCollectorProxy
- FeeSharingCollectorStorage
- FeesHelper
- FourYearVesting
- FourYearVestingFactory
- FourYearVestingLogic
- FourYearVestingStorage
- GenericTokenSender
- GovernorAlpha
- GovernorVault
- IApproveAndCall
- IChai
- IContractRegistry
- IConverterAMM
- IERC1820Registry
- IERC20_
- IERC20
- IERC777
- IERC777Recipient
- IERC777Sender
- IFeeSharingCollector
- IFourYearVesting
- IFourYearVestingFactory
- IFunctionsList
- ILiquidityMining
- ILiquidityPoolV1Converter
- ILoanPool
- ILoanToken
- ILoanTokenLogicBeacon
- ILoanTokenLogicModules
- ILoanTokenLogicProxy
- ILoanTokenModules
- ILoanTokenWRBTC
- ILockedSOV
- IMoCState
- IModulesProxyRegistry
- Initializable
- InterestUser
- IPot
- IPriceFeeds
- IPriceFeedsExt
- IProtocol
- IRSKOracle
- ISovryn
- ISovrynSwapNetwork
- IStaking
- ISwapsImpl
- ITeamVesting
- ITimelock
- IV1PoolOracle
- IVesting
- IVestingFactory
- IVestingRegistry
- IWrbtc
- IWrbtcERC20
- LenderInterestStruct
- LiquidationHelper
- LiquidityMining
- LiquidityMiningConfigToken
- LiquidityMiningProxy
- LiquidityMiningStorage
- LoanClosingsEvents
- LoanClosingsLiquidation
- LoanClosingsRollover
- LoanClosingsShared
- LoanClosingsWith
- LoanClosingsWithoutInvariantCheck
- LoanInterestStruct
- LoanMaintenance
- LoanMaintenanceEvents
- LoanOpenings
- LoanOpeningsEvents
- LoanParamsStruct
- LoanSettings
- LoanSettingsEvents
- LoanStruct
- LoanToken
- LoanTokenBase
- LoanTokenLogicBeacon
- LoanTokenLogicLM
- LoanTokenLogicProxy
- LoanTokenLogicStandard
- LoanTokenLogicStorage
- LoanTokenLogicWrbtc
- LoanTokenSettingsLowerAdmin
- LockedSOV
- MarginTradeStructHelpers
- Medianizer
- ModuleCommonFunctionalities
- ModulesCommonEvents
- ModulesProxy
- ModulesProxyRegistry
- MultiSigKeyHolders
- MultiSigWallet
- Mutex
- Objects
- OrderStruct
- OrigingVestingCreator
- OriginInvestorsClaim
- Ownable
- Pausable
- PausableOz
- PreviousLoanToken
- PreviousLoanTokenSettingsLowerAdmin
- PriceFeedRSKOracle
- PriceFeeds
- PriceFeedsLocal
- PriceFeedsMoC
- PriceFeedV1PoolOracle
- ProtocolAffiliatesInterface
- ProtocolLike
- ProtocolSettings
- ProtocolSettingsEvents
- ProtocolSettingsLike
- ProtocolSwapExternalInterface
- ProtocolTokenUser
- Proxy
- ProxyOwnable
- ReentrancyGuard
- RewardHelper
- RSKAddrValidator
- SafeERC20
- SafeMath
- SafeMath96
- setGet
- SharedReentrancyGuard
- SignedSafeMath
- SOV
- sovrynProtocol
- StakingAdminModule
- StakingGovernanceModule
- StakingInterface
- StakingProxy
- StakingRewards
- StakingRewardsProxy
- StakingRewardsStorage
- StakingShared
- StakingStakeModule
- StakingStorageModule
- StakingStorageShared
- StakingVestingModule
- StakingWithdrawModule
- State
- SwapsEvents
- SwapsExternal
- SwapsImplLocal
- SwapsImplSovrynSwap
- SwapsUser
- TeamVesting
- Timelock
- TimelockHarness
- TimelockInterface
- TokenSender
- UpgradableProxy
- USDTPriceFeed
- Utils
- VaultController
- Vesting
- VestingCreator
- VestingFactory
- VestingLogic
- VestingRegistry
- VestingRegistry2
- VestingRegistry3
- VestingRegistryLogic
- VestingRegistryProxy
- VestingRegistryStorage
- VestingStorage
- WeightedStakingModule
- WRBTC