Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 167 additions & 29 deletions contracts/BorrowerOperations.sol

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions contracts/BorrowerOperationsStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import "./Interfaces/IZEROStaking.sol";
import "./Interfaces/IFeeDistributor.sol";
import "./Dependencies/Ownable.sol";
import "./Dependencies/Mynt/IMassetManager.sol";
import "./Interfaces/IRedemptionBuffer.sol";

contract BorrowerOperationsStorage is Ownable {
string public constant NAME = "BorrowerOperations";
Expand All @@ -36,4 +37,19 @@ contract BorrowerOperationsStorage is Ownable {

IMassetManager public massetManager;
IFeeDistributor public feeDistributor;

// --- Redemption buffer config ---

// Redemption buffer contract used to hold protocol RBTC
IRedemptionBuffer internal redemptionBuffer;

// Fraction of incoming RBTC sent to the buffer on openTrove.
// 1e18 == 100%, e.g. 1e17 == 10%.
uint256 internal redemptionBufferRate;

// ---------------------------------------------------------------------
// Reentrancy guard (BorrowerOperations only)
// ---------------------------------------------------------------------
// 0 = uninitialized, 1 = not entered, 2 = entered
uint256 internal _reentrancyStatus;
}
22 changes: 22 additions & 0 deletions contracts/Dependencies/TroveManagerBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,24 @@ contract TroveManagerBase is LiquityBase, TroveManagerStorage {
*/
uint256 public constant BETA = 2;

// ---------------------------------------------------------------------
// Reentrancy guard
// ---------------------------------------------------------------------
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;

modifier nonReentrant() {
require(_reentrancyStatus != _ENTERED, "TroveManager: reentrant call");
_reentrancyStatus = _ENTERED;
_;
_reentrancyStatus = _NOT_ENTERED;
}

modifier requireNotEntered() {
require(_reentrancyStatus != _ENTERED, "TroveManager: locked");
_;
}

/**
--- Variable container structs for liquidations ---

Expand Down Expand Up @@ -152,6 +170,7 @@ contract TroveManagerBase is LiquityBase, TroveManagerStorage {

constructor(uint256 _bootstrapPeriod) public {
BOOTSTRAP_PERIOD = _bootstrapPeriod;
_reentrancyStatus = _NOT_ENTERED; // init guard
}

/// Return the current collateral ratio (ICR) of a given Trove. Takes a trove's pending coll and debt rewards from redistributions into account.
Expand Down Expand Up @@ -351,6 +370,9 @@ contract TroveManagerBase is LiquityBase, TroveManagerStorage {
uint256 _redemptionRate,
uint256 _ETHDrawn
) internal pure returns (uint256) {
if (_ETHDrawn == 0) {
return 0; // This prevents a revert if for instance no eth is drawn from the redemption buffer or no eth is drawn from troves.
}
uint256 redemptionFee = _redemptionRate.mul(_ETHDrawn).div(DECIMAL_PRECISION);
require(
redemptionFee < _ETHDrawn,
Expand Down
617 changes: 460 additions & 157 deletions contracts/Dependencies/TroveManagerRedeemOps.sol

Large diffs are not rendered by default.

46 changes: 41 additions & 5 deletions contracts/FeeDistributor.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.6.11;

import "./Interfaces/IFeeDistributor.sol";
Expand All @@ -23,6 +22,8 @@ contract FeeDistributor is CheckContract, FeeDistributorStorage, IFeeDistributor
event ZUSDDistributed(uint256 _zusdDistributedAmount);
event RBTCistributed(uint256 _rbtcDistributedAmount);

event RedemptionBufferAddressChanged(address _redemptionBufferAddress);

// --- Dependency setters ---

function setAddresses(
Expand Down Expand Up @@ -62,17 +63,48 @@ contract FeeDistributor is CheckContract, FeeDistributorStorage, IFeeDistributor
emit ActivePoolAddressSet(_activePoolAddress);
}

// --------------------------------------------------------------------
// RedemptionBuffer wiring
// --------------------------------------------------------------------

/**
* @notice Getter required by IFeeDistributor.
* @dev Implemented explicitly (instead of relying on a public storage variable getter)
* to avoid multiple-inheritance conflicts in Solidity 0.6.x.
*/
function redemptionBufferAddress() external view override returns (address) {
return _redemptionBufferAddress;
}

/**
* @notice Set RedemptionBuffer address.
* @dev Owner-only. This enables:
* - accepting RBTC sent from RedemptionBuffer (receive())
* - optionally allowing RedemptionBuffer to call distributeFees()
*/
function setRedemptionBufferAddress(address _buffer) external override onlyOwner {
require(_buffer != address(0), "FeeDistributor: zero buffer");
checkContract(_buffer);

_redemptionBufferAddress = _buffer;
emit RedemptionBufferAddressChanged(_buffer);
}

function setFeeToFeeSharingCollector(uint256 FEE_TO_FEE_SHARING_COLLECTOR_) public onlyOwner {
FEE_TO_FEE_SHARING_COLLECTOR = FEE_TO_FEE_SHARING_COLLECTOR_;
}

function distributeFees() public override {
require(
msg.sender == address(borrowerOperations) || msg.sender == address(troveManager),
msg.sender == address(borrowerOperations) ||
msg.sender == address(troveManager) ||
msg.sender == _redemptionBufferAddress,
"FeeDistributor: invalid caller"
);

uint256 zusdtoDistribute = zusdToken.balanceOf(address(this));
uint256 rbtcToDistribute = address(this).balance;

if (zusdtoDistribute != 0) {
_distributeZUSD(zusdtoDistribute);
}
Expand All @@ -89,6 +121,7 @@ contract FeeDistributor is CheckContract, FeeDistributorStorage, IFeeDistributor
zusdToken.approve(address(feeSharingCollector), feeToFeeSharingCollector);

feeSharingCollector.transferTokens(address(zusdToken), uint96(feeToFeeSharingCollector));

// Send fee to ZERO staking contract
uint256 feeToZeroStaking = toDistribute.sub(feeToFeeSharingCollector);
if (feeToZeroStaking != 0) {
Expand Down Expand Up @@ -119,11 +152,14 @@ contract FeeDistributor is CheckContract, FeeDistributorStorage, IFeeDistributor
emit RBTCistributed(toDistribute);
}

function _requireCallerIsActivePool() internal view {
require(msg.sender == activePoolAddress, "FeeDistributor: caller is not ActivePool");
function _requireCallerIsActivePoolOrRedemptionBuffer() internal view {
require(
msg.sender == activePoolAddress || msg.sender == _redemptionBufferAddress,
"FeeDistributor: invalid RBTC sender"
);
}

receive() external payable {
_requireCallerIsActivePool();
_requireCallerIsActivePoolOrRedemptionBuffer();
}
}
3 changes: 3 additions & 0 deletions contracts/FeeDistributorStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,7 @@ contract FeeDistributorStorage is Ownable {

//pct of fees sent to feeSharingCollector address
uint256 public FEE_TO_FEE_SHARING_COLLECTOR;

// Buffer address so we can accept RBTC from it and optionally let it call distributeFees()
address internal _redemptionBufferAddress;
}
60 changes: 57 additions & 3 deletions contracts/Interfaces/IBorrowerOperations.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ pragma solidity 0.6.11;
pragma experimental ABIEncoderV2;

import "../Dependencies/Mynt/IMassetManager.sol";
import "./IRedemptionBuffer.sol";
import { IPermit2, ISignatureTransfer } from "./IPermit2.sol";

/// Common interface for the Trove Manager.
/// @title IBorrowerOperations
/// @notice External interface for BorrowerOperations (opening/adjusting/closing troves).
/// @dev
/// Redemptions are handled by TroveManager/TroveManagerRedeemOps, but BorrowerOperations is
/// responsible for issuing debt and collecting:
/// - the normal ZUSD borrowing fee (paid in ZUSD and minted to FeeDistributor)
/// - the RedemptionBuffer fee (paid in RBTC and deposited into RedemptionBuffer)
interface IBorrowerOperations {
// --- Events ---

Expand All @@ -21,6 +28,12 @@ interface IBorrowerOperations {
event SortedTrovesAddressChanged(address _sortedTrovesAddress);
event ZUSDTokenAddressChanged(address _zusdTokenAddress);
event ZEROStakingAddressChanged(address _zeroStakingAddress);
/// @notice Emitted when the Mynt/mAsset manager is configured.
event MassetManagerAddressChanged(address _massetManagerAddress);
/// @notice Emitted when the RedemptionBuffer contract address is updated.
event RedemptionBufferAddressChanged(address _redemptionBufferAddress);
/// @notice Emitted when the redemption buffer rate is updated.
event RedemptionBufferRateChanged(uint256 _redemptionBufferRate);

event TroveCreated(address indexed _borrower, uint256 arrayIndex);
event TroveUpdated(
Expand Down Expand Up @@ -65,6 +78,45 @@ interface IBorrowerOperations {
address _zeroStakingAddress
) external;

/// @notice Sets the Mynt/mAsset manager used for NUE/DLLR flows.
/// @dev Owner/governance only in implementation.
function setMassetManagerAddress(address _massetManagerAddress) external;

// --- RedemptionBuffer configuration ---

/// @notice Sets the RedemptionBuffer contract address.
/// @dev Owner/governance only in implementation.
function setRedemptionBufferAddress(address _buffer) external;

/// @notice Sets the redemption buffer rate used to compute the RBTC fee.
/// @dev 1e18 precision; 1e18 == 100%. Owner/governance only in implementation.
function setRedemptionBufferRate(uint256 _rate) external;

/// @notice Returns the configured RedemptionBuffer contract address.
function getRedemptionBufferAddress() external view returns (address);

/// @notice Returns the configured redemption buffer rate (1e18 precision).
function getRedemptionBufferRate() external view returns (uint256);

// --- RedemptionBuffer fee quoting ---

/// @notice Quotes the extra RBTC (wei) required on top of collateral when borrowing `_ZUSDAmount`.
/// @dev NOT view in many Liquity forks because priceFeed.fetchPrice() is non-view.
/// Frontends should call via eth_call/staticcall.
function getRedemptionBufferFeeRBTC(uint256 _ZUSDAmount) external returns (uint256);

/// @notice View-only quote that uses a caller-supplied price.
/// @dev Lets frontends avoid calling BorrowerOperations.getRedemptionBufferFeeRBTC()
/// if they already have a price from elsewhere. The implementation should mirror the
/// exact arithmetic (including rounding) used by openTrove/adjustTrove.
/// @param _ZUSDAmount Amount of new ZUSD debt being minted (not including borrowing fee).
/// @param _price Oracle price in 1e18 precision (RBTC/USD or RBTC/ZUSD face-value model).
/// @return feeRBTC Extra RBTC (wei) required as the redemption buffer fee.
function getRedemptionBufferFeeRBTCWithPrice(uint256 _ZUSDAmount, uint256 _price)
external
view
returns (uint256 feeRBTC);

/**
* @notice payable function that creates a Trove for the caller with the requested debt, and the Ether received as collateral.
* Successful execution is conditional mainly on the resulting collateralization ratio which must exceed the minimum (110% in Normal Mode, 150% in Recovery Mode).
Expand Down Expand Up @@ -129,6 +181,7 @@ interface IBorrowerOperations {
* @notice issues `_amount` of ZUSD from the caller’s Trove to the caller.
* Executes only if the Trove's collateralization ratio would remain above the minimum, and the resulting total collateralization ratio is above 150%.
* The borrower has to provide a `_maxFeePercentage` that he/she is willing to accept in case of a fee slippage, i.e. when a redemption transaction is processed first, driving up the issuance fee.
* When increasing debt and redemptionBufferRate > 0, caller must send msg.value at least equal to the redemption buffer fee; quote via getRedemptionBufferFeeRBTC().
* @param _maxFee max fee percentage to acept in case of a fee slippage
* @param _amount ZUSD amount to withdraw
* @param _upperHint upper trove id hint
Expand All @@ -139,15 +192,16 @@ interface IBorrowerOperations {
uint256 _amount,
address _upperHint,
address _lowerHint
) external;
) external payable;

/// Borrow (withdraw) ZUSD tokens from a trove: mint new ZUSD tokens to the owner and convert it to DLLR in one transaction
/// When increasing debt and redemptionBufferRate > 0, caller must send msg.value at least equal to the redemption buffer fee; quote via getRedemptionBufferFeeRBTC().
function withdrawZusdAndConvertToDLLR(
uint256 _maxFeePercentage,
uint256 _ZUSDAmount,
address _upperHint,
address _lowerHint
) external returns (uint256);
) external payable returns (uint256);

/// @notice repay `_amount` of ZUSD to the caller’s Trove, subject to leaving 50 debt in the Trove (which corresponds to the 50 ZUSD gas compensation).
/// @param _amount ZUSD amount to repay
Expand Down
51 changes: 35 additions & 16 deletions contracts/Interfaces/IFeeDistributor.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.6.11;

/// Common interface for Fee Distributor.
/// @title IFeeDistributor
/// @notice FeeDistributor receives protocol fees (ZUSD and/or RBTC) and forwards them to
/// FeeSharingCollector / ZEROStaking according to protocol configuration.
/// @dev
/// - BorrowerOperations sends ZUSD borrowing fees here and then calls distributeFees().
/// - TroveManager sends RBTC redemption fees here and then calls distributeFees().
/// - With the RedemptionBuffer "swap" model, TroveManagerRedeemOps may:
/// * transfer ZUSD from redeemer -> FeeDistributor (ERC20 transferFrom)
/// * send RBTC redemption fee from RedemptionBuffer -> FeeDistributor
/// and then call distributeFees().
interface IFeeDistributor {
// --- Events ---
// --- Events (mirrors FeeDistributor.sol) ---

event FeeSharingCollectorAddressChanged(address _feeSharingCollectorAddress);
event ZeroStakingAddressChanged(address _zeroStakingAddress);
Expand All @@ -14,22 +22,19 @@ interface IFeeDistributor {
event ZUSDTokenAddressChanged(address _zusdTokenAddress);
event ActivePoolAddressSet(address _activePoolAddress);

/// @notice Emitted when ZUSD is distributed to collectors/stakers.
event ZUSDDistributed(uint256 _zusdDistributedAmount);

/// @notice Emitted when RBTC is distributed to collectors/stakers.
/// @dev Note: name preserved (typo) for backwards compatibility with existing deployments/logs.
event RBTCistributed(uint256 _rbtcDistributedAmount);

// --- Functions ---

/**
* @notice Called only once on init, to set addresses of other Zero contracts. Callable only by owner
* @dev initializer function, checks addresses are contracts
* @param _feeSharingCollectorAddress FeeSharingCollector address
* @param _zeroStakingAddress ZEROStaking contract address
* @param _borrowerOperationsAddress borrowerOperations contract address
* @param _troveManagerAddress TroveManager contract address
* @param _wrbtcAddress wrbtc ERC20 contract address
* @param _zusdTokenAddress ZUSDToken contract address
* @param _activePoolAddress ActivePool contract address
*/
/// @notice Emitted when the RedemptionBuffer address is configured.
event RedemptionBufferAddressChanged(address _redemptionBufferAddress);

// --- Admin/initializer functions ---

/// @notice Called once on init to wire core contracts.
function setAddresses(
address _feeSharingCollectorAddress,
address _zeroStakingAddress,
Expand All @@ -40,5 +45,19 @@ interface IFeeDistributor {
address _activePoolAddress
) external;

/// @notice Sets the RedemptionBuffer contract address.
/// @dev Needed if FeeDistributor.receive() should accept RBTC directly from RedemptionBuffer
/// (e.g. when redemption fees on buffer-swaps are paid from the buffer).
function setRedemptionBufferAddress(address _redemptionBufferAddress) external;

// --- Core function ---

/// @notice Distributes any ZUSD and/or RBTC currently held by FeeDistributor.
/// @dev In your implementation this is permissioned (BO/TroveManager/(optional) RedemptionBuffer).
function distributeFees() external;

// --- Views / getters ---

/// @notice Getter for the configured RedemptionBuffer address (public var in implementation).
function redemptionBufferAddress() external view returns (address);
}
33 changes: 33 additions & 0 deletions contracts/Interfaces/IRedemptionBuffer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.6.11;

/// @title IRedemptionBuffer
/// @notice Minimal interface for the protocol RBTC "buffer" used during redemptions.
/// @dev
/// - BorrowerOperations deposits RBTC (collected as a fee when debt is issued).
/// - TroveManager withdraws RBTC to satisfy redemptions.
/// - Governance may send RBTC to FeeDistributor (or elsewhere) via distributeToStakers().
///
/// IMPORTANT DESIGN NOTE:
/// This interface does NOT prescribe whether redeemer ZUSD is burned or transferred (swap-style).
/// That policy lives in TroveManagerRedeemOps. The buffer just holds/sends RBTC.
interface IRedemptionBuffer {
/// @notice Receive RBTC into the buffer.
/// @dev Expected caller: BorrowerOperations (onlyBorrowerOps in implementation).
function deposit() external payable;

/// @notice Withdraw RBTC from the buffer.
/// @dev Expected caller: TroveManager (onlyTroveManager in implementation).
/// @param _to Destination to receive RBTC (redeemer, FeeDistributor, etc.).
/// @param _amount Amount of RBTC (wei) to send.
function withdrawForRedemption(address payable _to, uint256 _amount) external;

/// @notice Governance-controlled distribution of RBTC held in the buffer.
/// @dev Typically used to send RBTC to FeeDistributor so it gets split to stakers.
/// @param _amount Amount of RBTC (wei) to distribute.
function distributeToStakers(uint256 _amount) external;

/// @notice Returns the RBTC balance tracked by the buffer.
/// @dev Implementation usually tracks an internal accounting variable (e.g. totalBufferedColl).
function getBalance() external view returns (uint256);
}
Loading