// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.28; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {IPolicyPool} from "./interfaces/IPolicyPool.sol"; import {IEToken} from "./interfaces/IEToken.sol"; import {Reserve} from "./Reserve.sol"; import {IPremiumsAccount} from "./interfaces/IPremiumsAccount.sol"; import {Policy} from "./Policy.sol"; /** * @title Ensuro Premiums Account * @notice This contract holds the pure premiums of a set of risk modules. * @dev Pure premiums is the part of the premium that is expected to cover the losses. The contract keeps track of the pure premiums of the active policies * (_activePurePremiums) and the surplus or deficit generated by the finalized policies (pure premiums collected - * losses). * * Collaborates with a junior {EToken} and a senior {EToken} that act as lenders when the premiums aren't enough to * cover the losses. * * @custom:security-contact security@ensuro.co * @author Ensuro */ contract PremiumsAccount is IPremiumsAccount, Reserve { using Policy for Policy.PolicyData; using Math for uint256; using SafeERC20 for IERC20Metadata; using SafeCast for uint256; uint256 internal constant WAD = 1e18; uint256 internal constant FOUR_DECIMAL_TO_WAD = 1e14; uint16 internal constant HUNDRED_PERCENT = 1e4; /** * @dev The Junior eToken is the first {EToken} to which the PremiumsAccount will go for credit when it runs out of * money. Optional (address(0)). */ /// @custom:oz-upgrades-unsafe-allow state-variable-immutable IEToken internal immutable _juniorEtk; /** * @dev The Senior eToken is the second {EToken} to which the PremiumsAccount will go for credit, after trying before * with the junior eToken, when it runs out of money. Optional (address(0)). */ /// @custom:oz-upgrades-unsafe-allow state-variable-immutable IEToken internal immutable _seniorEtk; /** * @dev The active pure premiums field keeps track of the pure premiums collected by the active policies of risk * modules linked with this PremiumsAccount. */ uint256 internal _activePurePremiums; // sum of pure-premiums of active policies - In Wad /** * @dev The surplus field keeps track of the surplus or deficit (when negative) of the actual payouts made by the * PremiumsAccount versus the collected pure premiums. On the negative side, it has a limit defined by `_maxDeficit()`, * after that limit, internal loans are taken from the eTokens. */ int256 internal _surplus; /** * @notice This struct has the parameters that can be modified by governance * @member deficitRatio A value between [0, 1] that defines the percentage of active pure premiums that can be used * to cover losses. * @member yieldVault This is the implementation contract that manages the PremiumsAccount's funds. See * {yieldVault()} * @member jrLoanLimit This is the maximum amount that can be borrowed from the Junior eToken (without decimals) * @member srLoanLimit This is the maximum amount that can be borrowed from the Senior eToken (without decimals) */ struct PackedParams { IERC4626 yieldVault; uint32 jrLoanLimit; uint32 srLoanLimit; uint16 deficitRatio; } PackedParams internal _params; /** * @notice Thrown when yield-vault losses would push the account below its maximum allowed deficit. * @dev `excess` is the remaining unpaid amount returned by `_payFromPremiums(losses)` after clamping to `_maxDeficit()`. * @param losses The total losses being applied * @param excess The unpaid portion that exceeds the max-deficit capacity */ error LossesCannotExceedMaxDeficit(uint256 losses, uint256 excess); /** * @notice Thrown during upgrade validation if the configured eToken addresses change unexpectedly. * @dev Upgrades must keep the same junior/senior eToken wiring (unless the current eToken is zero-address). * @param oldEToken The currently configured eToken * @param newEToken The eToken reported by the new implementation */ error InvalidUpgradeETokenChanged(IEToken oldEToken, IEToken newEToken); /** * @notice Thrown when a new deficit ratio is invalid (out of range or not representable with 4 decimals). * @param newDeficitRatio The proposed ratio (wad) */ error InvalidDeficitRatio(uint256 newDeficitRatio); /** * @notice Thrown when attempting to set a deficit ratio without adjustment while the current deficit exceeds the new * maximum allowed deficit. * @param currentDeficit Current deficit (positive value, i.e., `-surplus`) * @param newMaxDeficit New maximum deficit (positive value, i.e., `-maxDeficit`) */ error DeficitExceedsMaxDeficit(int256 currentDeficit, int256 newMaxDeficit); /** * @notice Thrown when the required funds cannot be fully borrowed from the configured eTokens within their limits. * @param amountLeft The remaining amount that could not be borrowed */ error CannotBeBorrowed(uint256 amountLeft); /** * @notice Thrown when a loan limit cannot be represented with 0 decimals when packing/unpacking. * @param loanLimit The provided limit value */ error InvalidLoanLimit(uint256 loanLimit); /** * @notice Thrown when the destination address is invalid. * @param destination The provided destination address */ error InvalidDestination(address destination); /** * @notice Thrown when attempting to withdraw more than the current surplus (when `amount != type(uint256).max`). * @param amountRequired The requested amount to withdraw * @param surplus The current surplus (can be negative) */ error WithdrawExceedsSurplus(uint256 amountRequired, int256 surplus); /** * @notice Emitted when "won premiums" move in or out of the PremiumsAccount. * @dev Emitted by `receiveGrant` when funds are received without liability, and by `withdrawWonPremiums` when * surplus is withdrawn (typically to the treasury). * * @param moneyIn Indicates if money came in or out (false). * @param value The amount of money received or given */ event WonPremiumsInOut(bool moneyIn, uint256 value); /** * @notice Emitted when the deficitRatio is changed * * @param oldRatio Ratio before the change * @param newRatio Ratio after the change * @param adjustment Adjustement (etk loan) made to adjust the contract, so deficit <= maxDeficit */ event DeficitRatioChanged(uint256 oldRatio, uint256 newRatio, uint256 adjustment); /** * @notice Emitted when the loan limits are changed * * @param oldLimit Limit before the change * @param newLimit Limit after the change * @param isSenior If true, the limit changed is the senior limit, otherwise is the junior limit */ event LoanLimitChanged(uint256 oldLimit, uint256 newLimit, bool isSenior); /** * @dev Constructor of the contract, sets the immutable fields. * * @param juniorEtk_ Address of the Junior EToken (first loss lender). `address(0)` if not present. * @param seniorEtk_ Address of the Senior EToken (2nd loss lender). `address(0)` if not present. */ /// @custom:oz-upgrades-unsafe-allow constructor constructor(IPolicyPool policyPool_, IEToken juniorEtk_, IEToken seniorEtk_) Reserve(policyPool_) { _juniorEtk = juniorEtk_; _seniorEtk = seniorEtk_; } /** * @dev Initializes the PremiumsAccount */ function initialize() public initializer { __PremiumsAccount_init(); } /** * @dev Initializes the PremiumsAccount (to be called by subclasses) */ // solhint-disable-next-line func-name-mixedcase function __PremiumsAccount_init() internal onlyInitializing { __Reserve_init(); __PremiumsAccount_init_unchained(); } // solhint-disable-next-line func-name-mixedcase function __PremiumsAccount_init_unchained() internal onlyInitializing { /* _activePurePremiums = 0; */ _params = PackedParams({ deficitRatio: HUNDRED_PERCENT, yieldVault: IERC4626(address(0)), jrLoanLimit: 0, srLoanLimit: 0 }); } function _upgradeValidations(address newImpl) internal view virtual override { super._upgradeValidations(newImpl); IPremiumsAccount newPA = IPremiumsAccount(newImpl); (IEToken newJr, IEToken newSr) = newPA.etks(); require(newJr == _juniorEtk || address(_juniorEtk) == address(0), InvalidUpgradeETokenChanged(_juniorEtk, newJr)); require(newSr == _seniorEtk || address(_seniorEtk) == address(0), InvalidUpgradeETokenChanged(_seniorEtk, newSr)); } /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { return super.supportsInterface(interfaceId) || interfaceId == type(IPremiumsAccount).interfaceId; } /// @inheritdoc Reserve function yieldVault() public view override returns (IERC4626) { return _params.yieldVault; } /// @inheritdoc Reserve function _setYieldVault(IERC4626 newYV) internal override { _params.yieldVault = newYV; } /** * @dev This is called by the {Reserve} base class to record the earnings generated by the asset management. * * @param earningsOrLosses Indicates the amount earned since last time earnings where recorded. * - If positive, accumulates the amount in the surplus. * - If negative (losses) substracts it from surplus. It never can exceed _maxDeficit and doesn't takes * loans to cover asset losses. */ function _yieldEarnings(int256 earningsOrLosses) internal override { if (earningsOrLosses >= 0) { _storePurePremiumWon(uint256(earningsOrLosses)); } else { uint256 excess = _payFromPremiums(-earningsOrLosses); require(excess == 0, LossesCannotExceedMaxDeficit(uint256(-earningsOrLosses), excess)); } super._yieldEarnings(earningsOrLosses); } function purePremiums() external view override returns (uint256) { return uint256(int256(_activePurePremiums) + _surplus); } /** * @dev Returns the total amount of pure premiums that were collected by the active policies of the risk modules * linked to this PremiumsAccount. */ function activePurePremiums() external view returns (uint256) { return _activePurePremiums; } /** * @dev Returns the surplus between pure premiums collected and payouts of finalized policies. Returns 0 if no surplus * or deficit. */ function wonPurePremiums() external view returns (uint256) { return _surplus >= 0 ? uint256(_surplus) : 0; } /** * @dev Returns the amount of active pure premiums that was used to cover payouts of finalized policies (in excess of * collected pure premiums). This is limited by `_maxDeficit()` */ function borrowedActivePP() external view returns (uint256) { return _surplus >= 0 ? 0 : uint256(-_surplus); } /** * @dev Returns the surplus between pure premiums collected and payouts of finalized policies. Losses where more than * premiums collected, returns a negative number that indicates the amount of the active pure premiums that was used * to cover finalized premiums. */ function surplus() external view returns (int256) { return _surplus; } /** * @dev Returns the amount of funds available to cover losses or repay eToken loans. */ function fundsAvailable() public view returns (uint256) { // This is guaranteed to be positive because _maxDeficit is negative and always gte _surplus in absolute value return uint256(_surplus - _maxDeficit(deficitRatio())); } function seniorEtk() external view override returns (IEToken) { return _seniorEtk; } function juniorEtk() external view override returns (IEToken) { return _juniorEtk; } function etks() external view override returns (IEToken, IEToken) { return (_juniorEtk, _seniorEtk); } /** * @dev Returns the maximum deficit that's supported by the PremiumsAccount. If more money is needed, it must take * loans from the eTokens. The value is calculated as a fraction of the active pure premiums. The fraction is * regulated by the `deficitRatio` parameter that indicates the percentage of the active pure premiums that can be * used to cover payouts of finalized policies. In many cases is fine to use the active pure premiums to cover the * losses because in most cases the policies with payout are triggered long time before the policies without payout. * But this also can be dangerous because it can be postponing the losses that should impact on liquidity providers. * * @param ratio The ratio used in the calculation of the deficit. It's the deficitRatio parameter (whether the current * one or the new one when it's being modified). */ function _maxDeficit(uint256 ratio) internal view returns (int256) { return -int256(_activePurePremiums.mulDiv(ratio, WAD)); } function _toAmount(uint32 value) internal view returns (uint256) { // 0 decimals to amount decimals return uint256(value) * 10 ** currency().decimals(); } function _toZeroDecimals(uint256 amount) internal view returns (uint32) { // Removes the decimals from the amount return (amount / 10 ** currency().decimals()).toUint32(); } /** * @dev Returns the percentage of the active pure premiums that can be used to cover losses of finalized policies. */ function deficitRatio() public view returns (uint256) { return uint256(_params.deficitRatio) * FOUR_DECIMAL_TO_WAD; // 4 -> 18 decimals } /** * @dev Returns the limit on the Junior eToken loans (infinite if _params.jrLoanLimit == 0) */ function jrLoanLimit() public view returns (uint256) { return _params.jrLoanLimit == 0 ? type(uint256).max : _toAmount(_params.jrLoanLimit); } /** * @dev Returns the limit on the Senior eToken loans (infinite if _params.srLoanLimit == 0) */ function srLoanLimit() public view returns (uint256) { return _params.srLoanLimit == 0 ? type(uint256).max : _toAmount(_params.srLoanLimit); } /** * @notice Changes the `deficitRatio` parameter. * * @param newRatio New deficit ratio (wad). Must be `<= WAD` and exactly representable with 4 decimals * (multiple of `FOUR_DECIMAL_TO_WAD`). * @param adjustment If true, allows borrowing from eTokens to satisfy the new max-deficit bound when needed. * * @custom:pre `newRatio <= WAD` * @custom:pre `newRatio` must be exactly representable with 4 decimals: * `uint256(uint16(newRatio / FOUR_DECIMAL_TO_WAD)) * FOUR_DECIMAL_TO_WAD == newRatio` * @custom:pre If `adjustment == false`, then `_surplus >= _maxDeficit(newRatio)` * * @custom:throws {InvalidDeficitRatio} if `newRatio` is out of range or not representable with 4 decimals * @custom:throws {DeficitExceedsMaxDeficit} if `adjustment == false` and `_surplus < _maxDeficit(newRatio)` * * @custom:emits {DeficitRatioChanged} */ function setDeficitRatio(uint256 newRatio, bool adjustment) external { uint16 truncatedRatio = (newRatio / FOUR_DECIMAL_TO_WAD).toUint16(); require( newRatio <= WAD && uint256(truncatedRatio) * FOUR_DECIMAL_TO_WAD == newRatio, InvalidDeficitRatio(newRatio) ); int256 maxDeficit = _maxDeficit(newRatio); if (!adjustment && _surplus < maxDeficit) revert DeficitExceedsMaxDeficit(-_surplus, -maxDeficit); uint256 borrow; if (_surplus < maxDeficit) { // Do the adjustment borrow = uint256(-_surplus + maxDeficit); _surplus = maxDeficit; _borrowFromEtk(borrow, address(this), address(_juniorEtk) != address(0)); } emit DeficitRatioChanged(_params.deficitRatio * FOUR_DECIMAL_TO_WAD, newRatio, borrow); _params.deficitRatio = truncatedRatio; } /** * @notice Changes the `jrLoanLimit` or `srLoanLimit` parameter. * * @param newLimitJr The new limit to be set for the loans taken from the Junior eToken. * If newLimitJr == MAX_UINT, it's ignored. If == 0, means the loans are unbounded. * @param newLimitSr The new limit to be set for the loans taken from the Senior eToken. * If newLimitSr == MAX_UINT, it's ignored. If == 0, means the loans are unbounded. * * @custom:pre For each limit `newLimit` that is updated (`newLimit != type(uint256).max`), it must be representable with 0 decimals: * `_toAmount(_toZeroDecimals(newLimit)) == newLimit` * * @custom:throws {InvalidLoanLimit} if any provided limit cannot be packed/unpacked without losing precision * * @custom:emits {LoanLimitChanged} once per updated limit (up to two emits) */ function setLoanLimits(uint256 newLimitJr, uint256 newLimitSr) external { if (newLimitJr != type(uint256).max) { emit LoanLimitChanged(jrLoanLimit(), newLimitJr, false); _params.jrLoanLimit = _toZeroDecimals(newLimitJr); require(_toAmount(_params.jrLoanLimit) == newLimitJr, InvalidLoanLimit(newLimitJr)); } if (newLimitSr != type(uint256).max) { emit LoanLimitChanged(srLoanLimit(), newLimitSr, true); _params.srLoanLimit = _toZeroDecimals(newLimitSr); require(_toAmount(_params.srLoanLimit) == newLimitSr, InvalidLoanLimit(newLimitSr)); } } /** * @dev Internal function called when money in the PremiumsAccount is not enough and we need to borrow from the * eTokens. * * @param borrow The amount to borrow. * @param receiver The address that will receive the money of the loan. Usually is the policy holder if this is called * in the context of a policy payout. * @param jrEtk If true it indicates that the loan is asked first from the junior eToken. */ function _borrowFromEtk(uint256 borrow, address receiver, bool jrEtk) internal { uint256 left = borrow; if (jrEtk) { uint256 jrLoan = _juniorEtk.getLoan(address(this)); if (jrLoan + borrow <= jrLoanLimit()) { left = _juniorEtk.internalLoan(borrow, receiver); } else if (jrLoan < jrLoanLimit()) { // Partial loan uint256 loanExcess = jrLoan + borrow - jrLoanLimit(); left = loanExcess + _juniorEtk.internalLoan(borrow - loanExcess, receiver); } } if (left != 0) { // if _seniorEtk == address(0) it will revert without message, instead of CannotBeBorrowed if (_seniorEtk.getLoan(address(this)) + left <= srLoanLimit()) { left = _seniorEtk.internalLoan(left, receiver); } // in the senior eToken doesn't make sense to handle partial loan require(left == 0, CannotBeBorrowed(left)); } } /** * @dev Updates the `_surplus` field with the payment made. Since the _surplus can never exceed `_maxDeficit()`, * returns the remaining amount in case something can't be paid from the PremiumsAccount. * * @param toPay The amount to pay. * @return The amount that couldn't be paid from the premiums account. */ function _payFromPremiums(int256 toPay) internal returns (uint256) { int256 newSurplus = _surplus - toPay; int256 maxDeficit = _maxDeficit(deficitRatio()); if (newSurplus >= maxDeficit) { _surplus = newSurplus; return 0; } _surplus = maxDeficit; return uint256(-newSurplus + maxDeficit); } /** * @dev Stores an earned pure premium. Adds to the surplus, increasing the surplus if it was positive or reducing the * deficit if it was negative. * * @param purePremiumWon The amount earned */ function _storePurePremiumWon(uint256 purePremiumWon) internal { _surplus += int256(purePremiumWon); } /** * @notice Endpoint to receive "free money" and inject that money into the premium pool. * @dev Can be used for example if the PolicyPool subscribes an excess loss policy with other company. * * @param amount The amount to be transferred. * * @custom:pre `msg.sender` approved `currency()` to this contract for at least `amount` * * @custom:emits {WonPremiumsInOut} with `moneyIn = true` */ function receiveGrant(uint256 amount) external { _storePurePremiumWon(amount); currency().safeTransferFrom(msg.sender, address(this), amount); emit WonPremiumsInOut(true, amount); } /** * @notice Withdraws excess premiums (surplus) to the destination. * @dev This might be needed in some cases for example if we are deprecating the protocol or the excess premiums * are needed to compensate something. Or to extract profits accrued either by Ensuro or the partners. * * @param amount The amount to withdraw. If amount == type(uint256).max it withdraws as much as possible and doesn't * fails. If amount != type(uint256).max it tries to withdraw that amount or it fails * @param destination The address that will receive the transferred funds. * @return Returns the actual amount withdrawn. * * @custom:pre `destination != address(0)` * @custom:pre If `amount != type(uint256).max`, then `int256(amount) <= _surplus` * * @custom:throws {InvalidDestination} if `destination == address(0)` * @custom:throws {WithdrawExceedsSurplus} if `amount != type(uint256).max` and `int256(amount) > _surplus` * * @custom:emits {WonPremiumsInOut} with `moneyIn = false` */ function withdrawWonPremiums(uint256 amount, address destination) external returns (uint256) { require(destination != address(0), InvalidDestination(destination)); if (address(yieldVault()) != address(0)) recordEarnings(); if (amount == type(uint256).max) { if (_surplus <= 0) return 0; amount = uint256(_surplus); } else { require(int256(amount) <= _surplus, WithdrawExceedsSurplus(amount, _surplus)); } _surplus -= int256(amount); _transferTo(destination, amount); emit WonPremiumsInOut(false, amount); return amount; } /// @inheritdoc IPremiumsAccount function policyCreated(Policy.PolicyData calldata policy) external override onlyPolicyPool { _activePurePremiums += policy.purePremium; if (policy.jrScr > 0) _juniorEtk.lockScr(policy.id, policy.jrScr, policy.jrInterestRate()); if (policy.srScr > 0) _seniorEtk.lockScr(policy.id, policy.srScr, policy.srInterestRate()); } /// @inheritdoc IPremiumsAccount function policyReplaced( Policy.PolicyData calldata oldPolicy, Policy.PolicyData calldata newPolicy ) external override onlyPolicyPool { /** * Assumptions: * 1. newPolicy.purePremium >= oldPolicy.purePremium (validated by PolicyPool) * 2. jrCoc != 0 ==> jrScr != 0 ^ srCoc != 0 ==> srScr != 0 (guaranteed by Policy.sol) * 3. newPolicy.jrCoc >= oldPolicy.jrCoc (validated by PolicyPool) * 4. newPolicy.srCoc >= oldPolicy.srCoc (validated by PolicyPool) * 5. Then sr/jrInterestRate() never fails */ _activePurePremiums += newPolicy.purePremium - oldPolicy.purePremium; /** * Applies an adjustment based on the difference between the accrued interest with the old policy and the new * policy. * The CoC is disbursed linearly from policy start (the same for old and new policy) and policy.expiration (might * be different). * The adjustment corrects the gap between the two straight lines at the replacement time */ if (oldPolicy.jrScr != 0) _juniorEtk.unlockScr( oldPolicy.id, oldPolicy.jrScr, oldPolicy.jrInterestRate(), int256(newPolicy.jrAccruedInterest()) - int256(oldPolicy.jrAccruedInterest()) ); if (newPolicy.jrScr > 0) _juniorEtk.lockScr(newPolicy.id, newPolicy.jrScr, newPolicy.jrInterestRate()); if (oldPolicy.srScr != 0) _seniorEtk.unlockScr( oldPolicy.id, oldPolicy.srScr, oldPolicy.srInterestRate(), int256(newPolicy.srAccruedInterest()) - int256(oldPolicy.srAccruedInterest()) ); if (newPolicy.srScr > 0) _seniorEtk.lockScr(newPolicy.id, newPolicy.srScr, newPolicy.srInterestRate()); } /// @inheritdoc IPremiumsAccount function policyCancelled( Policy.PolicyData calldata policy, uint256 purePremiumRefund, uint256 jrCocRefund, uint256 srCocRefund, address policyHolder ) external override onlyPolicyPool { _activePurePremiums -= policy.purePremium; uint256 borrowFromScr = _payFromPremiums(int256(purePremiumRefund) - int256(policy.purePremium)); if (borrowFromScr != 0) { _unlockScrWithRefund(policy, jrCocRefund, srCocRefund, policyHolder); _borrowFromEtk(borrowFromScr, policyHolder, policy.jrScr > 0); } else { _unlockScrWithRefund(policy, jrCocRefund, srCocRefund, policyHolder); } _transferTo(policyHolder, purePremiumRefund - borrowFromScr); } /// @inheritdoc IPremiumsAccount function policyResolvedWithPayout( address policyHolder, Policy.PolicyData calldata policy, uint256 payout ) external override onlyPolicyPool { _activePurePremiums -= policy.purePremium; uint256 borrowFromScr = _payFromPremiums(int256(payout) - int256(policy.purePremium)); if (borrowFromScr != 0) { _unlockScr(policy); _borrowFromEtk(borrowFromScr, policyHolder, policy.jrScr > 0); } else { _unlockScr(policy); } _transferTo(policyHolder, payout - borrowFromScr); } /** * @dev Internal function that calls the eTokens to unlock the solvency capital when the policy expires * * @param policy The policy expired */ function _unlockScr(Policy.PolicyData memory policy) internal { if (policy.jrScr > 0) { _juniorEtk.unlockScr( policy.id, policy.jrScr, policy.jrInterestRate(), int256(policy.jrCoc) - int256(policy.jrAccruedInterest()) ); } if (policy.srScr > 0) { _seniorEtk.unlockScr( policy.id, policy.srScr, policy.srInterestRate(), int256(policy.srCoc) - int256(policy.srAccruedInterest()) ); } } /** * @dev Internal function that calls the eTokens to unlock the solvency capital when the policy is cancelled, doing * refund of the jr and sr Coc * * @param policy The policy cancelled */ function _unlockScrWithRefund( Policy.PolicyData memory policy, uint256 jrCocRefund, uint256 srCocRefund, address policyHolder ) internal { if (policy.jrScr > 0) { _juniorEtk.unlockScrWithRefund( policy.id, policy.jrScr, policy.jrInterestRate(), int256(policy.jrCoc - jrCocRefund) - int256(policy.jrAccruedInterest()), policyHolder, jrCocRefund ); } if (policy.srScr > 0) { _seniorEtk.unlockScrWithRefund( policy.id, policy.srScr, policy.srInterestRate(), int256(policy.srCoc - srCocRefund) - int256(policy.srAccruedInterest()), policyHolder, srCocRefund ); } } /** * @notice Function that repays the loan(s) if fundsAvailable * * @return available The funds still available after repayment */ function repayLoans() external returns (uint256 available) { if (address(yieldVault()) != address(0)) recordEarnings(); available = fundsAvailable(); if (available != 0 && address(_seniorEtk) != address(0)) available = _repayLoan(available, _seniorEtk); if (available != 0 && address(_juniorEtk) != address(0)) available = _repayLoan(available, _juniorEtk); return available; } /** * @dev Internal function that repays a loan taken (if any outstanding) from the an eToken * * @param fundsAvailable_ The amount of funds available for the repayment * @param etk The eToken with the potential debt * @return The excess amount of the purePremiumWon that wasn't used for the loan repayment. */ function _repayLoan(uint256 fundsAvailable_, IEToken etk) internal returns (uint256) { uint256 borrowedFromEtk = etk.getLoan(address(this)); if (borrowedFromEtk == 0) return fundsAvailable_; uint256 repayAmount = Math.min(fundsAvailable_, borrowedFromEtk); _surplus -= int256(repayAmount); // Makes sure there's enough liquidity for repayAmount _transferTo(address(this), repayAmount); // Checks the allowance before repayment if (currency().allowance(address(this), address(etk)) < repayAmount) { // If I have to approve, I approve for all the pending debt (not just repayAmount), this way I avoid some // future approvals. currency().approve(address(etk), borrowedFromEtk); } etk.repayLoan(repayAmount, address(this)); return fundsAvailable_ - repayAmount; } /// @inheritdoc IPremiumsAccount function policyExpired(Policy.PolicyData calldata policy) external override onlyPolicyPool { _activePurePremiums -= policy.purePremium; _storePurePremiumWon(policy.purePremium); _unlockScr(policy); } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[47] private __gap; }