Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/PriceFeeds/yieldBearingStablecoinPriceFeed.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import "./MainnetPriceFeedBase.sol";
import "openzeppelin-contracts/contracts/interfaces/IERC4626.sol";
contract YieldBearingStablecoinPriceFeed is MainnetPriceFeedBase {
uint256 public constant UNDERLYING_USD_DEVIATION_THRESHOLD = 2e16; // 2%
IERC4626 public immutable yieldBearingToken;
constructor(
address _underlyingUsdOracleAddress, // Underlying asset/USD oracle
address _yieldBearingToken, // ERC4626 yield-bearing token address
uint256 _underlyingUsdStalenessThreshold,
address _borrowerOperationsAddress
) MainnetPriceFeedBase(_underlyingUsdOracleAddress, _underlyingUsdStalenessThreshold, _borrowerOperationsAddress) {
yieldBearingToken = IERC4626(_yieldBearingToken);
_fetchPricePrimary(false);
// Check the oracle didn't already fail
assert(priceSource == PriceSource.primary);
}
function fetchPrice() public returns (uint256, bool) {
return _fetchPricePrimary(false);
}
function fetchRedemptionPrice() external returns (uint256, bool) {
return _fetchPricePrimary(true);
}
function _fetchPricePrimary(bool _isRedemption) internal returns (uint256, bool) {
// If using last good price due to oracle failure, return it
if (priceSource == PriceSource.lastGoodPrice) {
return (lastGoodPrice, false);
}
// Get underlying asset/USD price (returns in 18 decimals from _getOracleAnswer)
(uint256 underlyingUsdPrice, bool underlyingUsdOracleDown) = _getOracleAnswer(ethUsdOracle);
if (underlyingUsdOracleDown) {
return (_shutDownAndSwitchToLastGoodPrice(address(ethUsdOracle.aggregator)), true);
}
// Get canonical rate from ERC4626 yield-bearing token (18 decimals)
uint256 yieldBearingRate = yieldBearingToken.convertToAssets(1e18);
uint256 canonicalPrice = underlyingUsdPrice * yieldBearingRate / 1e18;
uint256 price = canonicalPrice;
lastGoodPrice = price;
return (price, false);
}
function _withinDeviationThreshold(uint256 _priceToCheck, uint256 _referencePrice, uint256 _deviationThreshold)
internal
pure
returns (bool)
{
uint256 max = _referencePrice * (DECIMAL_PRECISION + _deviationThreshold) / DECIMAL_PRECISION;
uint256 min = _referencePrice * (DECIMAL_PRECISION - _deviationThreshold) / DECIMAL_PRECISION;
return _priceToCheck >= min && _priceToCheck <= max;
}
}
"
},
"src/PriceFeeds/MainnetPriceFeedBase.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import "../Dependencies/Ownable.sol";
import "../Dependencies/AggregatorV3Interface.sol";
import "../Interfaces/IMainnetPriceFeed.sol";
import "../BorrowerOperations.sol";
abstract contract MainnetPriceFeedBase is IMainnetPriceFeed {
// Determines where the PriceFeed sources data from. Possible states:
// - primary: Uses the primary price calcuation, which depends on the specific feed
// - ETHUSDxCanonical: Uses Chainlink's ETH-USD multiplied by the LST' canonical rate
// - lastGoodPrice: the last good price recorded by this PriceFeed.
PriceSource public priceSource;
// Last good price tracker for the derived USD price
uint256 public lastGoodPrice;
struct Oracle {
AggregatorV3Interface aggregator;
uint256 stalenessThreshold;
uint8 decimals;
}
struct ChainlinkResponse {
uint80 roundId;
int256 answer;
uint256 timestamp;
bool success;
}
error InsufficientGasForExternalCall();
event ShutDownFromOracleFailure(address _failedOracleAddr);
Oracle public ethUsdOracle;
IBorrowerOperations borrowerOperations;
constructor(address _ethUsdOracleAddress, uint256 _ethUsdStalenessThreshold, address _borrowOperationsAddress) {
// Store ETH-USD oracle
ethUsdOracle.aggregator = AggregatorV3Interface(_ethUsdOracleAddress);
ethUsdOracle.stalenessThreshold = _ethUsdStalenessThreshold;
ethUsdOracle.decimals = ethUsdOracle.aggregator.decimals();
borrowerOperations = IBorrowerOperations(_borrowOperationsAddress);
assert(ethUsdOracle.decimals == 8);
}
function _getOracleAnswer(Oracle memory _oracle) internal view returns (uint256, bool) {
ChainlinkResponse memory chainlinkResponse = _getCurrentChainlinkResponse(_oracle.aggregator);
uint256 scaledPrice;
bool oracleIsDown;
// Check oracle is serving an up-to-date and sensible price. If not, shut down this collateral branch.
if (!_isValidChainlinkPrice(chainlinkResponse, _oracle.stalenessThreshold)) {
oracleIsDown = true;
} else {
scaledPrice = _scaleChainlinkPriceTo18decimals(chainlinkResponse.answer, _oracle.decimals);
}
return (scaledPrice, oracleIsDown);
}
function _shutDownAndSwitchToLastGoodPrice(address _failedOracleAddr) internal returns (uint256) {
// Shut down the branch
borrowerOperations.shutdownFromOracleFailure();
priceSource = PriceSource.lastGoodPrice;
emit ShutDownFromOracleFailure(_failedOracleAddr);
return lastGoodPrice;
}
function _getCurrentChainlinkResponse(AggregatorV3Interface _aggregator)
internal
view
returns (ChainlinkResponse memory chainlinkResponse)
{
uint256 gasBefore = gasleft();
// Try to get latest price data:
try _aggregator.latestRoundData() returns (
uint80 roundId, int256 answer, uint256, /* startedAt */ uint256 updatedAt, uint80 /* answeredInRound */
) {
// If call to Chainlink succeeds, return the response and success = true
chainlinkResponse.roundId = roundId;
chainlinkResponse.answer = answer;
chainlinkResponse.timestamp = updatedAt;
chainlinkResponse.success = true;
return chainlinkResponse;
} catch {
// Require that enough gas was provided to prevent an OOG revert in the call to Chainlink
// causing a shutdown. Instead, just revert. Slightly conservative, as it includes gas used
// in the check itself.
if (gasleft() <= gasBefore / 64) revert InsufficientGasForExternalCall();
// If call to Chainlink aggregator reverts, return a zero response with success = false
return chainlinkResponse;
}
}
// False if:
// - Call to Chainlink aggregator reverts
// - price is too stale, i.e. older than the oracle's staleness threshold
// - Price answer is 0 or negative
function _isValidChainlinkPrice(ChainlinkResponse memory chainlinkResponse, uint256 _stalenessThreshold)
internal
view
returns (bool)
{
return chainlinkResponse.success && block.timestamp - chainlinkResponse.timestamp < _stalenessThreshold
&& chainlinkResponse.answer > 0;
}
// Trust assumption: Chainlink won't change the decimal precision on any feed used in v2 after deployment
function _scaleChainlinkPriceTo18decimals(int256 _price, uint256 _decimals) internal pure returns (uint256) {
// Scale an int price to a uint with 18 decimals
return uint256(_price) * 10 ** (18 - _decimals);
}
}
"
},
"lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol)
pragma solidity ^0.8.0;
import "../token/ERC20/IERC20.sol";
import "../token/ERC20/extensions/IERC20Metadata.sol";
/**
* @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in
* https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
*
* _Available since v4.7._
*/
interface IERC4626 is IERC20, IERC20Metadata {
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed sender,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/**
* @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
*
* - MUST be an ERC-20 token contract.
* - MUST NOT revert.
*/
function asset() external view returns (address assetTokenAddress);
/**
* @dev Returns the total amount of the underlying asset that is “managed” by Vault.
*
* - SHOULD include any compounding that occurs from yield.
* - MUST be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT revert.
*/
function totalAssets() external view returns (uint256 totalManagedAssets);
/**
* @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToShares(uint256 assets) external view returns (uint256 shares);
/**
* @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToAssets(uint256 shares) external view returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
* through a deposit call.
*
* - MUST return a limited value if receiver is subject to some deposit limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
* - MUST NOT revert.
*/
function maxDeposit(address receiver) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
* call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
* in the same transaction.
* - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
* deposit would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewDeposit(uint256 assets) external view returns (uint256 shares);
/**
* @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* deposit execution, and are accounted for during deposit.
* - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
* - MUST return a limited value if receiver is subject to some mint limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
* - MUST NOT revert.
*/
function maxMint(address receiver) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
* in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
* same transaction.
* - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
* would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by minting.
*/
function previewMint(uint256 shares) external view returns (uint256 assets);
/**
* @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
* execution, and are accounted for during mint.
* - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function mint(uint256 shares, address receiver) external returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
* Vault, through a withdraw call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxWithdraw(address owner) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
* call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
* called
* in the same transaction.
* - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
* the withdrawal would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
/**
* @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* withdraw execution, and are accounted for during withdraw.
* - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
* through a redeem call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxRedeem(address owner) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
* in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
* same transaction.
* - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
* redemption would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by redeeming.
*/
function previewRedeem(uint256 shares) external view returns (uint256 assets);
/**
* @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* redeem execution, and are accounted for during redeem.
* - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}
"
},
"src/Dependencies/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
/**
* Based on OpenZeppelin's Ownable contract:
* https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol
*
* @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 {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting `initialOwner` as the initial owner.
*/
constructor(address initialOwner) {
_owner = initialOwner;
emit OwnershipTransferred(address(0), initialOwner);
}
/**
* @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(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Returns true if the caller is the current owner.
*/
function isOwner() public view returns (bool) {
return msg.sender == _owner;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*
* NOTE: This function is not safe, as it doesn’t check owner is calling it.
* Make sure you check it before calling it.
*/
function _renounceOwnership() internal {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
}
"
},
"src/Dependencies/AggregatorV3Interface.sol": {
"content": "// SPDX-License-Identifier: MIT
// Code from https://github.com/smartcontractkit/chainlink/blob/master/evm-contracts/src/v0.6/interfaces/AggregatorV3Interface.sol
pragma solidity 0.8.24;
interface AggregatorV3Interface {
function decimals() external view returns (uint8);
function latestRoundData()
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}
"
},
"src/Interfaces/IMainnetPriceFeed.sol": {
"content": "// SPDX-License-Identifier: MIT
import "../Interfaces/IPriceFeed.sol";
import "../Dependencies/AggregatorV3Interface.sol";
pragma solidity ^0.8.0;
interface IMainnetPriceFeed is IPriceFeed {
enum PriceSource {
primary,
ETHUSDxCanonical,
lastGoodPrice
}
function ethUsdOracle() external view returns (AggregatorV3Interface, uint256, uint8);
function priceSource() external view returns (PriceSource);
}
"
},
"src/BorrowerOperations.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.24;
import "./Dependencies/LiquityBase.sol";
import "./Dependencies/AddRemoveManagers.sol";
import "./Interfaces/IBorrowerOperations.sol";
import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import "./Interfaces/IAddressesRegistry.sol";
import "./Interfaces/ITroveManager.sol";
import "./Interfaces/IBoldToken.sol";
import "./Interfaces/ICollSurplusPool.sol";
import "./Interfaces/ISortedTroves.sol";
import "./Types/LatestTroveData.sol";
import "./Types/LatestBatchData.sol";
import {IEbisuBorrowerOperationsHelper} from "./Interfaces/IEbisuBorrowerOperationsHelper.sol";
import {IEbisuBranchManager} from "./Interfaces/IEbisuBranchManager.sol";
import "./Dependencies/EbisuUpgradeable.sol";
contract BorrowerOperations is LiquityBase, AddRemoveManagers, IBorrowerOperations, EbisuUpgradeable {
using SafeERC20 for IERC20;
// --- Connected contract declarations ---
IERC20 internal collToken;
ITroveManager internal troveManager;
address internal gasPoolAddress;
ICollSurplusPool internal collSurplusPool;
IBoldToken internal boldToken;
IEbisuBorrowerOperationsHelper public boHelper;
IEbisuBranchManager internal branchManager;
// A doubly linked list of Troves, sorted by their collateral ratios
ISortedTroves internal sortedTroves;
// Wrapped ETH for liquidation reserve (gas compensation)
IWETH internal WETH;
// Shutdown system collateral ratio. If the system's total collateral ratio (TCR) for a given collateral falls below the SCR,
// the protocol triggers the shutdown of the borrow market and permanently disables all borrowing operations except for closing Troves.
bool public hasBeenShutDown;
// Extra buffer of collateral ratio to join a batch or adjust a trove inside a batch (on top of MCR)
uint256 private BCR;
uint256 private minDebt;
/*
* Mapping from TroveId to granted address for interest rate setting (batch manager).
*
* Batch managers set the interest rate for every Trove in the batch. The interest rate is the same for all Troves in the batch.
*/
mapping(uint256 => address) public interestBatchManagerOf;
// Reserved for future upgrades
uint256[50] private __gap;
/* --- Variable container structs ---
Used to hold, return and assign variables inside a function, in order to avoid the error:
"CompilerError: Stack too deep". */
struct OpenTroveVars {
ITroveManager troveManager;
uint256 troveId;
TroveChange change;
LatestBatchData batch;
}
struct LocalVariables_removeFromBatch {
ITroveManager troveManager;
ISortedTroves sortedTroves;
address batchManager;
LatestTroveData trove;
LatestBatchData batch;
uint256 batchFutureDebt;
TroveChange batchChange;
}
error IsShutDown();
error TCRNotBelowSCR();
error TroveExists();
error TroveNotOpen();
error TroveNotActive();
error TroveNotZombie();
error TroveWithZeroDebt();
error UpfrontFeeTooHigh();
error ICRBelowMCR();
error ICRBelowMCRPlusBCR();
error TCRBelowCCR();
error DebtBelowMin();
error NewOracleFailureDetected();
error TroveNotInBatch();
error CallerNotTroveManager();
error CallerNotPriceFeed();
error MintingDebtPaused();
error DebtCapReached();
error BatchSharesRatioTooLow();
error CallerNotBorrowerOperationsHelper();
error CallerNotBranchManager();
event ShutDown(uint256 _tcr);
constructor() {
_disableInitializers();
}
//TODO: remove virtual on prod
function initialize(address _addressesRegistryAddress)
public
virtual
override(AddRemoveManagers, LiquityBase, IBorrowerOperations)
initializer
{
IAddressesRegistry _addressesRegistry = IAddressesRegistry(_addressesRegistryAddress);
LiquityBase.initialize(_addressesRegistryAddress);
AddRemoveManagers.initialize(_addressesRegistryAddress);
_ebisuAdminRegistry = IEbisuAdminRegistry(address(_addressesRegistry.collateralRegistry()));
collToken = _addressesRegistry.collToken();
WETH = _addressesRegistry.WETH();
troveManager = _addressesRegistry.troveManager();
gasPoolAddress = _addressesRegistry.gasPoolAddress();
collSurplusPool = _addressesRegistry.collSurplusPool();
sortedTroves = _addressesRegistry.sortedTroves();
boldToken = _addressesRegistry.boldToken();
boHelper = IEbisuBorrowerOperationsHelper(_addressesRegistry.borrowerOperationsHelper());
branchManager = _addressesRegistry.branchManager();
collToken.approve(address(activePool), type(uint256).max);
BCR = _addressesRegistry.BCR();
minDebt = MIN_DEBT;
// This makes impossible to open a trove with zero withdrawn Bold
assert(minDebt > 0);
}
// --- Borrower Trove Operations ---
function openTrove(
address _owner,
uint256 _ownerIndex,
uint256 _collAmount,
uint256 _boldAmount,
uint256 _upperHint,
uint256 _lowerHint,
uint256 _annualInterestRate,
uint256 _maxUpfrontFee,
address _addManager,
address _removeManager,
address _receiver
) external virtual override returns (uint256) {
branchManager.requireValidAnnualInterestRate(_annualInterestRate);
OpenTroveVars memory vars;
vars.troveId = _openTrove(
_owner,
_ownerIndex,
_collAmount,
_boldAmount,
_annualInterestRate,
address(0),
0,
0,
_maxUpfrontFee,
_addManager,
_removeManager,
_receiver,
vars.change
);
// Set the stored Trove properties and mint the NFT
troveManager.onOpenTrove(_owner, vars.troveId, vars.change, _annualInterestRate);
sortedTroves.insert(vars.troveId, _annualInterestRate, _upperHint, _lowerHint);
return vars.troveId;
}
function openTroveAndJoinInterestBatchManager(OpenTroveAndJoinInterestBatchManagerParams calldata _params)
external
virtual
override
returns (uint256)
{
boHelper.requireValidInterestBatchManager(_params.interestBatchManager);
OpenTroveVars memory vars;
vars.troveManager = troveManager;
vars.batch = vars.troveManager.getLatestBatchData(_params.interestBatchManager);
// We set old weighted values here, as it's only necessary for batches, so we don't need to pass them to _openTrove func
vars.change.batchAccruedManagementFee = vars.batch.accruedManagementFee;
vars.change.oldWeightedRecordedDebt = vars.batch.weightedRecordedDebt;
vars.change.oldWeightedRecordedBatchManagementFee = vars.batch.weightedRecordedBatchManagementFee;
vars.troveId = _openTrove(
_params.owner,
_params.ownerIndex,
_params.collAmount,
_params.boldAmount,
vars.batch.annualInterestRate,
_params.interestBatchManager,
vars.batch.entireDebtWithoutRedistribution,
vars.batch.annualManagementFee,
_params.maxUpfrontFee,
_params.addManager,
_params.removeManager,
_params.receiver,
vars.change
);
interestBatchManagerOf[vars.troveId] = _params.interestBatchManager;
// Set the stored Trove properties and mint the NFT
vars.troveManager.onOpenTroveAndJoinBatch(
_params.owner,
vars.troveId,
vars.change,
_params.interestBatchManager,
vars.batch.entireCollWithoutRedistribution,
vars.batch.entireDebtWithoutRedistribution
);
sortedTroves.insertIntoBatch(
vars.troveId,
BatchId.wrap(_params.interestBatchManager),
vars.batch.annualInterestRate,
_params.upperHint,
_params.lowerHint
);
return vars.troveId;
}
function _requireMintingDebtAllowed() internal view virtual {
if (!branchManager.mintingDebtAllowed()) {
revert MintingDebtPaused();
}
}
function _openTrove(
address _owner,
uint256 _ownerIndex,
uint256 _collAmount,
uint256 _boldAmount,
uint256 _annualInterestRate,
address _interestBatchManager,
uint256 _batchEntireDebt,
uint256 _batchManagementAnnualFee,
uint256 _maxUpfrontFee,
address _addManager,
address _removeManager,
address _receiver,
TroveChange memory _change
) internal virtual returns (uint256) {
_requireIsNotShutDown();
// verify branch is not paused
_requireMintingDebtAllowed();
LocalVariables_openTrove memory vars;
// stack too deep not allowing to reuse troveManager from outer functions
vars.troveManager = troveManager;
vars.activePool = activePool;
vars.boldToken = boldToken;
vars.price = _requireOraclesLive();
// --- Checks ---
vars.troveId = uint256(keccak256(abi.encode(msg.sender, _owner, _ownerIndex)));
_requireTroveDoesNotExists(vars.troveManager, vars.troveId);
_change.collIncrease = _collAmount;
_change.debtIncrease = _boldAmount;
// For simplicity, we ignore the fee when calculating the approx. interest rate
_change.newWeightedRecordedDebt = (_batchEntireDebt + _change.debtIncrease) * _annualInterestRate;
vars.avgInterestRate = vars.activePool.getNewApproxAvgInterestRateFromTroveChange(_change);
_change.upfrontFee = _calcUpfrontFee(_change.debtIncrease, vars.avgInterestRate);
_requireUserAcceptsUpfrontFee(_change.upfrontFee, _maxUpfrontFee);
vars.entireDebt = _change.debtIncrease + _change.upfrontFee;
if (vars.entireDebt < minDebt) {
revert DebtBelowMin();
}
vars.ICR = LiquityMath._computeCR(_collAmount, vars.entireDebt, vars.price);
// Recalculate newWeightedRecordedDebt, now taking into account the upfront fee, and the batch fee if needed
if (_interestBatchManager == address(0)) {
_change.newWeightedRecordedDebt = vars.entireDebt * _annualInterestRate;
// ICR is based on the requested Bold amount + upfront fee.
_requireICRisAboveMCR(vars.ICR);
} else {
// old values have been set outside, before calling this function
_change.newWeightedRecordedDebt = (_batchEntireDebt + vars.entireDebt) * _annualInterestRate;
_change.newWeightedRecordedBatchManagementFee =
(_batchEntireDebt + vars.entireDebt) * _batchManagementAnnualFee;
// ICR is based on the requested Bold amount + upfront fee.
// Troves in a batch have a stronger requirement (MCR+BCR)
_requireICRisAboveMCRPlusBCR(vars.ICR);
}
vars.newTCR = _getNewTCRFromTroveChange(_change, vars.price);
_requireNewTCRisAboveCCR(vars.newTCR);
// --- Effects & interactions ---
// Set add/remove managers
_setAddManager(vars.troveId, _addManager);
_setRemoveManagerAndReceiver(vars.troveId, _removeManager, _receiver);
vars.activePool.mintAggInterestAndAccountForTroveChange(_change, _interestBatchManager);
// Pull coll tokens from sender and move them to the Active Pool
_pullCollAndSendToActivePool(vars.activePool, _collAmount);
// Mint the requested _boldAmount to the borrower and mint the gas comp to the GasPool
vars.boldToken.mint(msg.sender, _boldAmount);
WETH.transferFrom(msg.sender, gasPoolAddress, ETH_GAS_COMPENSATION);
_requireTotalDebtBelowCap();
return vars.troveId;
}
// Send collateral to a trove
function addColl(uint256 _troveId, uint256 _collAmount) external virtual override {
ITroveManager troveManagerCached = troveManager;
_requireTroveIsActive(troveManagerCached, _troveId);
TroveChange memory troveChange;
troveChange.collIncrease = _collAmount;
_adjustTrove(
troveManagerCached,
_troveId,
troveChange,
0 // _maxUpfrontFee
);
}
// Withdraw collateral from a trove
function withdrawColl(uint256 _troveId, uint256 _collWithdrawal) external virtual override {
ITroveManager troveManagerCached = troveManager;
_requireTroveIsActive(troveManagerCached, _troveId);
TroveChange memory troveChange;
troveChange.collDecrease = _collWithdrawal;
_adjustTrove(
troveManagerCached,
_troveId,
troveChange,
0 // _maxUpfrontFee
);
}
// Withdraw Bold tokens from a trove: mint new Bold tokens to the owner, and increase the trove's debt accordingly
function withdrawBold(uint256 _troveId, uint256 _boldAmount, uint256 _maxUpfrontFee) external virtual override {
ITroveManager troveManagerCached = troveManager;
_requireTroveIsActive(troveManagerCached, _troveId);
TroveChange memory troveChange;
troveChange.debtIncrease = _boldAmount;
_adjustTrove(troveManagerCached, _troveId, troveChange, _maxUpfrontFee);
}
// Repay Bold tokens to a Trove: Burn the repaid Bold tokens, and reduce the trove's debt accordingly
function repayBold(uint256 _troveId, uint256 _boldAmount) external virtual override {
ITroveManager troveManagerCached = troveManager;
_requireTroveIsActive(troveManagerCached, _troveId);
TroveChange memory troveChange;
troveChange.debtDecrease = _boldAmount;
_adjustTrove(
troveManagerCached,
_troveId,
troveChange,
0 // _maxUpfrontFee
);
}
function _initTroveChange(
TroveChange memory _troveChange,
uint256 _collChange,
bool _isCollIncrease,
uint256 _boldChange,
bool _isDebtIncrease
) internal pure virtual {
if (_isCollIncrease) {
_troveChange.collIncrease = _collChange;
} else {
_troveChange.collDecrease = _collChange;
}
if (_isDebtIncrease) {
_troveChange.debtIncrease = _boldChange;
} else {
_troveChange.debtDecrease = _boldChange;
}
}
function adjustTrove(
uint256 _troveId,
uint256 _collChange,
bool _isCollIncrease,
uint256 _boldChange,
bool _isDebtIncrease,
uint256 _maxUpfrontFee
) external virtual override {
ITroveManager troveManagerCached = troveManager;
_requireTroveIsActive(troveManagerCached, _troveId);
TroveChange memory troveChange;
_initTroveChange(troveChange, _collChange, _isCollIncrease, _boldChange, _isDebtIncrease);
_adjustTrove(troveManagerCached, _troveId, troveChange, _maxUpfrontFee);
}
function adjustZombieTrove(
uint256 _troveId,
uint256 _collChange,
bool _isCollIncrease,
uint256 _boldChange,
bool _isDebtIncrease,
uint256 _upperHint,
uint256 _lowerHint,
uint256 _maxUpfrontFee
) external virtual override {
ITroveManager troveManagerCached = troveManager;
_requireTroveIsZombie(troveManagerCached, _troveId);
TroveChange memory troveChange;
_initTroveChange(troveChange, _collChange, _isCollIncrease, _boldChange, _isDebtIncrease);
_adjustTrove(troveManagerCached, _troveId, troveChange, _maxUpfrontFee);
troveManagerCached.setTroveStatusToActive(_troveId);
address batchManager = interestBatchManagerOf[_troveId];
uint256 batchAnnualInterestRate;
if (batchManager != address(0)) {
LatestBatchData memory batch = troveManagerCached.getLatestBatchData(batchManager);
batchAnnualInterestRate = batch.annualInterestRate;
}
_reInsertIntoSortedTroves(
_troveId,
troveManagerCached.getTroveAnnualInterestRate(_troveId),
_upperHint,
_lowerHint,
batchManager,
batchAnnualInterestRate
);
}
function adjustTroveInterestRate(
uint256 _troveId,
uint256 _newAnnualInterestRate,
uint256 _upperHint,
uint256 _lowerHint,
uint256 _maxUpfrontFee
) external virtual {
_requireIsNotShutDown();
ITroveManager troveManagerCached = troveManager;
(TroveChange memory troveChange, LatestTroveData memory trove, uint256 newDebt) = boHelper
.initAdjustTroveInterestRate(
msg.sender, _troveId, _newAnnualInterestRate, _maxUpfrontFee, _upperHint, _lowerHint
);
troveManagerCached.onAdjustTroveInterestRate(
_troveId, trove.entireColl, newDebt, _newAnnualInterestRate, troveChange
);
}
/*
* _adjustTrove(): Alongside a debt change, this function can perform either a collateral top-up or a collateral withdrawal.
*/
function _adjustTrove(
ITroveManager _troveManager,
uint256 _troveId,
TroveChange memory _troveChange,
uint256 _maxUpfrontFee
) internal virtual {
_requireIsNotShutDown();
LocalVariables_adjustTrove memory vars;
vars.activePool = activePool;
vars.boldToken = boldToken;
vars.price = _requireOraclesLive();
vars.isBelowCriticalThreshold = _checkBelowCriticalThreshold(vars.price, branchManager.CCR());
// --- Checks ---
_requireTroveIsOpen(_troveManager, _troveId);
address owner = troveNFT.ownerOf(_troveId);
address receiver = owner; // If it’s a withdrawal, and remove manager privilege is set, a different receiver can be defined
if (_troveChange.collDecrease > 0 || _troveChange.debtIncrease > 0) {
receiver = _requireSenderIsOwnerOrRemoveManagerAndGetReceiver(_troveId, owner);
} else {
// RemoveManager assumes AddManager, so if the former is set, there's no need to check the latter
_requireSenderIsOwnerOrAddManager(_troveId, owner);
// No need to check the type of trove change for two reasons:
// - If the check above fails, it means sender is not owner, nor AddManager, nor RemoveManager.
// An independent 3rd party should not be allowed here.
// - If it's not collIncrease or debtDecrease, _requireNonZeroAdjustment would revert
}
(vars, _troveChange) = boHelper.initAdjustTrove(
_troveId, msg.sender, vars, _troveChange, _maxUpfrontFee, interestBatchManagerOf[_troveId]
);
// --- Effects and interactions ---
// vars.activePool.mintAggInterestAndAccountForTroveChange(_troveChange, batchManager);
_moveTokensFromAdjustment(receiver, _troveChange, vars.boldToken, vars.activePool);
}
function closeTrove(uint256 _troveId) external virtual override {
// --- Checks ---
address receiver = _requireSenderIsOwnerOrRemoveManagerAndGetReceiver(_troveId, troveNFT.ownerOf(_troveId));
address batchManager = interestBatchManagerOf[_troveId];
LatestTroveData memory trove = boHelper.initCloseTrove(msg.sender, _troveId, batchManager);
// Return ETH gas compensation
WETH.transferFrom(gasPoolAddress, receiver, ETH_GAS_COMPENSATION);
// Burn the remainder of the Trove's entire debt from the user
boldToken.burn(msg.sender, trove.entireDebt);
// Send the collateral back to the user
activePool.sendColl(receiver, trove.entireColl);
_wipeTroveMappings(_troveId);
}
function applyPendingDebt(uint256 _troveId, uint256 _lowerHint, uint256 _upperHint) public virtual {
_requireIsNotShutDown();
ITroveManager troveManagerCached = troveManager;
_requireTroveIsOpen(troveManagerCached, _troveId);
LatestTroveData memory trove = troveManagerCached.getLatestTroveData(_troveId);
_requireNonZeroDebt(trove.entireDebt);
TroveChange memory change;
change.appliedRedistBoldDebtGain = trove.redistBoldDebtGain;
change.appliedRedistCollGain = trove.redistCollGain;
address batchManager = interestBatchManagerOf[_troveId];
LatestBatchData memory batch;
if (batchManager == address(0)) {
change.oldWeightedRecordedDebt = trove.weightedRecordedDebt;
change.newWeightedRecordedDebt = trove.entireDebt * trove.annualInterestRate;
} else {
batch = troveManagerCached.getLatestBatchData(batchManager);
change.batchAccruedManagementFee = batch.accruedManagementFee;
change.oldWeightedRecordedDebt = batch.weightedRecordedDebt;
change.newWeightedRecordedDebt =
(batch.entireDebtWithoutRedistribution + trove.redistBoldDebtGain) * batch.annualInterestRate;
change.oldWeightedRecordedBatchManagementFee = batch.weightedRecordedBatchManagementFee;
change.newWeightedRecordedBatchManagementFee =
(batch.entireDebtWithoutRedistribution + trove.redistBoldDebtGain) * batch.annualManagementFee;
}
troveManagerCached.onApplyTroveInterest(
_troveId,
trove.entireColl,
trove.entireDebt,
batchManager,
batch.entireCollWithoutRedistribution,
batch.entireDebtWithoutRedistribution,
change
);
activePool.mintAggInterestAndAccountForTroveChange(change, batchManager);
// If the trove was zombie, and now it's not anymore, put it back in the list
if (_checkTroveIsZombie(troveManagerCached, _troveId) && trove.entireDebt >= minDebt) {
troveManagerCached.setTroveStatusToActive(_troveId);
_reInsertIntoSortedTroves(
_troveId, trove.annualInterestRate, _upperHint, _lowerHint, batchManager, batch.annualInterestRate
);
}
}
function setInterestIndividualDelegate(
uint256 _troveId,
address _delegate,
uint128 _minInterestRate,
uint128 _maxInterestRate,
// only needed if trove was previously in a batch:
uint256 _newAnnualInterestRate,
uint256 _upperHint,
uint256 _lowerHint,
uint256 _maxUpfrontFee,
uint256 _minInterestRateChangePeriod
) external virtual {
_requireIsNotShutDown();
boHelper.initSetInterestIndividualDelegate(
msg.sender, _troveId, _delegate, _minInterestRate, _maxInterestRate, _minInterestRateChangePeriod
);
// Can't have both individual delegation and batch manager
if (interestBatchManagerOf[_troveId] != address(0)) {
// Not needed, implicitly checked in removeFromBatch
//_requireValidAnnualInterestRate(_newAnnualInterestRate);
removeFromBatch(_troveId, _newAnnualInterestRate, _upperHint, _lowerHint, _maxUpfrontFee);
}
}
function lowerBatchManagementFee(uint256 _newAnnualManagementFee) external virtual {
boHelper.lowerBatchManagementFee(msg.sender, _newAnnualManagementFee);
}
function kickFromBatch(uint256 _troveId, uint256 _upperHint, uint256 _lowerHint) external virtual override {
_removeFromBatch({
_troveId: _troveId,
_newAnnualInterestRate: 0, // ignored when kicking
_upperHint: _upperHint,
_lowerHint: _lowerHint,
_maxUpfrontFee: 0, // will use the batch's existing interest rate, so no fee
_kick: true
});
}
function removeFromBatch(
uint256 _troveId,
uint256 _newAnnualInterestRate,
uint256 _upperHint,
uint256 _lowerHint,
uint256 _maxUpfrontFee
) public virtual override {
_removeFromBatch({
_troveId: _troveId,
_newAnnualInterestRate: _newAnnualInterestRate,
_upperHint: _upperHint,
_lowerHint: _lowerHint,
_maxUpfrontFee: _maxUpfrontFee,
_kick: false
});
}
function _removeFromBatch(
uint256 _troveId,
uint256 _newAnnualInterestRate,
uint256 _upperHint,
uint256 _lowerHint,
uint256 _maxUpfrontFee,
bool _kick
) internal virtual {
_requireIsNotShutDown();
LocalVariables_removeFromBatch memory vars;
vars.troveManager = troveManager;
vars.sortedTroves = sortedTroves;
if (_kick) {
_requireTroveIsOpen(vars.troveManager, _troveId);
} else {
_requireTroveIsActive(vars.troveManager, _troveId);
_requireCallerIsBorrower(_troveId);
branchManager.requireValidAnnualInterestRate(_newAnnualInterestRate);
}
vars.batchManager = _requireIsInBatch(_troveId);
vars.trove = vars.troveManager.getLatestTroveData(_troveId);
vars.batch = vars.troveManager.getLatestBatchData(vars.batchManager);
if (_kick) {
if (vars.batch.totalDebtShares * MAX_BATCH_SHARES_RATIO >= vars.batch.entireDebtWithoutRedistribution) {
revert BatchSharesRatioTooLow();
}
_newAnnualInterestRate = vars.batch.annualInterestRate;
}
delete interestBatchManagerOf[_troveId];
if (!_checkTroveIsZombie(vars.troveManager, _troveId)) {
// Remove trove from Batch in SortedTroves
vars.sortedTroves.removeFromBatch(_troveId);
// Reinsert as single trove
vars.sortedTroves.insert(_troveId, _newAnnualInterestRate, _upperHint, _lowerHint);
}
vars.batchFutureDebt =
vars.batch.entireDebtWithoutRedistribution - (vars.trove.entireDebt - vars.trove.redistBoldDebtGain);
vars.batchChange.appliedRedistBoldDebtGain = vars.trove.redistBoldDebtGain;
vars.batchChange.appliedRedistCollGain = vars.trove.redistCollGain;
vars.batchChange.batchAccruedManagementFee = vars.batch.accruedManagementFee;
vars.batchChange.oldWeightedRecordedDebt = vars.batch.weightedRecordedDebt;
vars.batchChange.newWeightedRecordedDebt =
vars.batchFutureDebt * vars.batch.annualInterestRate + vars.trove.entireDebt * _newAnnualInterestRate;
// Apply upfront fee on premature adjustments. It checks the resulting ICR
if (
vars.batch.annualInterestRate != _newAnnualInterestRate
&& block.timestamp < vars.trove.lastInterestRateAdjTime + INTEREST_RATE_ADJ_COOLDOWN
) {
(vars.trove.entireDebt, vars.batchChange) = boHelper.applyUpfrontFee(
vars.trove.entireColl, vars.trove.entireDebt, vars.batchChange, _maxUpfrontFee, false
);
}
// Recalculate newWeightedRecordedDebt, now taking into account the upfront fee
vars.batchChange.newWeightedRecordedDebt =
vars.batchFutureDebt * vars.batch.annualInterestRate + vars.trove.entireDebt * _newAnnualInterestRate;
// Add batch fees
vars.batchChange.oldWeightedRecordedBatchManagementFee = vars.batch.weightedRecordedBatchManagementFee;
vars.batchChange.newWeightedRecordedBatchManagementFee = vars.batchFutureDebt * vars.batch.annualManagementFee;
activePool.mintAggInterestAndAccountForTroveChange(vars.batchChange, vars.batchManager);
vars.troveManager.onRemoveFromBatch(
_troveId,
vars.trove.entireColl,
vars.trove.entireDebt,
vars.batchChange,
vars.batchManager,
vars.batch.entireCollWithoutRedistribution,
vars.batch.entireDebtWithoutRedistribution,
_newAnnualInterestRate
);
}
function switchBatchManager(
uint256 _troveId,
uint256 _removeUpperHint,
uint256 _removeLowerHint,
address _newBatchManager,
uint256 _addUpperHint,
uint256 _addLowerHint,
uint256 _maxUpfrontFee
) external virtual override {
LatestBatchData memory oldBatch = boHelper.initSwitchBatchManager(_troveId, _newBatchManager);
removeFromBatch(_troveId, oldBatch.annualInterestRate, _removeUpperHint, _removeLowerHint, 0);
boHelper.setInterestBatchManager(
msg.sender, _troveId, _newBatchManager, _addUpperHint, _addLowerHint, _maxUpfrontFee
);
}
function _calcUpfrontFee(uint256 _debt, uint256 _avgInterestRate) internal pure virtual returns (uint256) {
return _calcInterest(_debt * _avgInterestRate, UPFRONT_INTEREST_PERIOD);
}
// Call from TM to clean state here
function onLiquidateTrove(uint256 _troveId) external virtual {
_requireCallerIsTroveManager();
_wipeTroveMappings(_troveId);
}
function _requireCallerIsTroveManager() internal view virtual {
if (msg.sender != address(troveManager)) {
revert CallerNotTroveManager();
}
}
function _wipeTroveMappings(uint256 _troveId) internal virtual {
boHelper.removeInterestIndividualDelegate(_troveId);
delete interestBatchManagerOf[_troveId];
_wipeAddRemoveManagers(_troveId);
}
/**
* Claim remaining collateral from a liquidation with ICR exceeding the liquidation penalty
*/
function claimCollateral() external virtual override {
// send coll from CollSurplus Pool to owner
collSurplusPool.claimColl(msg.sender);
}
function shutdown() external virtual {
if (hasBeenShutDown) revert IsShutDown();
uint256 totalColl = getEntireBranchColl();
uint256 totalDebt = getEntireBranchDebt();
(uint256 price, bool newOracleFailureDetected) = priceFeed.fetchPrice();
// If the oracle failed, the above call to PriceFeed will have shut this branch down
if (newOracleFailureDetected) return;
// Otherwise, proceed with the TCR check:
uint256 TCR = LiquityMath._computeCR(totalColl, totalDebt, price);
if (TCR >= branchManager.SCR()) revert TCRNotBelowSCR();
_applyShutdown();
emit ShutDown(TCR);
}
// Not technically a "Borrower op", but seems best placed here given current shutdown logic.
function shutdownFromOracleFailure() external virtual {
_requireCallerIsPriceFeed();
// No-op rather than revert here, so that the outer function call which fetches the price does not revert
// if the system is already shut down.
if (hasBeenShutDown) return;
_applyShutdown();
}
function _requireCallerIsPriceFeed() internal view virtual {
if (msg.sender != address(priceFeed)) {
revert CallerNotPriceFeed();
}
}
function _applyShutdown() internal virtual {
activePool.mintAggInterest();
hasBeenShutDown = true;
branchManager.shutdown();
}
// --- Helper functions ---
function _reInsertIntoSortedTroves(
uint256 _troveId,
uint256 _troveAnnualInterestRate,
uint256 _upperHint,
uint256 _lowerHint,
address _batchManager,
uint256 _batchAnnualInterestRate
) internal virtual {
// If it was in a batch, we need to put it back, otherwise we insert it normally
if (_batchManager == address(0)) {
sortedTroves.insert(_troveId, _troveAnnualInterestRate, _upperHint, _lowerHint);
} else {
sortedTroves.insertIntoBatch(
_troveId, BatchId.wrap(_batchManager), _batchAnnualInterestRate, _upperHint, _lowerHint
);
}
}
// This function mints the BOLD corresponding to the borrower's chosen debt increase
// (it does not mint the accrued interest).
function _moveTokensFromAdjustment(
address withdrawalReceiver,
TroveChange memory _troveChange,
IBoldToken _boldToken,
IActivePool _activePool
) internal virtual {
if (_troveChange.debtIncrease > 0) {
_requireMintingDebtAllowed();
_boldToken.mint(withdrawalReceiver, _troveChange.debtIncrease);
_requireTotalDebtBelowCap();
} else if (_troveChange.debtDecrease > 0) {
_boldToken.burn(msg.sender, _troveChange.debtDecrease);
}
if (_troveChange.collIncrease > 0) {
// Pull coll tokens from sender and move them to the Active Pool
_pullCollAndSendToActivePool(_activePool, _troveChange.collIncrease);
} else if (_troveChange.collDecrease > 0) {
// Pull Coll from Active Pool and decrease its recorded Coll balance
_activePool.sendColl(withdrawalReceiver, _troveChange.collDecrease);
}
}
function _pullCollAndSendToActivePool(IActivePool _activePool, uint256 _amount) internal virtual {
// Send Coll tokens from sender to active pool
collToken.safeTransferFrom(msg.sender, address(_activePool), _amount);
// Make sure Active Pool accountancy is right
_activePool.accountForReceivedColl(_amount);
}
// --- 'Require' wrapper functions ---
function _requireIsNotShutDown() internal view virtual {
if (hasBeenShutDown) {
revert IsShutDown();
}
}
function _requireTroveDoesNotExists(ITroveManager _troveManager, uint256 _troveId) internal view virtual {
ITroveManager.Status status = _troveManager.getTroveStatus(_troveId);
if (status != ITroveManager.Status.nonExistent) {
revert TroveExists();
}
}
function _requireIsInBatch(uint256 _troveId) internal view virtual returns (address) {
address batchManager = interestBatchManagerOf[_troveId];
if (batchManager == address(0)) {
revert TroveNotInBatch();
}
return batchManager;
}
function _requireTroveIsOpen(ITroveManager _troveManager, uint256 _troveId) internal view virtual {
ITroveManager.Status status = _troveManager.getTroveStatus(_troveId);
if (status != ITroveManager.Status.active && status != ITroveManager.Status.zombie) {
revert TroveNotOpen();
}
}
function _requireTroveIsActive(ITroveManager _troveManager, uint256 _troveId) internal view virtual {
ITroveManager.Status status = _troveManager.getTroveStatus(_troveId);
if (status != ITroveManager.Status.active) {
revert TroveNotActive();
}
}
function _requireTotalDebtBelowCap() internal view virtual {
if (activePool.totalDebtTaken() > branchManager.debtCap()) {
if (msg.sender != _ebisuAdminRegistry.ebisuAdmin()) {
revert DebtCapReached();
}
}
}
function _requireTroveIsZombie(ITroveManager _troveManager, uint256 _troveId) internal view virtual {
if (!_checkTroveIsZombie(_troveManager, _troveId)) {
revert TroveNotZombie();
}
}
function _checkTroveIsZombie(ITroveManager _troveManager, uint256 _troveId) internal view virtual returns (bool) {
ITroveManager.Status status = _troveManager.getTroveStatus(_troveId);
return status == ITroveManager.Status.zombie;
}
function _requireNonZeroDebt(uint256 _troveDebt) internal pure virtual {
if (_troveDebt == 0) {
revert TroveWithZeroDebt();
}
}
function _requireUserAcceptsUpfrontFee(uint256 _fee, uint256 _maxFee) internal pure virtual {
if (_fee > _maxFee) {
revert UpfrontFeeTooHigh();
}
}
function _requireICRisAboveMCR(uint256 _newICR) internal view virtual {
if (_newICR < branchManager.MCR()) {
revert ICRBelowMCR();
}
}
function _requireNewTCRisAboveCCR(uint256 _newTCR) internal view virtual {
if (_newTCR < branchManager.CCR()) {
revert TCRBelowCCR();
}
}
function _requireOraclesLive() internal virtual returns (uint256) {
(uint256 price, bool newOracleFailureDetected) = priceFeed.fetchPrice();
if (newOracleFailureDetected) {
revert NewOracleFailureDetected();
}
return price;
}
// --- ICR and TCR getters ---
function _getNewTCRFromTroveChange(TroveChange memory _troveChange, uint256 _price)
internal
view
virtual
returns (uint256 newTCR)
{
uint256 totalColl = getEntireBranchColl();
totalColl += _troveChange.collIncrease;
totalColl -= _troveChange.collDecrease;
uint256 totalDebt = getEntireBranchDebt();
totalDebt += _troveChange.debtIncrease;
totalDebt += _troveChange.upfrontFee;
totalDebt -= _troveChange.debtDecrease;
newTCR = LiquityMath._computeCR(totalColl, totalDebt, _price);
}
function setInterestBatchManagerOf(uint256 _troveId, address _batchManager) external virtual {
if (msg.sender != address(boHelper)) {
revert CallerNotBorrowerOperationsHelper();
}
interestBatchManagerOf[_troveId] = _batchManager;
}
function _requireICRisAboveMCRPlusBCR(uint256 _newICR) internal view virtual {
if (_newICR < branchManager.MCR() + BCR) {
revert ICRBelowMCRPlusBCR();
}
}
function setMinDebt(uint256 _minDebt) external virtual {
if (msg.sender != address(branchManager)) {
revert CallerNotBranchManager();
}
minDebt = _minDebt;
}
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
"
},
"lib/openzeppelin-con
Submitted on: 2025-10-28 17:30:57
Comments
Log in to comment.
No comments yet.