Description:
Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/Zappers/LeverageLSTZapperV2.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import "./GasCompZapper.sol";
import "../Dependencies/Constants.sol";
contract LeverageLSTZapperV2 is GasCompZapper, ILeverageZapper {
using SafeERC20 for IERC20;
uint256 private constant BPS_DENOMINATOR = 10000;
constructor(
IAddressesRegistry _addressesRegistry,
IFlashLoanProvider _flashLoanProvider,
IExchange _exchange
)
GasCompZapper(_addressesRegistry, _flashLoanProvider, _exchange)
{
// Approval of WETH and Coll to BorrowerOperations is done in parent GasCompZapper
// Approve Bold to exchange module (Coll is approved in parent GasCompZapper)
boldToken.approve(address(_exchange), type(uint256).max);
}
function openLeveragedTroveWithRawETH(OpenLeveragedTroveParams memory _params) external payable {
require(msg.value == ETH_GAS_COMPENSATION, "LZ: Wrong ETH");
require(
_params.batchManager == address(0) || _params.annualInterestRate == 0,
"LZ: Cannot choose interest if joining a batch"
);
// Include the original sender in the index, so it is included in the final troveId
_params.ownerIndex = _getTroveIndex(msg.sender, _params.ownerIndex);
// Set initial balances to make sure there are not lefovers
InitialBalances memory initialBalances;
_setInitialTokensAndBalances(collToken, boldToken, initialBalances);
// Convert ETH to WETH
WETH.deposit{value: msg.value}();
// Pull own coll
collToken.safeTransferFrom(msg.sender, address(this), _params.collAmount);
// Flash loan coll
flashLoanProvider.makeFlashLoan(
collToken, _params.flashLoanAmount, IFlashLoanProvider.Operation.OpenTrove, abi.encode(_params)
);
// return leftovers to user
_returnLeftovers(initialBalances);
}
// Callback from the flash loan provider
function receiveFlashLoanOnOpenLeveragedTrove(
OpenLeveragedTroveParams calldata _params,
uint256 _effectiveFlashLoanAmount
) external override {
require(msg.sender == address(flashLoanProvider), "LZ: Caller not FlashLoan provider");
uint256 totalCollAmount = _params.collAmount + _effectiveFlashLoanAmount;
// We compute boldAmount off-chain for efficiency
// Open trove
uint256 troveId;
if (_params.batchManager == address(0)) {
troveId = borrowerOperations.openTrove(
_params.owner,
_params.ownerIndex,
totalCollAmount,
_params.boldAmount,
_params.upperHint,
_params.lowerHint,
_params.annualInterestRate,
_params.maxUpfrontFee,
// Add this contract as add/receive manager to be able to fully adjust trove,
// while keeping the same management functionality
address(this), // add manager
address(this), // remove manager
address(this) // receiver for remove manager
);
} else {
IBorrowerOperations.OpenTroveAndJoinInterestBatchManagerParams memory
openTroveAndJoinInterestBatchManagerParams = IBorrowerOperations
.OpenTroveAndJoinInterestBatchManagerParams({
owner: _params.owner,
ownerIndex: _params.ownerIndex,
collAmount: totalCollAmount,
boldAmount: _params.boldAmount,
upperHint: _params.upperHint,
lowerHint: _params.lowerHint,
interestBatchManager: _params.batchManager,
maxUpfrontFee: _params.maxUpfrontFee,
// Add this contract as add/receive manager to be able to fully adjust trove,
// while keeping the same management functionality
addManager: address(this), // add manager
removeManager: address(this), // remove manager
receiver: address(this) // receiver for remove manager
});
troveId =
borrowerOperations.openTroveAndJoinInterestBatchManager(openTroveAndJoinInterestBatchManagerParams);
}
// Set add/remove managers
_setAddManager(troveId, _params.addManager);
_setRemoveManagerAndReceiver(troveId, _params.removeManager, _params.receiver);
// Calculate flash loan fee and total repayment needed
uint256 flashLoanFeeBps = flashLoanProvider.getFlashLoanFeeBps();
uint256 flashLoanFee = (_params.flashLoanAmount * flashLoanFeeBps) / BPS_DENOMINATOR;
uint256 totalRepayment = _params.flashLoanAmount + flashLoanFee;
// Swap Bold to Coll (need enough to cover flashLoan + fee)
exchange.swapFromBold(_params.boldAmount, totalRepayment);
// Send coll back to return flash loan including fee
collToken.safeTransfer(address(flashLoanProvider), totalRepayment);
}
function leverUpTrove(LeverUpTroveParams calldata _params) external {
address owner = troveNFT.ownerOf(_params.troveId);
address receiver = _requireSenderIsOwnerOrRemoveManagerAndGetReceiver(_params.troveId, owner);
_requireZapperIsReceiver(_params.troveId);
// Set initial balances to make sure there are not lefovers
InitialBalances memory initialBalances;
_setInitialTokensBalancesAndReceiver(collToken, boldToken, initialBalances, receiver);
// Flash loan coll
flashLoanProvider.makeFlashLoan(
collToken, _params.flashLoanAmount, IFlashLoanProvider.Operation.LeverUpTrove, abi.encode(_params)
);
// return leftovers to user
_returnLeftovers(initialBalances);
}
// Callback from the flash loan provider
function receiveFlashLoanOnLeverUpTrove(LeverUpTroveParams calldata _params, uint256 _effectiveFlashLoanAmount)
external
override
{
require(msg.sender == address(flashLoanProvider), "LZ: Caller not FlashLoan provider");
// Adjust trove
// With the received coll from flash loan, we increase both the trove coll and debt
borrowerOperations.adjustTrove(
_params.troveId,
_effectiveFlashLoanAmount, // flash loan amount minus fee
true, // _isCollIncrease
_params.boldAmount,
true, // _isDebtIncrease
_params.maxUpfrontFee
);
// Swap Bold to Coll
// No need to use a min: if the obtained amount is not enough, the flash loan return below won’t be enough
// And the flash loan provider will revert after this function exits
// The frontend should calculate in advance the `_params.boldAmount` needed for this to work
// Calculate flash loan fee and total repayment needed
uint256 flashLoanFeeBps = flashLoanProvider.getFlashLoanFeeBps();
uint256 flashLoanFee = (_params.flashLoanAmount * flashLoanFeeBps) / BPS_DENOMINATOR;
uint256 totalRepayment = _params.flashLoanAmount + flashLoanFee;
exchange.swapFromBold(_params.boldAmount, totalRepayment);
// Send coll back to return flash loan including fee
collToken.safeTransfer(address(flashLoanProvider), totalRepayment);
}
function leverDownTrove(LeverDownTroveParams calldata _params) external {
address owner = troveNFT.ownerOf(_params.troveId);
address receiver = _requireSenderIsOwnerOrRemoveManagerAndGetReceiver(_params.troveId, owner);
_requireZapperIsReceiver(_params.troveId);
// Set initial balances to make sure there are not lefovers
InitialBalances memory initialBalances;
_setInitialTokensBalancesAndReceiver(collToken, boldToken, initialBalances, receiver);
// Flash loan coll
flashLoanProvider.makeFlashLoan(
collToken, _params.flashLoanAmount, IFlashLoanProvider.Operation.LeverDownTrove, abi.encode(_params)
);
// return leftovers to user
_returnLeftovers(initialBalances);
}
// Callback from the flash loan provider
function receiveFlashLoanOnLeverDownTrove(LeverDownTroveParams calldata _params, uint256 _effectiveFlashLoanAmount)
external
override
{
require(msg.sender == address(flashLoanProvider), "LZ: Caller not FlashLoan provider");
// Swap Coll from flash loan to Bold, so we can repay and downsize trove
// We swap the flash loan minus the flash loan fee
// The frontend should calculate in advance the `_params.minBoldAmount` to achieve the desired leverage ratio
// (with some slippage tolerance)
uint256 receivedBoldAmount = exchange.swapToBold(_effectiveFlashLoanAmount, _params.minBoldAmount);
// Calculate flash loan fee and total repayment needed
uint256 flashLoanFeeBps = flashLoanProvider.getFlashLoanFeeBps();
uint256 flashLoanFee = (_params.flashLoanAmount * flashLoanFeeBps) / BPS_DENOMINATOR;
uint256 totalRepayment = _params.flashLoanAmount + flashLoanFee;
// Adjust trove - withdraw enough collateral to cover flash loan + fee
borrowerOperations.adjustTrove(
_params.troveId,
totalRepayment,
false, // _isCollIncrease
receivedBoldAmount,
false, // _isDebtIncrease
0
);
// Send coll back to return flash loan including fee
collToken.safeTransfer(address(flashLoanProvider), totalRepayment);
}
// As formulas are symmetrical, it can be used in both ways
function leverageRatioToCollateralRatio(uint256 _inputRatio) external pure returns (uint256) {
return _inputRatio * DECIMAL_PRECISION / (_inputRatio - DECIMAL_PRECISION);
}
/**
* @notice Calculate the flash loan fee for a given amount
* @param _amount The flash loan amount
* @return The fee amount based on the configured fee rate
*/
function calculateFlashLoanFee(uint256 _amount) external view returns (uint256) {
uint256 flashLoanFeeBps = flashLoanProvider.getFlashLoanFeeBps();
return (_amount * flashLoanFeeBps) / BPS_DENOMINATOR;
}
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
* Revert on invalid signature.
*/
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return
success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
}
}
"
},
"src/Zappers/GasCompZapper.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import "./BaseZapper.sol";
import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import "src/Dependencies/Constants.sol";
contract GasCompZapper is BaseZapper {
using SafeERC20 for IERC20;
IERC20 public immutable collToken;
constructor(IAddressesRegistry _addressesRegistry, IFlashLoanProvider _flashLoanProvider, IExchange _exchange)
BaseZapper(_addressesRegistry, _flashLoanProvider, _exchange)
{
collToken = _addressesRegistry.collToken();
require(address(WETH) != address(collToken), "GCZ: Wrong coll branch");
// Approve WETH to BorrowerOperations
WETH.approve(address(borrowerOperations), type(uint256).max);
// Approve coll to BorrowerOperations
collToken.approve(address(borrowerOperations), type(uint256).max);
// Approve Coll to exchange module (for closeTroveFromCollateral)
collToken.approve(address(_exchange), type(uint256).max);
}
function openTroveWithRawETH(OpenTroveParams calldata _params) external payable returns (uint256) {
require(msg.value == ETH_GAS_COMPENSATION, "GCZ: Wrong ETH");
require(
_params.batchManager == address(0) || _params.annualInterestRate == 0,
"GCZ: Cannot choose interest if joining a batch"
);
// Convert ETH to WETH
WETH.deposit{value: msg.value}();
// Pull coll
collToken.safeTransferFrom(msg.sender, address(this), _params.collAmount);
uint256 troveId;
// Include sender in index
uint256 index = _getTroveIndex(_params.ownerIndex);
if (_params.batchManager == address(0)) {
troveId = borrowerOperations.openTrove(
_params.owner,
index,
_params.collAmount,
_params.boldAmount,
_params.upperHint,
_params.lowerHint,
_params.annualInterestRate,
_params.maxUpfrontFee,
// Add this contract as add/receive manager to be able to fully adjust trove,
// while keeping the same management functionality
address(this), // add manager
address(this), // remove manager
address(this) // receiver for remove manager
);
} else {
IBorrowerOperations.OpenTroveAndJoinInterestBatchManagerParams memory
openTroveAndJoinInterestBatchManagerParams = IBorrowerOperations
.OpenTroveAndJoinInterestBatchManagerParams({
owner: _params.owner,
ownerIndex: index,
collAmount: _params.collAmount,
boldAmount: _params.boldAmount,
upperHint: _params.upperHint,
lowerHint: _params.lowerHint,
interestBatchManager: _params.batchManager,
maxUpfrontFee: _params.maxUpfrontFee,
// Add this contract as add/receive manager to be able to fully adjust trove,
// while keeping the same management functionality
addManager: address(this), // add manager
removeManager: address(this), // remove manager
receiver: address(this) // receiver for remove manager
});
troveId =
borrowerOperations.openTroveAndJoinInterestBatchManager(openTroveAndJoinInterestBatchManagerParams);
}
boldToken.transfer(msg.sender, _params.boldAmount);
// Set add/remove managers
_setAddManager(troveId, _params.addManager);
_setRemoveManagerAndReceiver(troveId, _params.removeManager, _params.receiver);
return troveId;
}
function addColl(uint256 _troveId, uint256 _amount) external {
address owner = troveNFT.ownerOf(_troveId);
_requireSenderIsOwnerOrAddManager(_troveId, owner);
IBorrowerOperations borrowerOperationsCached = borrowerOperations;
// Pull coll
collToken.safeTransferFrom(msg.sender, address(this), _amount);
borrowerOperationsCached.addColl(_troveId, _amount);
}
function withdrawColl(uint256 _troveId, uint256 _amount) external {
address owner = troveNFT.ownerOf(_troveId);
address receiver = _requireSenderIsOwnerOrRemoveManagerAndGetReceiver(_troveId, owner);
_requireZapperIsReceiver(_troveId);
borrowerOperations.withdrawColl(_troveId, _amount);
// Send coll left
collToken.safeTransfer(receiver, _amount);
}
function withdrawBold(uint256 _troveId, uint256 _boldAmount, uint256 _maxUpfrontFee) external {
address owner = troveNFT.ownerOf(_troveId);
address receiver = _requireSenderIsOwnerOrRemoveManagerAndGetReceiver(_troveId, owner);
_requireZapperIsReceiver(_troveId);
borrowerOperations.withdrawBold(_troveId, _boldAmount, _maxUpfrontFee);
// Send Bold
boldToken.transfer(receiver, _boldAmount);
}
function repayBold(uint256 _troveId, uint256 _boldAmount) external {
address owner = troveNFT.ownerOf(_troveId);
_requireSenderIsOwnerOrAddManager(_troveId, owner);
// Set initial balances to make sure there are not lefovers
InitialBalances memory initialBalances;
_setInitialTokensAndBalances(collToken, boldToken, initialBalances);
// Pull Bold
boldToken.transferFrom(msg.sender, address(this), _boldAmount);
borrowerOperations.repayBold(_troveId, _boldAmount);
// return leftovers to user
_returnLeftovers(initialBalances);
}
function adjustTrove(
uint256 _troveId,
uint256 _collChange,
bool _isCollIncrease,
uint256 _boldChange,
bool _isDebtIncrease,
uint256 _maxUpfrontFee
) external {
InitialBalances memory initialBalances;
address receiver =
_adjustTrovePre(_troveId, _collChange, _isCollIncrease, _boldChange, _isDebtIncrease, initialBalances);
borrowerOperations.adjustTrove(
_troveId, _collChange, _isCollIncrease, _boldChange, _isDebtIncrease, _maxUpfrontFee
);
_adjustTrovePost(_collChange, _isCollIncrease, _boldChange, _isDebtIncrease, receiver, initialBalances);
}
function adjustZombieTrove(
uint256 _troveId,
uint256 _collChange,
bool _isCollIncrease,
uint256 _boldChange,
bool _isDebtIncrease,
uint256 _upperHint,
uint256 _lowerHint,
uint256 _maxUpfrontFee
) external {
InitialBalances memory initialBalances;
address receiver =
_adjustTrovePre(_troveId, _collChange, _isCollIncrease, _boldChange, _isDebtIncrease, initialBalances);
borrowerOperations.adjustZombieTrove(
_troveId, _collChange, _isCollIncrease, _boldChange, _isDebtIncrease, _upperHint, _lowerHint, _maxUpfrontFee
);
_adjustTrovePost(_collChange, _isCollIncrease, _boldChange, _isDebtIncrease, receiver, initialBalances);
}
function _adjustTrovePre(
uint256 _troveId,
uint256 _collChange,
bool _isCollIncrease,
uint256 _boldChange,
bool _isDebtIncrease,
InitialBalances memory _initialBalances
) internal returns (address) {
address receiver = _checkAdjustTroveManagers(_troveId, _collChange, _isCollIncrease, _isDebtIncrease);
// Set initial balances to make sure there are not lefovers
_setInitialTokensAndBalances(collToken, boldToken, _initialBalances);
// Pull coll
if (_isCollIncrease) {
collToken.safeTransferFrom(msg.sender, address(this), _collChange);
}
// Pull Bold
if (!_isDebtIncrease) {
boldToken.transferFrom(msg.sender, address(this), _boldChange);
}
return receiver;
}
function _adjustTrovePost(
uint256 _collChange,
bool _isCollIncrease,
uint256 _boldChange,
bool _isDebtIncrease,
address _receiver,
InitialBalances memory _initialBalances
) internal {
// Send coll left
if (!_isCollIncrease) {
collToken.safeTransfer(_receiver, _collChange);
}
// Send Bold
if (_isDebtIncrease) {
boldToken.transfer(_receiver, _boldChange);
}
// return leftovers to user
_returnLeftovers(_initialBalances);
}
function closeTroveToRawETH(uint256 _troveId) external {
address owner = troveNFT.ownerOf(_troveId);
address payable receiver = payable(_requireSenderIsOwnerOrRemoveManagerAndGetReceiver(_troveId, owner));
_requireZapperIsReceiver(_troveId);
// pull Bold for repayment
LatestTroveData memory trove = troveManager.getLatestTroveData(_troveId);
boldToken.transferFrom(msg.sender, address(this), trove.entireDebt);
borrowerOperations.closeTrove(_troveId);
// Send coll left
collToken.safeTransfer(receiver, trove.entireColl);
// Send gas compensation
WETH.withdraw(ETH_GAS_COMPENSATION);
(bool success,) = receiver.call{value: ETH_GAS_COMPENSATION}("");
require(success, "GCZ: Sending ETH failed");
}
function closeTroveFromCollateral(uint256 _troveId, uint256 _flashLoanAmount, uint256 _minExpectedCollateral)
external
override
{
address owner = troveNFT.ownerOf(_troveId);
address payable receiver = payable(_requireSenderIsOwnerOrRemoveManagerAndGetReceiver(_troveId, owner));
_requireZapperIsReceiver(_troveId);
CloseTroveParams memory params = CloseTroveParams({
troveId: _troveId,
flashLoanAmount: _flashLoanAmount,
minExpectedCollateral: _minExpectedCollateral,
receiver: receiver
});
// Set initial balances to make sure there are not lefovers
InitialBalances memory initialBalances;
initialBalances.tokens[0] = collToken;
initialBalances.tokens[1] = IERC20(address(boldToken));
_setInitialBalancesAndReceiver(initialBalances, receiver);
// Flash loan coll
flashLoanProvider.makeFlashLoan(
collToken, _flashLoanAmount, IFlashLoanProvider.Operation.CloseTrove, abi.encode(params)
);
// return leftovers to user
_returnLeftovers(initialBalances);
}
function receiveFlashLoanOnCloseTroveFromCollateral(
CloseTroveParams calldata _params,
uint256 _effectiveFlashLoanAmount
) external {
require(msg.sender == address(flashLoanProvider), "GCZ: Caller not FlashLoan provider");
LatestTroveData memory trove = troveManager.getLatestTroveData(_params.troveId);
uint256 collLeft = trove.entireColl - _params.flashLoanAmount;
require(collLeft >= _params.minExpectedCollateral, "GCZ: Not enough collateral received");
// Swap Coll from flash loan to Bold, so we can repay and close trove
// We swap the flash loan minus the flash loan fee
exchange.swapToBold(_effectiveFlashLoanAmount, trove.entireDebt);
// We asked for a min of entireDebt in swapToBold call above, so we don’t check again here:
//uint256 receivedBoldAmount = exchange.swapToBold(_effectiveFlashLoanAmount, trove.entireDebt);
//require(receivedBoldAmount >= trove.entireDebt, "GCZ: Not enough BOLD obtained to repay");
borrowerOperations.closeTrove(_params.troveId);
// Send coll back to return flash loan
collToken.safeTransfer(address(flashLoanProvider), _params.flashLoanAmount);
// Send coll left
collToken.safeTransfer(_params.receiver, collLeft);
// Send gas compensation
WETH.withdraw(ETH_GAS_COMPENSATION);
(bool success,) = _params.receiver.call{value: ETH_GAS_COMPENSATION}("");
require(success, "GCZ: Sending ETH failed");
}
receive() external payable {}
// Unimplemented flash loan receive functions for leverage
function receiveFlashLoanOnOpenLeveragedTrove(
ILeverageZapper.OpenLeveragedTroveParams calldata _params,
uint256 _effectiveFlashLoanAmount
) external virtual override {}
function receiveFlashLoanOnLeverUpTrove(
ILeverageZapper.LeverUpTroveParams calldata _params,
uint256 _effectiveFlashLoanAmount
) external virtual override {}
function receiveFlashLoanOnLeverDownTrove(
ILeverageZapper.LeverDownTroveParams calldata _params,
uint256 _effectiveFlashLoanAmount
) external virtual override {}
}
"
},
"src/Dependencies/Constants.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.24;
address constant ZERO_ADDRESS = address(0);
uint256 constant MAX_UINT256 = type(uint256).max;
uint256 constant DECIMAL_PRECISION = 1e18;
uint256 constant _100pct = DECIMAL_PRECISION;
uint256 constant _1pct = DECIMAL_PRECISION / 100;
// Amount of ETH to be locked in gas pool on opening troves
uint256 constant ETH_GAS_COMPENSATION = 0.0375 ether;
// Liquidation
uint256 constant MIN_LIQUIDATION_PENALTY_SP = 5e16; // 5%
uint256 constant MAX_LIQUIDATION_PENALTY_REDISTRIBUTION = 20e16; // 20%
// Collateral branch parameters (SETH = staked ETH, i.e. wstETH / rETH)
uint256 constant CCR_WETH = 150 * _1pct;
uint256 constant CCR_SETH = 160 * _1pct;
uint256 constant MCR_WETH = 110 * _1pct;
uint256 constant MCR_SETH = 120 * _1pct;
uint256 constant SCR_WETH = 110 * _1pct;
uint256 constant SCR_SETH = 120 * _1pct;
// Batch CR buffer (same for all branches for now)
// On top of MCR to join a batch, or adjust inside a batch
uint256 constant BCR_ALL = 5 * _1pct;
uint256 constant LIQUIDATION_PENALTY_SP_WETH = 5 * _1pct;
uint256 constant LIQUIDATION_PENALTY_SP_SETH = 5 * _1pct;
uint256 constant LIQUIDATION_PENALTY_REDISTRIBUTION_WETH = 10 * _1pct;
uint256 constant LIQUIDATION_PENALTY_REDISTRIBUTION_SETH = 20 * _1pct;
// Fraction of collateral awarded to liquidator
uint256 constant COLL_GAS_COMPENSATION_DIVISOR = 200; // dividing by 200 yields 0.5%
uint256 constant COLL_GAS_COMPENSATION_CAP = 2 ether; // Max coll gas compensation capped at 2 ETH
// Minimum amount of net Bold debt a trove must have
uint256 constant MIN_DEBT = 2000e18;
uint256 constant MIN_ANNUAL_INTEREST_RATE = _1pct / 2; // 0.5%
uint256 constant MAX_ANNUAL_INTEREST_RATE = 250 * _1pct;
// Batch management params
uint128 constant MAX_ANNUAL_BATCH_MANAGEMENT_FEE = uint128(_100pct / 10); // 10%
uint128 constant MIN_INTEREST_RATE_CHANGE_PERIOD = 1 hours; // only applies to batch managers / batched Troves
uint256 constant REDEMPTION_FEE_FLOOR = _1pct / 2; // 0.5%
// For the debt / shares ratio to increase by a factor 1e9
// at a average annual debt increase (compounded interest + fees) of 10%, it would take more than 217 years (log(1e9)/log(1.1))
// at a average annual debt increase (compounded interest + fees) of 50%, it would take more than 51 years (log(1e9)/log(1.5))
// The increase pace could be forced to be higher through an inflation attack,
// but precisely the fact that we have this max value now prevents the attack
uint256 constant MAX_BATCH_SHARES_RATIO = 1e9;
// Half-life of 6h. 6h = 360 min
// (1/2) = d^360 => d = (1/2)^(1/360)
uint256 constant REDEMPTION_MINUTE_DECAY_FACTOR = 998076443575628800;
// BETA: 18 digit decimal. Parameter by which to divide the redeemed fraction, in order to calc the new base rate from a redemption.
// Corresponds to (1 / ALPHA) in the white paper.
uint256 constant REDEMPTION_BETA = 1;
// To prevent redemptions unless Bold depegs below 0.95 and allow the system to take off
uint256 constant INITIAL_BASE_RATE = _100pct; // 100% initial redemption rate
// Discount to be used once the shutdown thas been triggered
uint256 constant URGENT_REDEMPTION_BONUS = 2e16; // 2%
uint256 constant ONE_MINUTE = 1 minutes;
uint256 constant ONE_YEAR = 365 days;
uint256 constant UPFRONT_INTEREST_PERIOD = 7 days;
uint256 constant INTEREST_RATE_ADJ_COOLDOWN = 7 days;
uint256 constant SP_YIELD_SPLIT = 75 * _1pct; // 75%
uint256 constant MIN_BOLD_IN_SP = 1e18;
// Maximum number of collaterals allowed in the system
// Capped at 40 to ensure redemptions stay within gas limits
uint256 constant MAX_COLLATERALS = 40;
// Dummy contract that lets legacy Hardhat tests query some of the constants
contract Constants {
uint256 public constant _ETH_GAS_COMPENSATION = ETH_GAS_COMPENSATION;
uint256 public constant _MIN_DEBT = MIN_DEBT;
}
"
},
"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-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
"
},
"lib/openzeppelin-contracts/contracts/utils/Address.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
"
},
"src/Zappers/BaseZapper.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import "../Interfaces/IWETH.sol";
import "../Interfaces/IAddressesRegistry.sol";
import "../Interfaces/IBorrowerOperations.sol";
import "../Dependencies/AddRemoveManagers.sol";
import "./LeftoversSweep.sol";
import "./Interfaces/IFlashLoanProvider.sol";
import "./Interfaces/IFlashLoanReceiver.sol";
import "./Interfaces/IExchange.sol";
import "./Interfaces/IZapper.sol";
import "../Types/LatestTroveData.sol";
abstract contract BaseZapper is AddRemoveManagers, LeftoversSweep, IFlashLoanReceiver, IZapper {
IBorrowerOperations public immutable borrowerOperations; // LST branch (i.e., not WETH as collateral)
ITroveManager public immutable troveManager;
IWETH public immutable WETH;
IBoldToken public immutable boldToken;
IFlashLoanProvider public immutable flashLoanProvider;
IExchange public immutable exchange;
constructor(IAddressesRegistry _addressesRegistry, IFlashLoanProvider _flashLoanProvider, IExchange _exchange) {
AddRemoveManagers.initialize(address(_addressesRegistry));
borrowerOperations = _addressesRegistry.borrowerOperations();
troveManager = _addressesRegistry.troveManager();
boldToken = _addressesRegistry.boldToken();
WETH = _addressesRegistry.WETH();
flashLoanProvider = _flashLoanProvider;
exchange = _exchange;
}
function _getTroveIndex(address _sender, uint256 _ownerIndex) internal pure returns (uint256) {
return uint256(keccak256(abi.encode(_sender, _ownerIndex)));
}
function _getTroveIndex(uint256 _ownerIndex) internal view returns (uint256) {
return _getTroveIndex(msg.sender, _ownerIndex);
}
function _requireZapperIsReceiver(uint256 _troveId) internal view {
(, address receiver) = borrowerOperations.removeManagerReceiverOf(_troveId);
require(receiver == address(this), "BZ: Zapper is not receiver for this trove");
}
function _checkAdjustTroveManagers(
uint256 _troveId,
uint256 _collChange,
bool _isCollIncrease,
bool _isDebtIncrease
) internal view returns (address) {
address owner = troveNFT.ownerOf(_troveId);
address receiver = owner;
if ((!_isCollIncrease && _collChange > 0) || _isDebtIncrease) {
receiver = _requireSenderIsOwnerOrRemoveManagerAndGetReceiver(_troveId, owner);
_requireZapperIsReceiver(_troveId);
} 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
}
return receiver;
}
}
"
},
"src/Interfaces/IWETH.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol";
interface IWETH is IERC20Metadata {
function deposit() external payable;
function withdraw(uint256 wad) external;
}
"
},
"src/Interfaces/IAddressesRegistry.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IActivePool.sol";
import "./IBoldToken.sol";
import "./IBorrowerOperations.sol";
import "./ICollSurplusPool.sol";
import "./IDefaultPool.sol";
import "./IHintHelpers.sol";
import "./IMultiTroveGetter.sol";
import "./ISortedTroves.sol";
import "./IStabilityPool.sol";
import "./ITroveManager.sol";
import "./ITroveNFT.sol";
import {IMetadataNFT} from "../NFTMetadata/MetadataNFT.sol";
import {ICollateralRegistry} from "./ICollateralRegistry.sol";
import "./IInterestRouter.sol";
import "./IPriceFeed.sol";
import "./IWETH.sol";
import "./IEbisuBranchManager.sol";
interface IAddressesRegistry {
struct AddressVars {
IERC20Metadata collToken;
IBorrowerOperations borrowerOperations;
ITroveManager troveManager;
ITroveNFT troveNFT;
IMetadataNFT metadataNFT;
IStabilityPool stabilityPool;
IPriceFeed priceFeed;
IActivePool activePool;
IDefaultPool defaultPool;
address gasPoolAddress;
ICollSurplusPool collSurplusPool;
ISortedTroves sortedTroves;
IInterestRouter interestRouter;
IHintHelpers hintHelpers;
IMultiTroveGetter multiTroveGetter;
ICollateralRegistry collateralRegistry;
IBoldToken boldToken;
IWETH WETH;
address borrowerOperationsHelper;
IEbisuBranchManager branchManager;
}
function CCR() external view returns (uint256);
function SCR() external view returns (uint256);
function MCR() external view returns (uint256);
function BCR() external view returns (uint256);
function LIQUIDATION_PENALTY_SP() external view returns (uint256);
function LIQUIDATION_PENALTY_REDISTRIBUTION() external view returns (uint256);
function branchCollGasCompensationCap() external view returns (uint256);
function collToken() external view returns (IERC20Metadata);
function borrowerOperations() external view returns (IBorrowerOperations);
function troveManager() external view returns (ITroveManager);
function troveNFT() external view returns (ITroveNFT);
function metadataNFT() external view returns (IMetadataNFT);
function stabilityPool() external view returns (IStabilityPool);
function priceFeed() external view returns (IPriceFeed);
function activePool() external view returns (IActivePool);
function defaultPool() external view returns (IDefaultPool);
function gasPoolAddress() external view returns (address);
function collSurplusPool() external view returns (ICollSurplusPool);
function sortedTroves() external view returns (ISortedTroves);
function interestRouter() external view returns (IInterestRouter);
function hintHelpers() external view returns (IHintHelpers);
function multiTroveGetter() external view returns (IMultiTroveGetter);
function collateralRegistry() external view returns (ICollateralRegistry);
function boldToken() external view returns (IBoldToken);
function WETH() external returns (IWETH);
function borrowerOperationsHelper() external view returns (address);
function branchManager() external view returns (IEbisuBranchManager);
function setAddresses(AddressVars memory _vars) external;
}
"
},
"src/Interfaces/IBorrowerOperations.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./ILiquityBase.sol";
import "./IAddRemoveManagers.sol";
import "./IBoldToken.sol";
import "./IPriceFeed.sol";
import "./ISortedTroves.sol";
import "./ITroveManager.sol";
import "./IWETH.sol";
import "../Dependencies/BoStructs.sol";
import "./IEbisuBorrowerOperationsHelper.sol";
// Common interface for the Borrower Operations.
interface IBorrowerOperations is ILiquityBase, IAddRemoveManagers {
function initialize(address _addressesRegistryAddress) external;
function openTrove(
address _owner,
uint256 _ownerIndex,
uint256 _ETHAmount,
uint256 _boldAmount,
uint256 _upperHint,
uint256 _lowerHint,
uint256 _annualInterestRate,
uint256 _maxUpfrontFee,
address _addManager,
address _removeManager,
address _receiver
) external returns (uint256);
struct OpenTroveAndJoinInterestBatchManagerParams {
address owner;
uint256 ownerIndex;
uint256 collAmount;
uint256 boldAmount;
uint256 upperHint;
uint256 lowerHint;
address interestBatchManager;
uint256 maxUpfrontFee;
address addManager;
address removeManager;
address receiver;
}
function openTroveAndJoinInterestBatchManager(OpenTroveAndJoinInterestBatchManagerParams calldata _params)
external
returns (uint256);
function addColl(uint256 _troveId, uint256 _ETHAmount) external;
function withdrawColl(uint256 _troveId, uint256 _amount) external;
function withdrawBold(uint256 _troveId, uint256 _amount, uint256 _maxUpfrontFee) external;
function repayBold(uint256 _troveId, uint256 _amount) external;
function closeTrove(uint256 _troveId) external;
function adjustTrove(
uint256 _troveId,
uint256 _collChange,
bool _isCollIncrease,
uint256 _debtChange,
bool isDebtIncrease,
uint256 _maxUpfrontFee
) external;
function adjustZombieTrove(
uint256 _troveId,
uint256 _collChange,
bool _isCollIncrease,
uint256 _boldChange,
bool _isDebtIncrease,
uint256 _upperHint,
uint256 _lowerHint,
uint256 _maxUpfrontFee
) external;
function adjustTroveInterestRate(
uint256 _troveId,
uint256 _newAnnualInterestRate,
uint256 _upperHint,
uint256 _lowerHint,
uint256 _maxUpfrontFee
) external;
function applyPendingDebt(uint256 _troveId, uint256 _lowerHint, uint256 _upperHint) external;
function onLiquidateTrove(uint256 _troveId) external;
function claimCollateral() external;
function hasBeenShutDown() external view returns (bool);
function shutdown() external;
function shutdownFromOracleFailure() external;
// -- individual delegation --
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;
// -- batches --
function lowerBatchManagementFee(uint256 _newAnnualFee) external;
function interestBatchManagerOf(uint256 _troveId) external view returns (address);
function kickFromBatch(uint256 _troveId, uint256 _upperHint, uint256 _lowerHint) external;
function removeFromBatch(
uint256 _troveId,
uint256 _newAnnualInterestRate,
uint256 _upperHint,
uint256 _lowerHint,
uint256 _maxUpfrontFee
) external;
function switchBatchManager(
uint256 _troveId,
uint256 _removeUpperHint,
uint256 _removeLowerHint,
address _newBatchManager,
uint256 _addUpperHint,
uint256 _addLowerHint,
uint256 _maxUpfrontFee
) external;
function setInterestBatchManagerOf(uint256 _troveId, address _batchManager) external;
function boHelper() external view returns (IEbisuBorrowerOperationsHelper);
function setMinDebt(uint256 _minDebt) external;
}
"
},
"src/Dependencies/AddRemoveManagers.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.24;
import "../Interfaces/IAddRemoveManagers.sol";
import "../Interfaces/IAddressesRegistry.sol";
import "../Interfaces/ITroveNFT.sol";
contract AddRemoveManagers is IAddRemoveManagers {
ITroveNFT internal troveNFT;
struct RemoveManagerReceiver {
address manager;
address receiver;
}
/*
* Mapping from TroveId to granted address for operations that "give" money to the trove (add collateral, pay debt).
* Useful for instance for cold/hot wallet setups.
* If its value is zero address, any address is allowed to do those operations on behalf of trove owner.
* Otherwise, only the address in this mapping (and the trove owner) will be allowed.
* To restrict this permission to no one, trove owner should be set in this mapping.
*/
mapping(uint256 => address) public addManagerOf;
/*
* Mapping from TroveId to granted addresses for operations that "withdraw" money from the trove (withdraw collateral, borrow),
* and for each of those addresses another address for the receiver of those withdrawn funds.
* Useful for instance for cold/hot wallet setups or for automations.
* Only the address in this mapping, if any, and the trove owner, will be allowed.
* Therefore, by default this permission is restricted to no one.
* If the receiver is zero, the owner is assumed as the receiver.
* RemoveManager also assumes AddManager permission
*/
mapping(uint256 => RemoveManagerReceiver) public removeManagerReceiverOf;
error EmptyManager();
error NotBorrower();
error NotOwnerNorAddManager();
error NotOwnerNorRemoveManager();
event TroveNFTAddressChanged(address _newTroveNFTAddress);
event AddManagerUpdated(uint256 indexed _troveId, address _newAddManager);
event RemoveManagerAndReceiverUpdated(uint256 indexed _troveId, address _newRemoveManager, address _newReceiver);
function initialize(address _addressesRegistryAddress) public
Submitted on: 2025-10-30 14:28:19
Comments
Log in to comment.
No comments yet.