Skip to content
Closed
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
4 changes: 2 additions & 2 deletions snapshots/NativeTokenGateway.Operations.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"repayNative": "166460",
"supplyAsCollateralNative": "160122",
"supplyNative": "135753",
"withdrawNative: full": "125548",
"withdrawNative: partial": "136735"
"withdrawNative: full": "125568",
"withdrawNative: partial": "136760"
}
2 changes: 1 addition & 1 deletion snapshots/SignatureGateway.Operations.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
"supplyWithSig": "151985",
"updateUserDynamicConfigWithSig": "63120",
"updateUserRiskPremiumWithSig": "62090",
"withdrawWithSig": "130803"
"withdrawWithSig": "130823"
}
10 changes: 5 additions & 5 deletions snapshots/Spoke.Operations.ZeroRiskPremium.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
"usingAsCollateral: 1 borrow, enable": "42504",
"usingAsCollateral: 2 borrows, disable": "127250",
"usingAsCollateral: 2 borrows, enable": "42516",
"withdraw: 0 borrows, full": "127944",
"withdraw: 0 borrows, partial": "132840",
"withdraw: 1 borrow, partial": "159894",
"withdraw: 2 borrows, partial": "174452",
"withdraw: non collateral": "105891"
"withdraw: 0 borrows, full": "109192",
"withdraw: 0 borrows, partial": "108992",
"withdraw: 1 borrow, partial": "160318",
"withdraw: 2 borrows, partial": "174876",
"withdraw: non collateral": "105916"
}
10 changes: 5 additions & 5 deletions snapshots/Spoke.Operations.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
"usingAsCollateral: 1 borrow, enable": "42504",
"usingAsCollateral: 2 borrows, disable": "229165",
"usingAsCollateral: 2 borrows, enable": "42516",
"withdraw: 0 borrows, full": "127944",
"withdraw: 0 borrows, partial": "132840",
"withdraw: 1 borrow, partial": "210737",
"withdraw: 2 borrows, partial": "256902",
"withdraw: non collateral": "105891"
"withdraw: 0 borrows, full": "109192",
"withdraw: 0 borrows, partial": "108992",
"withdraw: 1 borrow, partial": "211161",
"withdraw: 2 borrows, partial": "257326",
"withdraw: non collateral": "105916"
}
5 changes: 4 additions & 1 deletion src/spoke/Spoke.sol
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,10 @@ abstract contract Spoke is

userPosition.suppliedShares -= withdrawnShares.toUint120();

if (_positionStatus[onBehalfOf].isUsingAsCollateral(reserveId)) {
PositionStatus storage positionStatus = _positionStatus[onBehalfOf];
if (
positionStatus.isUsingAsCollateral(reserveId) && positionStatus.isBorrowingAny(_reserveCount)
) {
uint256 newRiskPremium = _refreshAndValidateUserAccountData(onBehalfOf).riskPremium;
Copy link
Member

Choose a reason for hiding this comment

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

when i initially thought about it, the opt went in _processUserAccountData as a short circuit

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

debatable but we could assume a user has borrows if the asset is enabled as collateral. They could skip hf check by disabling the asset as collateral first (that runs HF check anyway) or not enabling it from the start.

This opt would benefit depositors (only; with no borrow intention), and not sure it's needed given that supply does not activate the asset as collateral by default.

_notifyRiskPremiumUpdate(onBehalfOf, newRiskPremium);
}
Expand Down
21 changes: 21 additions & 0 deletions src/spoke/libraries/PositionStatusMap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,27 @@ library PositionStatusMap {
}
}

/// @notice Checks if the user is borrowing any reserve.
/// @dev Disregards potential dirty bits set after `reserveCount`.
/// @param reserveCount The current `reserveCount`, to avoid reading uninitialized buckets.
function isBorrowingAny(
ISpoke.PositionStatus storage self,
uint256 reserveCount
) internal view returns (bool) {
unchecked {
uint256 bucket = reserveCount.bucketId();
if (self.map[bucket].isolateBorrowingUntil(reserveCount) != 0) {
return true;
}
while (bucket != 0) {
if (self.map[--bucket].isolateBorrowing() != 0) {
return true;
}
}
return false;
}
}

