Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
26 changes: 25 additions & 1 deletion contracts/BorrowerOperations.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.6.11;
pragma experimental ABIEncoderV2;

Expand Down Expand Up @@ -159,6 +158,25 @@ contract BorrowerOperations is
emit ZEROStakingAddressChanged(_zeroStakingAddress);
}

/*
* @notice set the user's block number for opening, and do check & reset to 0 for closing
*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/*
* @notice set the user's block number for opening, and do check & reset to 0 for closing
*
/*
* @notice set the user's block number for opening or increasing LoCs, and do check & reset to 0 for closing or decreasing
*

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* @dev This is the function will be called by the open, close, increase, decrease trove function
*/
function notInTheSameBlockHandler(bool _isOpening) private {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function notInTheSameBlockHandler(bool _isOpening) private {
function recoveryModeMutexHandler(bool _isOpening) private {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if(_isOpening) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function notInTheSameBlockHandler(bool _isOpening) private {
if(_isOpening) {
function notInTheSameBlockHandler(bool _openOrIncrease) private {
if(_openOrIncrease) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

userBlockNumber[tx.origin] = block.number;
} else {
if(userBlockNumber[tx.origin] > 0) {
if(userBlockNumber[tx.origin] == block.number) {
revert("ZeroProtocolMutex: mutex locked");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
revert("ZeroProtocolMutex: mutex locked");
revert("Recovery mode mutex locked. Try in another block");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

userBlockNumber[tx.origin] = 0;
}
}
}

function setMassetManagerAddress(address _massetManagerAddress) external onlyOwner {
massetManager = IMassetManager(_massetManagerAddress);
emit MassetManagerAddressChanged(_massetManagerAddress);
Expand Down Expand Up @@ -203,6 +221,8 @@ contract BorrowerOperations is
vars.price = priceFeed.fetchPrice();
bool isRecoveryMode = _checkRecoveryMode(vars.price);

if(isRecoveryMode) notInTheSameBlockHandler(true);

_requireValidMaxFeePercentage(_maxFeePercentage, isRecoveryMode);
_requireTroveisNotActive(contractsCache.troveManager, msg.sender);

Expand Down Expand Up @@ -585,6 +605,10 @@ contract BorrowerOperations is
vars.price = priceFeed.fetchPrice();
vars.isRecoveryMode = _checkRecoveryMode(vars.price);

if(vars.isRecoveryMode) {
notInTheSameBlockHandler(_isDebtIncrease);
}

if (_isDebtIncrease) {
_requireValidMaxFeePercentage(_maxFeePercentage, vars.isRecoveryMode);
_requireNonZeroDebtChange(_ZUSDChange);
Expand Down
5 changes: 5 additions & 0 deletions contracts/BorrowerOperationsStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,9 @@ contract BorrowerOperationsStorage is Ownable {

IMassetManager public massetManager;
IFeeDistributor public feeDistributor;

/*
* to store the user's block number
*/
mapping(address => uint256) public userBlockNumber;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/*
* to store the user's block number
*/
mapping(address => uint256) public userBlockNumber;
}
/*
* Store the LoC block number on open/increase when in the Recovery mode
*/
mapping(address => uint256) public recoveryModeMutex;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

41 changes: 41 additions & 0 deletions contracts/TestContracts/BorrowerOperationsCrossReentrancy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.6.11;

import "../Interfaces/IBorrowerOperations.sol";

interface IPriceFeedTestnet {
function setPrice(uint256 price) external returns (bool);
}

contract BorrowerOperationsCrossReentrancy {
IBorrowerOperations public borrowerOperations;

constructor(
IBorrowerOperations _borrowerOperations
) public {
borrowerOperations = _borrowerOperations;
}

fallback() external payable {}

function testCrossReentrancy(
uint256 _maxFeePercentage,
uint256 _ZUSDAmount,
address _upperHint,
address _lowerHint,
address _priceFeed
) public payable {
borrowerOperations.openTrove{value: msg.value}(
_maxFeePercentage,
_ZUSDAmount,
_upperHint,
_lowerHint
);

// manipulate the price so that the recovery mode will be triggered
IPriceFeedTestnet(_priceFeed).setPrice(1e8);

// // should revert due to reentrancy violation
borrowerOperations.addColl(_upperHint, _lowerHint);
}
}
30 changes: 30 additions & 0 deletions tests/js/BorrowerOperationsTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const TroveManagerTester = artifacts.require("TroveManagerTester");
const ZUSDTokenTester = artifacts.require("./ZUSDTokenTester");
const MassetManagerTester = artifacts.require("MassetManagerTester");
const NueMockToken = artifacts.require("NueMockToken");
const BorrowerOperationsCrossReentrancy = artifacts.require("BorrowerOperationsCrossReentrancy");
const { AllowanceProvider, PermitTransferFrom, SignatureTransfer } = require("@uniswap/permit2-sdk");

const th = testHelpers.TestHelper;
Expand Down Expand Up @@ -6995,6 +6996,35 @@ contract("BorrowerOperations", async accounts => {

assert.isTrue(newTCR.eq(expectedTCR));
});

it("openTrove(): open a Trove, then adjust it in the same block should revert", async () => {
await openTrove({ ICR: toBN(dec(2, 18)), extraParams: { from: alice } });
await priceFeed.setPrice("105000000000000000000");

assert.isTrue(await th.checkRecoveryMode(contracts));

const extraZUSDAmount = toBN(dec(1000, 18));
const MIN_DEBT = (
await th.getNetBorrowingAmount(contracts, await contracts.borrowerOperations.MIN_NET_DEBT())
).add(th.toBN(1)); // add 1 to avoid rounding issues
const zusdAmount = MIN_DEBT.add(extraZUSDAmount);

const price = await contracts.priceFeedTestnet.getPrice();
const totalDebt = await th.getOpenTroveTotalDebt(contracts, zusdAmount);
const ICR = toBN(dec(2, 18));
const borrowerOperationsCrossReentrancy = await BorrowerOperationsCrossReentrancy.new(contracts.borrowerOperations.address);

await assertRevert(
borrowerOperationsCrossReentrancy.testCrossReentrancy(
th._100pct,
extraZUSDAmount,
whale,
whale,
priceFeed.address,
{value: ICR.mul(totalDebt).div(price)}),
"ZeroProtocolMutex: mutex locked"
);
});
});

if (!withProxy) {
Expand Down