Skip to content

Commit 2659b7d

Browse files
authored
feat: allow receive supply shares when liquidating instead of underlying assets (#884)
1 parent d497ae3 commit 2659b7d

28 files changed

+737
-408
lines changed

snapshots/Spoke.Getters.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
2-
"getUserAccountData: supplies: 0, borrows: 0": "12164",
3-
"getUserAccountData: supplies: 1, borrows: 0": "47254",
4-
"getUserAccountData: supplies: 2, borrows: 0": "77459",
5-
"getUserAccountData: supplies: 2, borrows: 1": "98200",
6-
"getUserAccountData: supplies: 2, borrows: 2": "117595"
2+
"getUserAccountData: supplies: 0, borrows: 0": "12142",
3+
"getUserAccountData: supplies: 1, borrows: 0": "47232",
4+
"getUserAccountData: supplies: 2, borrows: 0": "77437",
5+
"getUserAccountData: supplies: 2, borrows: 1": "98178",
6+
"getUserAccountData: supplies: 2, borrows: 2": "117573"
77
}

snapshots/Spoke.Operations.ZeroRiskPremium.json

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,31 @@
11
{
22
"borrow: first": "199032",
33
"borrow: second action, same reserve": "179553",
4-
"liquidationCall: full": "291015",
5-
"liquidationCall: partial": "315109",
6-
"permitReserve + repay (multicall)": "235800",
7-
"permitReserve + supply (multicall)": "141050",
8-
"permitReserve + supply + enable collateral (multicall)": "176287",
9-
"repay: full": "151764",
10-
"repay: partial": "176227",
11-
"setUserPositionManagerWithSig: disable": "44918",
12-
"setUserPositionManagerWithSig: enable": "68947",
13-
"supply + enable collateral (multicall)": "154229",
14-
"supply: 0 borrows, collateral disabled": "115784",
15-
"supply: 0 borrows, collateral enabled": "120181",
16-
"supply: 1 borrow": "120173",
17-
"supply: second action, same reserve": "103081",
18-
"updateUserDynamicConfig: 1 collateral": "73761",
19-
"updateUserDynamicConfig: 2 collaterals": "88621",
20-
"updateUserRiskPremium: 1 borrow": "95276",
21-
"updateUserRiskPremium: 2 borrows": "111368",
22-
"usingAsCollateral: 0 borrows, enable": "58976",
23-
"usingAsCollateral: 1 borrow, disable": "105523",
24-
"usingAsCollateral: 1 borrow, enable": "32298",
25-
"usingAsCollateral: 2 borrows, disable": "127988",
26-
"usingAsCollateral: 2 borrows, enable": "41876",
4+
"liquidationCall (receiveShares): full": "263815",
5+
"liquidationCall (receiveShares): partial": "305858",
6+
"liquidationCall: full": "291754",
7+
"liquidationCall: partial": "315848",
8+
"permitReserve + repay (multicall)": "235861",
9+
"permitReserve + supply (multicall)": "141007",
10+
"permitReserve + supply + enable collateral (multicall)": "176310",
11+
"repay: full": "151846",
12+
"repay: partial": "176309",
13+
"setUserPositionManagerWithSig: disable": "44896",
14+
"setUserPositionManagerWithSig: enable": "68925",
15+
"supply + enable collateral (multicall)": "154251",
16+
"supply: 0 borrows, collateral disabled": "115762",
17+
"supply: 0 borrows, collateral enabled": "120159",
18+
"supply: 1 borrow": "120151",
19+
"supply: second action, same reserve": "103059",
20+
"updateUserDynamicConfig: 1 collateral": "73827",
21+
"updateUserDynamicConfig: 2 collaterals": "88687",
22+
"updateUserRiskPremium: 1 borrow": "95254",
23+
"updateUserRiskPremium: 2 borrows": "111346",
24+
"usingAsCollateral: 0 borrows, enable": "59042",
25+
"usingAsCollateral: 1 borrow, disable": "105589",
26+
"usingAsCollateral: 1 borrow, enable": "32364",
27+
"usingAsCollateral: 2 borrows, disable": "128054",
28+
"usingAsCollateral: 2 borrows, enable": "41942",
2729
"withdraw: 0 borrows, full": "127742",
2830
"withdraw: 0 borrows, partial": "132783",
2931
"withdraw: 1 borrow, partial": "161765",

snapshots/Spoke.Operations.json

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,31 @@
11
{
22
"borrow: first": "269201",
33
"borrow: second action, same reserve": "212722",
4-
"liquidationCall: full": "324401",
5-
"liquidationCall: partial": "348495",
6-
"permitReserve + repay (multicall)": "269179",
7-
"permitReserve + supply (multicall)": "141050",
8-
"permitReserve + supply + enable collateral (multicall)": "176287",
9-
"repay: full": "146405",
10-
"repay: partial": "209606",
11-
"setUserPositionManagerWithSig: disable": "44918",
12-
"setUserPositionManagerWithSig: enable": "68947",
13-
"supply + enable collateral (multicall)": "154229",
14-
"supply: 0 borrows, collateral disabled": "115784",
15-
"supply: 0 borrows, collateral enabled": "120181",
16-
"supply: 1 borrow": "120173",
17-
"supply: second action, same reserve": "103081",
18-
"updateUserDynamicConfig: 1 collateral": "73761",
19-
"updateUserDynamicConfig: 2 collaterals": "88621",
20-
"updateUserRiskPremium: 1 borrow": "177363",
21-
"updateUserRiskPremium: 2 borrows": "262728",
22-
"usingAsCollateral: 0 borrows, enable": "58976",
23-
"usingAsCollateral: 1 borrow, disable": "187610",
24-
"usingAsCollateral: 1 borrow, enable": "32298",
25-
"usingAsCollateral: 2 borrows, disable": "287347",
26-
"usingAsCollateral: 2 borrows, enable": "41876",
4+
"liquidationCall (receiveShares): full": "297201",
5+
"liquidationCall (receiveShares): partial": "339244",
6+
"liquidationCall: full": "325140",
7+
"liquidationCall: partial": "349234",
8+
"permitReserve + repay (multicall)": "269240",
9+
"permitReserve + supply (multicall)": "141007",
10+
"permitReserve + supply + enable collateral (multicall)": "176310",
11+
"repay: full": "146487",
12+
"repay: partial": "209688",
13+
"setUserPositionManagerWithSig: disable": "44896",
14+
"setUserPositionManagerWithSig: enable": "68925",
15+
"supply + enable collateral (multicall)": "154251",
16+
"supply: 0 borrows, collateral disabled": "115762",
17+
"supply: 0 borrows, collateral enabled": "120159",
18+
"supply: 1 borrow": "120151",
19+
"supply: second action, same reserve": "103059",
20+
"updateUserDynamicConfig: 1 collateral": "73827",
21+
"updateUserDynamicConfig: 2 collaterals": "88687",
22+
"updateUserRiskPremium: 1 borrow": "177341",
23+
"updateUserRiskPremium: 2 borrows": "262706",
24+
"usingAsCollateral: 0 borrows, enable": "59042",
25+
"usingAsCollateral: 1 borrow, disable": "187676",
26+
"usingAsCollateral: 1 borrow, enable": "32364",
27+
"usingAsCollateral: 2 borrows, disable": "287413",
28+
"usingAsCollateral: 2 borrows, enable": "41942",
2729
"withdraw: 0 borrows, full": "127742",
2830
"withdraw: 0 borrows, partial": "132783",
2931
"withdraw: 1 borrow, partial": "241352",

src/hub/Hub.sol

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -712,29 +712,6 @@ contract Hub is IHub, AccessManaged {
712712
);
713713
}
714714

715-
/// @dev Validates applied premium delta for given premium data and returns updated premium data.
716-
function _validateApplyPremiumDelta(
717-
uint256 drawnIndex,
718-
uint256 premiumShares,
719-
uint256 premiumOffset,
720-
uint256 realizedPremium,
721-
PremiumDelta calldata premium,
722-
uint256 premiumAmount
723-
) internal pure returns (uint128, uint128, uint128) {
724-
uint256 premiumBefore = premiumShares.rayMulUp(drawnIndex) - premiumOffset;
725-
premiumBefore += realizedPremium;
726-
727-
premiumShares = premiumShares.add(premium.sharesDelta);
728-
premiumOffset = premiumOffset.add(premium.offsetDelta);
729-
realizedPremium = realizedPremium.add(premium.realizedDelta);
730-
731-
uint256 premiumAfter = premiumShares.rayMulUp(drawnIndex) - premiumOffset;
732-
premiumAfter += realizedPremium;
733-
// can increase due to precision loss on premium (drawn unchanged)
734-
require(premiumAfter + premiumAmount - premiumBefore <= 2, InvalidPremiumChange());
735-
return (premiumShares.toUint128(), premiumOffset.toUint128(), realizedPremium.toUint128());
736-
}
737-
738715
/// @dev Returns the spoke's drawn amount for a specified asset.
739716
function _getSpokeDrawn(
740717
Asset storage asset,
@@ -878,4 +855,27 @@ contract Hub is IHub, AccessManaged {
878855
require(caller == asset.reinvestmentController, OnlyReinvestmentController());
879856
require(amount > 0 && amount <= asset.swept, InvalidAmount());
880857
}
858+
859+
/// @dev Validates applied premium delta for given premium data and returns updated premium data.
860+
function _validateApplyPremiumDelta(
861+
uint256 drawnIndex,
862+
uint256 premiumShares,
863+
uint256 premiumOffset,
864+
uint256 realizedPremium,
865+
PremiumDelta calldata premium,
866+
uint256 premiumAmount
867+
) internal pure returns (uint128, uint128, uint128) {
868+
uint256 premiumBefore = premiumShares.rayMulUp(drawnIndex) - premiumOffset;
869+
premiumBefore += realizedPremium;
870+
871+
premiumShares = premiumShares.add(premium.sharesDelta);
872+
premiumOffset = premiumOffset.add(premium.offsetDelta);
873+
realizedPremium = realizedPremium.add(premium.realizedDelta);
874+
875+
uint256 premiumAfter = premiumShares.rayMulUp(drawnIndex) - premiumOffset;
876+
premiumAfter += realizedPremium;
877+
// can increase due to precision loss on premium (drawn unchanged)
878+
require(premiumAfter + premiumAmount - premiumBefore <= 2, InvalidPremiumChange());
879+
return (premiumShares.toUint128(), premiumOffset.toUint128(), realizedPremium.toUint128());
880+
}
881881
}

src/hub/interfaces/IHub.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ interface IHub is IHubBase, IAccessManaged {
342342
/// @notice Returns whether the spoke is listed for the specified asset.
343343
/// @param assetId The identifier of the asset.
344344
/// @param spoke The address of the spoke.
345-
/// @return True if the spoke is listed, false otherwise.
345+
/// @return True if the spoke is listed.
346346
function isSpokeListed(uint256 assetId, address spoke) external view returns (bool);
347347

348348
/// @notice Returns the address of the spoke for an asset at the given index.

src/hub/interfaces/IHubBase.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ interface IHubBase {
151151
/// @param premiumDelta The change in premium.
152152
function refreshPremium(uint256 assetId, PremiumDelta calldata premiumDelta) external;
153153

154-
/// @notice Transfers existing `addedShares` of caller spoke to `feeReceiver`.
154+
/// @notice Transfers `shares` amount of existing `addedShares` of caller spoke to `feeReceiver`.
155155
/// @dev Only callable by active spokes. Utilized to pay liquidation fee.
156156
/// @param assetId The identifier of the asset.
157157
/// @param shares The amount of shares to pay to feeReceiver.

src/hub/libraries/AssetLogic.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,11 @@ library AssetLogic {
179179
uint256 liquidityFee = asset.liquidityFee;
180180
if (liquidityFee == 0) return 0;
181181

182-
// @dev we do not simplify further to avoid overestimating the liquidity growth
183-
uint256 feesAmount = (asset.drawnShares.rayMulDown(indexDelta) +
182+
// we do not simplify further to avoid overestimating the liquidity growth
183+
uint256 feeAmount = (asset.drawnShares.rayMulDown(indexDelta) +
184184
asset.premiumShares.rayMulDown(indexDelta)).percentMulDown(liquidityFee);
185185

186-
return feesAmount.toSharesDown(asset.totalAddedAssets() - feesAmount, asset.addedShares);
186+
return feeAmount.toSharesDown(asset.totalAddedAssets() - feeAmount, asset.addedShares);
187187
}
188188

189189
/// @notice Calculates the amount of unrealized fee shares since last accrual.

src/spoke/Spoke.sol

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ abstract contract Spoke is ISpoke, Multicall, NoncesKeyed, AccessManagedUpgradea
3030
using KeyValueList for KeyValueList.List;
3131
using PositionStatusMap for *;
3232
using MathUtils for *;
33+
using LiquidationLogic for *;
3334

3435
/// @inheritdoc ISpoke
3536
uint256 public constant MAX_ALLOWED_ASSET_ID = type(uint16).max;
@@ -299,7 +300,7 @@ abstract contract Spoke is ISpoke, Multicall, NoncesKeyed, AccessManagedUpgradea
299300
msg.sender
300301
);
301302

302-
_settlePremiumDebt(userPosition, premiumDelta.realizedDelta);
303+
userPosition.settlePremiumDebt(premiumDelta.realizedDelta);
303304
userPosition.drawnShares -= restoredShares.toUint128();
304305
if (userPosition.drawnShares == 0) {
305306
_positionStatus[onBehalfOf].setBorrowing(reserveId, false);
@@ -318,7 +319,8 @@ abstract contract Spoke is ISpoke, Multicall, NoncesKeyed, AccessManagedUpgradea
318319
uint256 collateralReserveId,
319320
uint256 debtReserveId,
320321
address user,
321-
uint256 debtToCover
322+
uint256 debtToCover,
323+
bool receiveShares
322324
) external {
323325
UserAccountData memory userAccountData = _calculateUserAccountData(user);
324326
LiquidationLogic.LiquidateUserParams memory params = LiquidationLogic.LiquidateUserParams({
@@ -334,7 +336,8 @@ abstract contract Spoke is ISpoke, Multicall, NoncesKeyed, AccessManagedUpgradea
334336
totalDebtValue: userAccountData.totalDebtValue,
335337
activeCollateralCount: userAccountData.activeCollateralCount,
336338
borrowedCount: userAccountData.borrowedCount,
337-
liquidator: msg.sender
339+
liquidator: msg.sender,
340+
receiveShares: receiveShares
338341
});
339342

340343
(params.drawnDebt, params.premiumDebt, params.accruedPremium) = _getUserDebt(
@@ -350,8 +353,7 @@ abstract contract Spoke is ISpoke, Multicall, NoncesKeyed, AccessManagedUpgradea
350353
bool isUserInDeficit = LiquidationLogic.liquidateUser(
351354
_reserves[collateralReserveId],
352355
_reserves[debtReserveId],
353-
_userPositions[user][collateralReserveId],
354-
_userPositions[user][debtReserveId],
356+
_userPositions,
355357
_positionStatus[user],
356358
_liquidationConfig,
357359
collateralDynConfig,
@@ -475,11 +477,6 @@ abstract contract Spoke is ISpoke, Multicall, NoncesKeyed, AccessManagedUpgradea
475477
{} catch {}
476478
}
477479

478-
/// @inheritdoc ISpoke
479-
function getLiquidationLogic() external pure returns (address) {
480-
return address(LiquidationLogic);
481-
}
482-
483480
/// @inheritdoc ISpoke
484481
function getLiquidationConfig() external view returns (LiquidationConfig memory) {
485482
return _liquidationConfig;
@@ -646,6 +643,11 @@ abstract contract Spoke is ISpoke, Multicall, NoncesKeyed, AccessManagedUpgradea
646643
return _domainSeparator();
647644
}
648645

646+
/// @inheritdoc ISpoke
647+
function getLiquidationLogic() external pure returns (address) {
648+
return address(LiquidationLogic);
649+
}
650+
649651
function _updateReservePriceSource(uint256 reserveId, address priceSource) internal {
650652
require(priceSource != address(0), InvalidAddress());
651653
IAaveOracle(ORACLE).setReserveSource(reserveId, priceSource);
@@ -858,31 +860,20 @@ abstract contract Spoke is ISpoke, Multicall, NoncesKeyed, AccessManagedUpgradea
858860
premiumDebtReported,
859861
premiumDelta
860862
);
861-
_settlePremiumDebt(userPosition, premiumDelta.realizedDelta);
863+
userPosition.settlePremiumDebt(premiumDelta.realizedDelta);
862864
userPosition.drawnShares -= deficitShares.toUint128();
863865
positionStatus.setBorrowing(reserveId, false);
864866
}
865867
// non-zero deficit means user ends up with zero total debt
866868
emit UpdateUserRiskPremium(user, 0);
867869
}
868870

869-
/// @notice Settles the premium debt by realizing change in premium and resetting premium shares and offset.
870-
function _settlePremiumDebt(UserPosition storage userPosition, int256 realizedDelta) internal {
871-
userPosition.premiumShares = 0;
872-
userPosition.premiumOffset = 0;
873-
userPosition.realizedPremium = userPosition.realizedPremium.add(realizedDelta).toUint128();
874-
}
875-
876871
function _getReserve(uint256 reserveId) internal view returns (Reserve storage) {
877872
Reserve storage reserve = _reserves[reserveId];
878873
require(address(reserve.hub) != address(0), ReserveNotListed());
879874
return reserve;
880875
}
881876

882-
function _validateReserveConfig(ReserveConfig calldata config) internal pure {
883-
require(config.collateralRisk <= MAX_ALLOWED_COLLATERAL_RISK, InvalidCollateralRisk());
884-
}
885-
886877
/// @dev CollateralFactor of historical config keys cannot be 0, which allows liquidations to proceed.
887878
function _validateUpdateDynamicReserveConfig(
888879
DynamicReserveConfig storage currentConfig,
@@ -953,6 +944,10 @@ abstract contract Spoke is ISpoke, Multicall, NoncesKeyed, AccessManagedUpgradea
953944
);
954945
}
955946

947+
function _validateReserveConfig(ReserveConfig calldata config) internal pure {
948+
require(config.collateralRisk <= MAX_ALLOWED_COLLATERAL_RISK, InvalidCollateralRisk());
949+
}
950+
956951
/// @dev Enforces compatible `maxLiquidationBonus` and `collateralFactor` so at the moment debt is created
957952
/// there is enough collateral to cover liquidation.
958953
function _validateDynamicReserveConfig(DynamicReserveConfig calldata config) internal pure {

src/spoke/TreasurySpoke.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ contract TreasurySpoke is ITreasurySpoke, Ownable2Step {
7272
}
7373

7474
/// @inheritdoc ISpokeBase
75-
function liquidationCall(uint256, uint256, address, uint256) external pure {
75+
function liquidationCall(uint256, uint256, address, uint256, bool) external pure {
7676
revert UnsupportedAction();
7777
}
7878

0 commit comments

Comments
 (0)