diff --git a/protocol/contracts/interfaces/v2/templeLineOfCredit/ITempleLineOfCredit.sol b/protocol/contracts/interfaces/v2/templeLineOfCredit/ITempleLineOfCredit.sol index 73dd57683..209d4b71d 100644 --- a/protocol/contracts/interfaces/v2/templeLineOfCredit/ITempleLineOfCredit.sol +++ b/protocol/contracts/interfaces/v2/templeLineOfCredit/ITempleLineOfCredit.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.17; // SPDX-License-Identifier: AGPL-3.0-or-later // Temple (interfaces/v2/templeLineOfCredit/ITempleLineOfCredit.sol) -import { ITlcDataTypes } from "contracts/interfaces/v2/templeLineOfCredit/ITlcDataTypes.sol"; +import { ITlcStorage } from "contracts/interfaces/v2/templeLineOfCredit/ITlcStorage.sol"; import { ITlcEventsAndErrors } from "contracts/interfaces/v2/templeLineOfCredit/ITlcEventsAndErrors.sol"; -interface ITempleLineOfCredit is ITlcDataTypes, ITlcEventsAndErrors { +interface ITempleLineOfCredit is ITlcStorage, ITlcEventsAndErrors { /** Add Collateral */ function addCollateral(uint256 collateralAmount, address onBehalfOf) external; @@ -26,10 +26,13 @@ interface ITempleLineOfCredit is ITlcDataTypes, ITlcEventsAndErrors { /** Position views */ function userPosition(address account) external view returns (UserPosition memory position); function totalPosition() external view returns (TotalPosition[2] memory positions); - function getUserData(address account) external view returns (UserData memory); - function getReserveToken(TokenType tokenType) external view returns (ReserveToken memory); - function getReserveCache(TokenType tokenType) external view returns (ReserveCache memory); + /** Liquidations */ + function computeLiquidity( + address[] memory accounts, + bool includePendingRequests + ) external view returns (LiquidityStatus[] memory status); + function batchLiquidate(address[] memory accounts) external; /** Liquidations */ function computeLiquidity( address[] memory accounts, @@ -37,6 +40,8 @@ interface ITempleLineOfCredit is ITlcDataTypes, ITlcEventsAndErrors { ) external view returns (LiquidityStatus[] memory status); function batchLiquidate(address[] memory accounts) external; + // Manually checkpoint debt to adjust interest rate based on latest utillization ratio + function refreshInterestRates(TokenType tokenType) external; // Manually checkpoint debt to adjust interest rate based on latest utillization ratio function refreshInterestRates(TokenType tokenType) external; @@ -47,4 +52,11 @@ interface ITempleLineOfCredit is ITlcDataTypes, ITlcEventsAndErrors { function setInterestRateModel(TokenType tokenType, address interestRateModel) external; function setMaxLtvRatio(TokenType tokenType, uint256 maxLtvRatio) external; function recoverToken(address token, address to, uint256 amount) external; + /** EXECUTORS/RESCUERS ONLY */ + function setTlcStrategy(address _tlcStrategy) external; + function setWithdrawCollateralCooldownSecs(uint256 cooldownSecs) external; + function setBorrowCooldownSecs(TokenType tokenType, uint256 cooldownSecs) external; + function setInterestRateModel(TokenType tokenType, address interestRateModel) external; + function setMaxLtvRatio(TokenType tokenType, uint256 maxLtvRatio) external; + function recoverToken(address token, address to, uint256 amount) external; } diff --git a/protocol/contracts/interfaces/v2/templeLineOfCredit/ITlcDataTypes.sol b/protocol/contracts/interfaces/v2/templeLineOfCredit/ITlcDataTypes.sol index 34ff53384..82a2117ea 100644 --- a/protocol/contracts/interfaces/v2/templeLineOfCredit/ITlcDataTypes.sol +++ b/protocol/contracts/interfaces/v2/templeLineOfCredit/ITlcDataTypes.sol @@ -6,55 +6,11 @@ import { IInterestRateModel } from "contracts/interfaces/v2/interestRate/IIntere import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface ITlcDataTypes { - struct UserDebtPosition { - uint256 debt; - uint256 maxBorrow; - uint256 healthFactor; - uint256 loanToValueRatio; - } - - struct UserPosition { - uint256 collateralPosted; - UserDebtPosition[2] debtPositions; - } - - struct TotalPosition { - /// @notice The DAI utilization rate as of the last checkpoint - uint256 utilizationRatio; - - // @notice The DAI borrow interest rate as of the last checkpoint - int256 borrowRate; - - // @notice The DAI total debt across all users as of this block - uint256 totalDebt; - } - - - - - - - enum ModuleKind { - LIQUIDATION, - POSITION - } - enum TokenType { DAI, OUD } - // enum FundsRequestType { - // BORROW_DAI, - // BORROW_OUD, - // WITHDRAW_COLLATERAL - // } - - struct WithdrawFundsRequest { - uint128 amount; - uint32 requestedAt; - } - enum TokenPriceType { /// @notice equal to 1 USD STABLE, @@ -74,6 +30,8 @@ interface ITlcDataTypes { struct ReserveTokenConfig { address tokenAddress; + address tokenAddress; + /// @notice The type of how to lookup the price of the token TokenPriceType tokenPriceType; @@ -86,6 +44,9 @@ interface ITlcDataTypes { /// @notice Maximum Loan To Value (LTV) ratio to prevent liquidation uint128 maxLtvRatio; + uint32 borrowCooldownSecs; + uint128 maxLtvRatio; + uint32 borrowCooldownSecs; } @@ -109,9 +70,15 @@ interface ITlcDataTypes { ReserveTokenTotals totals; } + struct WithdrawFundsRequest { + uint128 amount; + uint32 requestedAt; + } + struct UserTokenDebt { uint128 debt; WithdrawFundsRequest borrowRequest; + WithdrawFundsRequest borrowRequest; uint128 interestAccumulator; } @@ -128,26 +95,27 @@ interface ITlcDataTypes { uint256 collateral; uint256[2] debt; } + + struct UserDebtPosition { + uint256 debt; + uint256 maxBorrow; + uint256 healthFactor; + uint256 loanToValueRatio; + } - // @todo check if all of these are actually used - struct ReserveCache { - ReserveTokenConfig config; - - /// @notice The last time the debt was updated for this token - // uint32 interestAccumulatorUpdatedAt; - - /// @notice Total amount that has already been borrowed, which increases as interest accrues - uint128 totalDebt; + struct UserPosition { + uint256 collateralPosted; + UserDebtPosition[2] debtPositions; + } - /// @notice The interest rate as of the last borrow/repay/ - int96 interestRate; + struct TotalPosition { + /// @notice The DAI utilization rate as of the last checkpoint + uint256 utilizationRatio; - uint128 interestAccumulator; + // @notice The DAI borrow interest rate as of the last checkpoint + int256 borrowRate; - uint256 price; - - /// @notice The max allowed to be borrowed from the TRV - /// @dev Used as the denominator in the Utilisation Ratio - uint256 trvDebtCeiling; + // @notice The DAI total debt across all users as of this block + uint256 totalDebt; } } diff --git a/protocol/contracts/v2/templeLineOfCredit/TempleLineOfCredit.sol b/protocol/contracts/v2/templeLineOfCredit/TempleLineOfCredit.sol index 42139f2af..7fcfe7d03 100644 --- a/protocol/contracts/v2/templeLineOfCredit/TempleLineOfCredit.sol +++ b/protocol/contracts/v2/templeLineOfCredit/TempleLineOfCredit.sol @@ -2,28 +2,38 @@ pragma solidity ^0.8.17; // SPDX-License-Identifier: AGPL-3.0-or-later // Temple (v2/templeLineOfCredit/TempleLineOfCredit.sol) +import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IMintableToken } from "contracts/interfaces/common/IMintableToken.sol"; +import { IInterestRateModel } from "contracts/interfaces/v2/interestRate/IInterestRateModel.sol"; import { IMintableToken } from "contracts/interfaces/common/IMintableToken.sol"; import { IInterestRateModel } from "contracts/interfaces/v2/interestRate/IInterestRateModel.sol"; import { ITempleLineOfCredit } from "contracts/interfaces/v2/templeLineOfCredit/ITempleLineOfCredit.sol"; import { ITlcStrategy } from "contracts/interfaces/v2/templeLineOfCredit/ITlcStrategy.sol"; +import { ITlcStrategy } from "contracts/interfaces/v2/templeLineOfCredit/ITlcStrategy.sol"; +import { SafeCast } from "contracts/common/SafeCast.sol"; import { SafeCast } from "contracts/common/SafeCast.sol"; import { CommonEventsAndErrors } from "contracts/common/CommonEventsAndErrors.sol"; import { TempleElevatedAccess } from "contracts/v2/access/TempleElevatedAccess.sol"; import { TlcBase } from "contracts/v2/templeLineOfCredit/TlcBase.sol"; +import { TempleElevatedAccess } from "contracts/v2/access/TempleElevatedAccess.sol"; +import { TlcBase } from "contracts/v2/templeLineOfCredit/TlcBase.sol"; +contract TempleLineOfCredit is TlcBase, ITempleLineOfCredit, TempleElevatedAccess { contract TempleLineOfCredit is TlcBase, ITempleLineOfCredit, TempleElevatedAccess { using SafeERC20 for IERC20; using SafeCast for uint256; + // @dev Once constructed, setTlcStrategy() needs to be called // @dev Once constructed, setTlcStrategy() needs to be called constructor( address _initialRescuer, address _initialExecutor, address _templeToken, ReserveTokenConfig memory _daiTokenConfig, + ReserveTokenConfig memory _daiTokenConfig, ReserveTokenConfig memory _oudTokenConfig ) TempleElevatedAccess(_initialRescuer, _initialExecutor) @@ -32,8 +42,18 @@ contract TempleLineOfCredit is TlcBase, ITempleLineOfCredit, TempleElevatedAcces // Initialize the Reserve Tokens addReserveToken(reserveTokens[TokenType.DAI], _daiTokenConfig); addReserveToken(reserveTokens[TokenType.OUD], _oudTokenConfig); + ) + TempleElevatedAccess(_initialRescuer, _initialExecutor) + TlcBase(_templeToken) + { + // Initialize the Reserve Tokens + addReserveToken(reserveTokens[TokenType.DAI], _daiTokenConfig); + addReserveToken(reserveTokens[TokenType.OUD], _oudTokenConfig); } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* COLLATERAL */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* COLLATERAL */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ @@ -42,10 +62,14 @@ contract TempleLineOfCredit is TlcBase, ITempleLineOfCredit, TempleElevatedAcces * @dev Allows borrower to deposit temple collateral * @param collateralAmount is the amount to deposit */ + function addCollateral(uint256 collateralAmount, address onBehalfOf) external { + if (collateralAmount == 0) revert CommonEventsAndErrors.ExpectedNonZero(); + emit CollateralAdded(msg.sender, onBehalfOf, collateralAmount); function addCollateral(uint256 collateralAmount, address onBehalfOf) external { if (collateralAmount == 0) revert CommonEventsAndErrors.ExpectedNonZero(); emit CollateralAdded(msg.sender, onBehalfOf, collateralAmount); + allUserData[onBehalfOf].collateralPosted = (allUserData[onBehalfOf].collateralPosted + collateralAmount).encodeUInt128(); allUserData[onBehalfOf].collateralPosted = (allUserData[onBehalfOf].collateralPosted + collateralAmount).encodeUInt128(); templeToken.safeTransferFrom( @@ -75,12 +99,44 @@ contract TempleLineOfCredit is TlcBase, ITempleLineOfCredit, TempleElevatedAcces emit RemoveCollateralRequestCancelled(account); } + function requestRemoveCollateral(uint256 amount) external { + if (amount == 0) revert CommonEventsAndErrors.ExpectedNonZero(); + UserData storage _userData = allUserData[msg.sender]; + + _userData.removeCollateralRequest = WithdrawFundsRequest(amount.encodeUInt128(), uint32(block.timestamp)); + + if (_userData.removeCollateralRequest.amount > _userData.collateralPosted) revert ExceededMaxLtv(); + checkLiquidity(_userData); + + emit RemoveCollateralRequested(msg.sender, amount); + } + + function cancelRemoveCollateralRequest(address account) external { + // Either the account holder or the DAO elevated access is allowed to cancel individual requests + if (msg.sender != account && !isElevatedAccess(msg.sender, msg.sig)) revert CommonEventsAndErrors.InvalidAccess(); + + delete allUserData[msg.sender].removeCollateralRequest; + emit RemoveCollateralRequestCancelled(account); + } + /** + * @dev Allows borrower to deposit temple collateral * @dev Allows borrower to deposit temple collateral */ function removeCollateral(address recipient) external { UserData storage _userData = allUserData[msg.sender]; + uint256 _removeAmount; + { + WithdrawFundsRequest storage _request = _userData.removeCollateralRequest; + // @todo change the cooldown secs structure? + checkWithdrawalCooldown(_request.requestedAt, withdrawCollateralCooldownSecs); + _removeAmount = _request.amount; + // uint256 _removeAmount = popRequest(msg.sender, FundsRequestType.WITHDRAW_COLLATERAL); + delete allUserData[msg.sender].removeCollateralRequest; + function removeCollateral(address recipient) external { + UserData storage _userData = allUserData[msg.sender]; + uint256 _removeAmount; { WithdrawFundsRequest storage _request = _userData.removeCollateralRequest; @@ -100,6 +156,16 @@ contract TempleLineOfCredit is TlcBase, ITempleLineOfCredit, TempleElevatedAcces _userData.collateralPosted = uint128(_newCollateral); emit CollateralRemoved(msg.sender, recipient, _removeAmount); + uint256 _newCollateral = _userData.collateralPosted - _removeAmount; + + // Update the collateral, and then verify that it doesn't make the debt unsafe. + _userData.collateralPosted = uint128(_newCollateral); + + // A subtraction in collateral - so the downcast is safe + _userData.collateralPosted = uint128(_newCollateral); + emit CollateralRemoved(msg.sender, recipient, _removeAmount); + + checkLiquidity(_userData); checkLiquidity(_userData); templeToken.safeTransfer( @@ -126,8 +192,66 @@ contract TempleLineOfCredit is TlcBase, ITempleLineOfCredit, TempleElevatedAcces // they may have exceeded the max LTV in OUD. In that case // they shouldn't be allowed to borrow DAI until that OUD debt is paid down. checkLiquidity(_userData); + templeToken.safeTransfer( + recipient, + _removeAmount + ); } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* BORROW */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + function requestBorrow(TokenType tokenType, uint256 amount) external { + if (amount == 0) revert CommonEventsAndErrors.ExpectedNonZero(); + UserData storage _userData = allUserData[msg.sender]; + UserTokenDebt storage _userTokenDebt = _userData.debtData[uint256(tokenType)]; + + _userTokenDebt.borrowRequest = WithdrawFundsRequest(amount.encodeUInt128(), uint32(block.timestamp)); + emit BorrowRequested(msg.sender, tokenType, amount); + + // @todo add a test case for this. + // This will check both assets. + // This is expected, because even though they may be borrowing DAI + // they may have exceeded the max LTV in OUD. In that case + // they shouldn't be allowed to borrow DAI until that OUD debt is paid down. + checkLiquidity(_userData); + } + + function cancelBorrowRequest(address account, TokenType tokenType) external { + // Either the account holder or the DAO elevated access is allowed to cancel individual requests + if (msg.sender != account && !isElevatedAccess(msg.sender, msg.sig)) revert CommonEventsAndErrors.InvalidAccess(); + + delete allUserData[msg.sender].debtData[uint256(tokenType)].borrowRequest; + emit BorrowRequestCancelled(account, tokenType); + } + + function borrow(TokenType tokenType, address recipient) external { + UserData storage _userData = allUserData[msg.sender]; + UserTokenDebt storage _userTokenDebt = _userData.debtData[uint256(tokenType)]; + ReserveToken storage _reserveToken = reserveTokens[tokenType]; + ReserveCache memory _reserveCache = cache(_reserveToken); + + // Validate and pop the borrow request for this token + uint256 _borrowAmount; + { + WithdrawFundsRequest storage _request = _userTokenDebt.borrowRequest; + checkWithdrawalCooldown(_request.requestedAt, _reserveCache.config.borrowCooldownSecs); + _borrowAmount = _request.amount; + delete _userTokenDebt.borrowRequest; + } + + // Apply the new borrow + { + + uint256 _totalDebt = currentUserTokenDebt(_reserveCache, _userTokenDebt.debt, _userTokenDebt.interestAccumulator) + _borrowAmount; + + // Update the state + _userTokenDebt.debt = _totalDebt.encodeUInt128(); + _userTokenDebt.interestAccumulator = _reserveCache.interestAccumulator; + _reserveToken.totals.totalDebt = _reserveCache.totalDebt = ( + _reserveCache.totalDebt + _borrowAmount + ).encodeUInt128(); function cancelBorrowRequest(address account, TokenType tokenType) external { // Either the account holder or the DAO elevated access is allowed to cancel individual requests if (msg.sender != account && !isElevatedAccess(msg.sender, msg.sig)) revert CommonEventsAndErrors.InvalidAccess(); @@ -166,11 +290,24 @@ contract TempleLineOfCredit is TlcBase, ITempleLineOfCredit, TempleElevatedAcces // Update the borrow interest rates based on the now increased utilization ratio // console.log("about to update interest rate"); updateInterestRates(_reserveToken, _reserveCache); + // Update the borrow interest rates based on the now increased utilization ratio + // console.log("about to update interest rate"); + updateInterestRates(_reserveToken, _reserveCache); } emit Borrow(msg.sender, recipient, tokenType, _borrowAmount); checkLiquidity(_userData); + // Finally, send the tokens to the user. + if (tokenType == TokenType.DAI) { + // Borrow the funds from the TRV and send to the recipient + tlcStrategy.fundFromTrv(_borrowAmount, recipient); + } else { + // Mint the OUD and send to recipient + IMintableToken(_reserveCache.config.tokenAddress).mint(recipient, _borrowAmount ); + emit Borrow(msg.sender, recipient, tokenType, _borrowAmount); + checkLiquidity(_userData); + // Finally, send the tokens to the user. if (tokenType == TokenType.DAI) { // Borrow the funds from the TRV and send to the recipient @@ -193,10 +330,27 @@ contract TempleLineOfCredit is TlcBase, ITempleLineOfCredit, TempleElevatedAcces _doRepayToken(_reserveToken, cache(_reserveToken), repayAmount, _userTokenDebt, tokenType, msg.sender, onBehalfOf); } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* REPAY */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + function repay(TokenType tokenType, uint256 repayAmount, address onBehalfOf) external { + if (repayAmount == 0) revert CommonEventsAndErrors.ExpectedNonZero(); + + ReserveToken storage _reserveToken = reserveTokens[tokenType]; + UserTokenDebt storage _userTokenDebt = allUserData[onBehalfOf].debtData[uint256(tokenType)]; + _doRepayToken(_reserveToken, cache(_reserveToken), repayAmount, _userTokenDebt, tokenType, msg.sender, onBehalfOf); + } + /** * @notice Allows borrower to repay all outstanding balances * @dev leave no dust balance */ + function repayAll(TokenType tokenType, address onBehalfOf) external { + ReserveToken storage _reserveToken = reserveTokens[tokenType]; + ReserveCache memory _reserveCache = cache(_reserveToken); + UserTokenDebt storage _userTokenDebt = allUserData[onBehalfOf].debtData[uint256(tokenType)]; + function repayAll(TokenType tokenType, address onBehalfOf) external { ReserveToken storage _reserveToken = reserveTokens[tokenType]; ReserveCache memory _reserveCache = cache(_reserveToken); @@ -207,11 +361,29 @@ contract TempleLineOfCredit is TlcBase, ITempleLineOfCredit, TempleElevatedAcces if (repayAmount == 0) revert CommonEventsAndErrors.ExpectedNonZero(); _doRepayToken(_reserveToken, _reserveCache, repayAmount, _userTokenDebt, tokenType, msg.sender, onBehalfOf); } + uint256 repayAmount = currentUserTokenDebt(_reserveCache, _userTokenDebt.debt, _userTokenDebt.interestAccumulator); + if (repayAmount == 0) revert CommonEventsAndErrors.ExpectedNonZero(); + _doRepayToken(_reserveToken, _reserveCache, repayAmount, _userTokenDebt, tokenType, msg.sender, onBehalfOf); + } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* LIQUIDATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + // Given there are no dependencies on price, relying on off-chain lists of + // users (eg via subgraph) is enough for the bot to get the list of users + function computeLiquidity( + address[] memory accounts, + bool includePendingRequests + ) external view returns (LiquidityStatus[] memory status) { + ReserveCache[NUM_TOKEN_TYPES] memory reserveCaches = [ + cacheRO(reserveTokens[TokenType.DAI]), + cacheRO(reserveTokens[TokenType.OUD]) + ]; + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* LIQUIDATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + // Given there are no dependencies on price, relying on off-chain lists of // users (eg via subgraph) is enough for the bot to get the list of users function computeLiquidity( @@ -223,6 +395,13 @@ contract TempleLineOfCredit is TlcBase, ITempleLineOfCredit, TempleElevatedAcces cacheRO(reserveTokens[TokenType.OUD]) ]; + uint256 _numAccounts = accounts.length; + for (uint256 i; i < _numAccounts; ++i) { + status[i] = computeLiquidity( + allUserData[accounts[i]], + reserveCaches, + includePendingRequests + ); uint256 _numAccounts = accounts.length; for (uint256 i; i < _numAccounts; ++i) { status[i] = computeLiquidity( @@ -233,11 +412,21 @@ contract TempleLineOfCredit is TlcBase, ITempleLineOfCredit, TempleElevatedAcces } } + function batchLiquidate(address[] memory accounts) external { + LiquidityStatus memory _status; + uint256 _daiIndex = uint256(TokenType.DAI); + uint256 _oudIndex = uint256(TokenType.OUD); function batchLiquidate(address[] memory accounts) external { LiquidityStatus memory _status; uint256 _daiIndex = uint256(TokenType.DAI); uint256 _oudIndex = uint256(TokenType.OUD); + uint256 _numAccounts = accounts.length; + uint256 totalCollateralClaimed; + ReserveCache[NUM_TOKEN_TYPES] memory reserveCaches = [ + cache(reserveTokens[TokenType.DAI]), + cache(reserveTokens[TokenType.OUD]) + ]; uint256 _numAccounts = accounts.length; uint256 totalCollateralClaimed; ReserveCache[NUM_TOKEN_TYPES] memory reserveCaches = [ @@ -245,6 +434,12 @@ contract TempleLineOfCredit is TlcBase, ITempleLineOfCredit, TempleElevatedAcces cache(reserveTokens[TokenType.OUD]) ]; + uint256[NUM_TOKEN_TYPES] memory totalDebtWiped; + uint256 i; + address _account; + for (; i < _numAccounts; ++i) { + _account = accounts[i]; + _status = computeLiquidity(allUserData[_account], reserveCaches, false); uint256[NUM_TOKEN_TYPES] memory totalDebtWiped; uint256 i; address _account; @@ -260,10 +455,35 @@ contract TempleLineOfCredit is TlcBase, ITempleLineOfCredit, TempleElevatedAcces delete allUserData[_account]; } } + // Skip if this account is still under the maxLTV across all assets + if (_status.hasExceededMaxLtv) { + totalCollateralClaimed += _status.collateral; + totalDebtWiped[_daiIndex] += _status.debt[_daiIndex]; + totalDebtWiped[_oudIndex] += _status.debt[_oudIndex]; + delete allUserData[_account]; + } + } // burn the temple collateral by repaying to TRV. This will burn the equivalent dUSD debt too. treasuryReservesVault.repayTemple(totalCollateralClaimed, address(tlcStrategy)); + // Update the reserve token total state and update interest rates. + for (i = 0; i < NUM_TOKEN_TYPES; ++i) { + ReserveToken storage _reserveToken = reserveTokens[TokenType(i)]; + // LiquidationTokenParams memory _tokenParams = _liquidationParams.tokens[i]; + ReserveCache memory _reserveCache = reserveCaches[i]; + + // Update the reserve token details, and then update the interest rates. + // A decrease in amount, so this downcast is safe without a check + _reserveToken.totals.totalDebt = _reserveCache.totalDebt = uint128( + _reserveCache.totalDebt - totalDebtWiped[i] + ); + + updateInterestRates(_reserveToken, _reserveCache); + } + // burn the temple collateral by repaying to TRV. This will burn the equivalent dUSD debt too. + treasuryReservesVault.repayTemple(totalCollateralClaimed, address(tlcStrategy)); + // Update the reserve token total state and update interest rates. for (i = 0; i < NUM_TOKEN_TYPES; ++i) { ReserveToken storage _reserveToken = reserveTokens[TokenType(i)]; @@ -290,6 +510,23 @@ contract TempleLineOfCredit is TlcBase, ITempleLineOfCredit, TempleElevatedAcces // so the rate is updated. // add a test for this. + function setTlcStrategy( + address _tlcStrategy + ) external onlyElevatedAccess { + tlcStrategy = ITlcStrategy(_tlcStrategy); + treasuryReservesVault = tlcStrategy.treasuryReservesVault(); + emit TlcStrategySet(_tlcStrategy); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ADMIN */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // @todo Check we have setters and events for all the things + + // @todo when the TRV cap changes, the UR will change. A checkpoint will need to be done then too, + // so the rate is updated. + // add a test for this. + function setTlcStrategy( address _tlcStrategy ) external onlyElevatedAccess { @@ -298,16 +535,26 @@ contract TempleLineOfCredit is TlcBase, ITempleLineOfCredit, TempleElevatedAcces emit TlcStrategySet(_tlcStrategy); } + function setWithdrawCollateralCooldownSecs(uint256 cooldownSecs) external onlyElevatedAccess { + withdrawCollateralCooldownSecs = uint32(cooldownSecs); + emit WithdrawCollateralCooldownSecsSet(cooldownSecs); function setWithdrawCollateralCooldownSecs(uint256 cooldownSecs) external onlyElevatedAccess { withdrawCollateralCooldownSecs = uint32(cooldownSecs); emit WithdrawCollateralCooldownSecsSet(cooldownSecs); } + function setBorrowCooldownSecs(TokenType tokenType, uint256 cooldownSecs) external onlyElevatedAccess { + reserveTokens[tokenType].config.borrowCooldownSecs = uint32(cooldownSecs); + emit BorrowCooldownSecsSet(tokenType, uint32(cooldownSecs)); function setBorrowCooldownSecs(TokenType tokenType, uint256 cooldownSecs) external onlyElevatedAccess { reserveTokens[tokenType].config.borrowCooldownSecs = uint32(cooldownSecs); emit BorrowCooldownSecsSet(tokenType, uint32(cooldownSecs)); } + function setInterestRateModel(TokenType tokenType, address interestRateModel) external onlyElevatedAccess { + ReserveToken storage reserveToken = reserveTokens[tokenType]; //_getReserveToken(tokenType); + ReserveCache memory reserveCache = cache(reserveToken); + function setInterestRateModel(TokenType tokenType, address interestRateModel) external onlyElevatedAccess { ReserveToken storage reserveToken = reserveTokens[tokenType]; //_getReserveToken(tokenType); ReserveCache memory reserveCache = cache(reserveToken); @@ -315,13 +562,26 @@ contract TempleLineOfCredit is TlcBase, ITempleLineOfCredit, TempleElevatedAcces // Update the cache entry and calculate the new interest rate based off this model. reserveToken.config.interestRateModel = reserveCache.config.interestRateModel = IInterestRateModel(interestRateModel); updateInterestRates(reserveToken, reserveCache); + // Update the cache entry and calculate the new interest rate based off this model. + reserveToken.config.interestRateModel = reserveCache.config.interestRateModel = IInterestRateModel(interestRateModel); + updateInterestRates(reserveToken, reserveCache); } + function setMaxLtvRatio(TokenType tokenType, uint256 maxLtvRatio) external onlyElevatedAccess { + ReserveToken storage reserveToken = reserveTokens[tokenType]; //_getReserveToken(tokenType); + reserveToken.config.maxLtvRatio = maxLtvRatio.encodeUInt128(); function setMaxLtvRatio(TokenType tokenType, uint256 maxLtvRatio) external onlyElevatedAccess { ReserveToken storage reserveToken = reserveTokens[tokenType]; //_getReserveToken(tokenType); reserveToken.config.maxLtvRatio = maxLtvRatio.encodeUInt128(); } + /** + * @notice Governance can recover any token from the strategy. + */ + function recoverToken(address token, address to, uint256 amount) external /*override*/ onlyElevatedAccess { + // @todo need to change so collateral can't be pinched. + emit CommonEventsAndErrors.TokenRecovered(to, token, amount); + IERC20(token).safeTransfer(to, amount); /** * @notice Governance can recover any token from the strategy. */ @@ -331,7 +591,10 @@ contract TempleLineOfCredit is TlcBase, ITempleLineOfCredit, TempleElevatedAcces IERC20(token).safeTransfer(to, amount); } + /** + * @notice Update the total debt up until now, then recalculate the interest rate + * based on the updated utilisation ratio * @notice Update the total debt up until now, then recalculate the interest rate * based on the updated utilisation ratio */ @@ -348,10 +611,6 @@ contract TempleLineOfCredit is TlcBase, ITempleLineOfCredit, TempleElevatedAcces return allUserData[account]; } - function getReserveToken(TokenType tokenType) external view returns (ReserveToken memory) { - return reserveTokens[tokenType]; - } - function getReserveCache(TokenType tokenType) external view returns (ReserveCache memory) { return cacheRO(reserveTokens[tokenType]); } diff --git a/protocol/contracts/v2/templeLineOfCredit/TlcBase.sol b/protocol/contracts/v2/templeLineOfCredit/TlcBase.sol index 88a6fc04c..bfd5f2262 100644 --- a/protocol/contracts/v2/templeLineOfCredit/TlcBase.sol +++ b/protocol/contracts/v2/templeLineOfCredit/TlcBase.sol @@ -23,6 +23,28 @@ abstract contract TlcBase is TlcStorage, ITlcEventsAndErrors { using SafeCast for uint256; using CompoundedInterest for uint256; + // @todo check if all of these are actually used + struct ReserveCache { + ReserveTokenConfig config; + + /// @notice The last time the debt was updated for this token + // uint32 interestAccumulatorUpdatedAt; + + /// @notice Total amount that has already been borrowed, which increases as interest accrues + uint128 totalDebt; + + /// @notice The interest rate as of the last borrow/repay/ + int96 interestRate; + + uint128 interestAccumulator; + + uint256 price; + + /// @notice The max allowed to be borrowed from the TRV + /// @dev Used as the denominator in the Utilisation Ratio + uint256 trvDebtCeiling; + } + constructor(address _templeToken) TlcStorage(_templeToken) {} diff --git a/protocol/contracts/v2/templeLineOfCredit/TlcStorage.sol b/protocol/contracts/v2/templeLineOfCredit/TlcStorage.sol index 335e3018b..6f23baf39 100644 --- a/protocol/contracts/v2/templeLineOfCredit/TlcStorage.sol +++ b/protocol/contracts/v2/templeLineOfCredit/TlcStorage.sol @@ -3,23 +3,32 @@ pragma solidity ^0.8.17; // Temple (v2/templeLineOfCredit/TlcStorage.sol) import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { ITlcDataTypes } from "contracts/interfaces/v2/templeLineOfCredit/ITlcDataTypes.sol"; +import { ITlcStorage } from "contracts/interfaces/v2/templeLineOfCredit/ITlcStorage.sol"; import { ITreasuryReservesVault } from "contracts/interfaces/v2/ITreasuryReservesVault.sol"; import { ITlcStrategy } from "contracts/interfaces/v2/templeLineOfCredit/ITlcStrategy.sol"; -abstract contract TlcStorage is ITlcDataTypes { - - ITlcStrategy public tlcStrategy; +abstract contract TlcStorage is ITlcStorage { + ITlcStrategy public override tlcStrategy; /** * @notice Collateral Token supplied by users */ - IERC20 public immutable templeToken; + IERC20 public immutable override templeToken; /** * @notice Collateral Token supplied by users */ - ITreasuryReservesVault public treasuryReservesVault; + ITreasuryReservesVault public override treasuryReservesVault; + + uint32 public override withdrawCollateralCooldownSecs; + + // @todo add tests to check the sizes + uint256 public override constant NUM_TOKEN_TYPES = 2; + + // @todo check constants + uint256 internal constant INITIAL_INTEREST_ACCUMULATOR = 1e27; + uint256 public override constant PRICE_PRECISION = 1e18; + uint256 public override constant LTV_PRECISION = 1e18; /** * @notice User collateral and current token debt information @@ -29,23 +38,12 @@ abstract contract TlcStorage is ITlcDataTypes { /** * @notice Configuration and current data for borrowed tokens */ - mapping(TokenType => ReserveToken) public reserveTokens; - - uint32 public withdrawCollateralCooldownSecs; - - // @todo add tests to check the sizes - uint256 public constant NUM_TOKEN_TYPES = 2; - - // @todo check constants - uint256 internal constant INITIAL_INTEREST_ACCUMULATOR = 1e27; - uint256 public constant PRICE_PRECISION = 1e18; - uint256 public constant LTV_PRECISION = 1e18; + mapping(TokenType => ReserveToken) public override reserveTokens; constructor(address _templeToken) { templeToken = IERC20(_templeToken); } - - +} } \ No newline at end of file diff --git a/protocol/test/forge/v2/templeLineOfCredit/TlcBaseTest.t.sol b/protocol/test/forge/v2/templeLineOfCredit/TlcBaseTest.t.sol index 70832df07..0f4543c35 100644 --- a/protocol/test/forge/v2/templeLineOfCredit/TlcBaseTest.t.sol +++ b/protocol/test/forge/v2/templeLineOfCredit/TlcBaseTest.t.sol @@ -203,13 +203,9 @@ contract TlcBaseTest is TempleTest, ITlcDataTypes, ITlcEventsAndErrors { uint256 expectedOudAccumulatorCheckpoint ) internal { UserPosition memory actualUserPosition = tlc.userPosition(user); + + // @todo add checks for removeCollateralRequest? UserData memory actualUserData = tlc.getUserData(user); - // ( - // uint256 collateralPosted, - // ITempleLineOfCredit.UserTokenDebt[2] memory actualUserTokenDebts - // // ITempleLineOfCredit.UserTokenDebt memory actualDaiUserTokenDebt, - // // ITempleLineOfCredit.UserTokenDebt memory actualOudUserTokenDebt - // ) = tlc.getUserData(user); assertEq(actualUserPosition.collateralPosted, expectedUserPosition.collateralPosted, "collateral"); assertEq(actualUserData.collateralPosted, actualUserPosition.collateralPosted, "collateral 2"); @@ -243,14 +239,11 @@ contract TlcBaseTest is TempleTest, ITlcDataTypes, ITlcEventsAndErrors { uint256 expectedOudDebt, uint256 expectedOudInterestAccumulator ) internal { + // @todo is this a dup/similar to above? + UserData memory actualUserData = tlc.getUserData(account); - // ( - // uint256 collateralPosted, - // ITempleLineOfCredit.UserTokenDebt[2] memory actualUserTokenDebts - // // ITempleLineOfCredit.UserTokenDebt memory daiDebt, - // // ITempleLineOfCredit.UserTokenDebt memory oudDebt - // ) = tlc.getUserData(account); + // @todo add check for removeCollateralRequest assertEq(actualUserData.collateralPosted, expectedCollateral, "collateral"); assertEq(actualUserData.debtData[0].debt, expectedDaiDebt, "DAI debt");