/// @notice Finds the previous borrowing or collateralized reserve strictly before `fromReserveId`.
/// @dev The search starts at `fromReserveId` (exclusive) and scans backward across buckets.
/// @dev Returns `NOT_FOUND` if no borrowing or collateralized reserve exists before the bound.
Expand Down
4 changes: 4 additions & 0 deletions tests/mocks/PositionStatusMapWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ contract PositionStatusMapWrapper {
return _p.borrowCount(reserveCount);
}

function isBorrowingAny(uint256 reserveCount) external view returns (bool) {
return _p.isBorrowingAny(reserveCount);
}

function getBucketWord(uint256 reserveId) external view returns (uint256) {
return _p.getBucketWord(reserveId);
}
Expand Down
72 changes: 72 additions & 0 deletions tests/unit/libraries/PositionStatusMap.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,78 @@ contract PositionStatusMapTest is Base {
assertEq(p.borrowCount(reserveCount), borrowCount);
}

function test_isBorrowingAny() public {
p.setBorrowing(127, true);
assertTrue(p.isBorrowingAny(128));
p.setBorrowing(127, false);

p.setBorrowing(128, true);
assertFalse(p.isBorrowingAny(128));
assertTrue(p.isBorrowingAny(129));

// ignore invalid bits
assertFalse(p.isBorrowingAny(100));

p.setBorrowing(2, true);
assertTrue(p.isBorrowingAny(128));

p.setBorrowing(32, true);
assertTrue(p.isBorrowingAny(128));
p.setBorrowing(342, true);
assertTrue(p.isBorrowingAny(343));

p.setBorrowing(32, false);
assertTrue(p.isBorrowingAny(343));
p.setBorrowing(342, false);
p.setBorrowing(128, false);
p.setBorrowing(2, false);

// disregards collateral reserves
p.setUsingAsCollateral(32, true);
assertFalse(p.isBorrowingAny(343));

p.setUsingAsCollateral(79, true);
assertFalse(p.isBorrowingAny(343));

p.setUsingAsCollateral(255, true);
assertFalse(p.isBorrowingAny(343));
}

function test_isBorrowingAny_ignoresInvalidBits() public {
p.setBorrowing(127, true);
assertFalse(p.isBorrowingAny(100));
assertTrue(p.isBorrowingAny(200));
p.setBorrowing(127, false);

p.setBorrowing(255, true);
assertFalse(p.isBorrowingAny(200));
p.setBorrowing(133, true);
assertTrue(p.isBorrowingAny(200));
p.setBorrowing(255, false);
p.setBorrowing(133, false);

p.setBorrowing(383, true);
assertFalse(p.isBorrowingAny(300));
p.setBorrowing(283, true);
assertTrue(p.isBorrowingAny(300));
p.setBorrowing(383, false);
p.setBorrowing(283, false);

p.setBorrowing(511, true);
assertFalse(p.isBorrowingAny(500));
assertTrue(p.isBorrowingAny(600));
p.setBorrowing(511, false);
}

function test_isBorrowingAny(uint256 reserveCount, uint256 borrowingReserveId) public {
reserveCount = bound(reserveCount, 1, 1 << 10); // gas limit
borrowingReserveId = bound(borrowingReserveId, 0, 1 << 10); // gas limit

p.setBorrowing(borrowingReserveId, true);

assertEq(p.isBorrowingAny(reserveCount), reserveCount > borrowingReserveId ? true : false);
}

function test_setters_use_correct_slot(uint256 a) public {
uint256 bucket = a / 128;
bytes32 slot = keccak256(abi.encode(bucket, p.slot()));
Expand Down
Loading