Skip to content

Commit

Permalink
feat: simplify (#6)
Browse files Browse the repository at this point in the history
* refactor: allow aToken usage

* feat: all usage of aTokens

* fix: update snapshot

* feat: add ability to source aToken

* fix: naming

* fix: use new block

* Changed the batchLiquidate function to the external + fixed comment

---------

Co-authored-by: TepNik <tepelin.nikita@gmail.com>
  • Loading branch information
sakulstra and TepNik authored Feb 10, 2025
1 parent c5e3545 commit 5296130
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 183 deletions.
59 changes: 26 additions & 33 deletions snapshots/BatchRepayBadDebtSteward.json
Original file line number Diff line number Diff line change
@@ -1,35 +1,28 @@
{
"function batchLiquidate: with 0 users": "250226",
"function batchLiquidate: with 1 user": "580464",
"function batchLiquidate: with 2 users": "907296",
"function batchLiquidate: with 3 users": "1125027",
"function batchLiquidate: with 4 users": "1342771",
"function batchLiquidate: with 5 users": "1666388",
"function batchLiquidate: with 6 users": "1884131",
"function batchLiquidateWithMaxCap: with 0 users": "271158",
"function batchLiquidateWithMaxCap: with 1 user": "572541",
"function batchLiquidateWithMaxCap: with 2 users": "893497",
"function batchLiquidateWithMaxCap: with 3 users": "1105352",
"function batchLiquidateWithMaxCap: with 4 users": "1317219",
"function batchLiquidateWithMaxCap: with 5 users": "1634961",
"function batchLiquidateWithMaxCap: with 6 users": "1846828",
"function batchRepayBadDebt: with 0 users": "70072",
"function batchRepayBadDebt: with 1 user": "222244",
"function batchRepayBadDebt: with 2 users": "284640",
"function batchRepayBadDebt: with 3 users": "347036",
"function batchRepayBadDebt: with 4 users": "409431",
"function batchRepayBadDebt: with 5 users": "471827",
"function batchRepayBadDebt: with 6 users": "534223",
"function getBadDebtAmount: with 0 users": "12852",
"function getBadDebtAmount: with 1 user": "38658",
"function getBadDebtAmount: with 2 users": "51412",
"function getBadDebtAmount: with 4 users": "76922",
"function getBadDebtAmount: with 5 users": "89677",
"function getBadDebtAmount: with 6 users": "102432",
"function getDebtAmount: with 0 users": "12875",
"function getDebtAmount: with 1 user": "34341",
"function getDebtAmount: with 2 users": "51412",
"function getDebtAmount: with 4 users": "59584",
"function getDebtAmount: with 5 users": "67998",
"function getDebtAmount: with 6 users": "76413"
"function batchLiquidate: with 0 users": "98545",
"function batchLiquidate: with 1 user": "585000",
"function batchLiquidate: with 2 users": "911832",
"function batchLiquidate: with 3 users": "1129564",
"function batchLiquidate: with 4 users": "1347307",
"function batchLiquidate: with 5 users": "1670924",
"function batchLiquidate: with 6 users": "1888668",
"function batchRepayBadDebt: with 0 users": "70526",
"function batchRepayBadDebt: with 1 user": "222698",
"function batchRepayBadDebt: with 2 users": "285094",
"function batchRepayBadDebt: with 3 users": "347490",
"function batchRepayBadDebt: with 4 users": "409885",
"function batchRepayBadDebt: with 5 users": "472281",
"function batchRepayBadDebt: with 6 users": "534677",
"function getBadDebtAmount: with 0 users": "12830",
"function getBadDebtAmount: with 1 user": "38636",
"function getBadDebtAmount: with 2 users": "51390",
"function getBadDebtAmount: with 4 users": "76900",
"function getBadDebtAmount: with 5 users": "89655",
"function getBadDebtAmount: with 6 users": "102410",
"function getDebtAmount: with 0 users": "12853",
"function getDebtAmount: with 1 user": "34319",
"function getDebtAmount: with 2 users": "51390",
"function getDebtAmount: with 4 users": "59562",
"function getDebtAmount: with 5 users": "67976",
"function getDebtAmount: with 6 users": "76391"
}
90 changes: 49 additions & 41 deletions src/maintenance/BatchRepayBadDebtSteward.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,50 +77,33 @@ contract BatchRepayBadDebtSteward is IBatchRepayBadDebtSteward, RescuableBase, M
}

/* EXTERNAL FUNCTIONS */

/// @inheritdoc IBatchRepayBadDebtSteward
function batchLiquidate(address debtAsset, address collateralAsset, address[] memory users) external override {
(uint256 maxDebtAmount,) = getDebtAmount(debtAsset, users);

batchLiquidateWithMaxCap(debtAsset, collateralAsset, users, maxDebtAmount);
}

/// @inheritdoc IBatchRepayBadDebtSteward
function batchRepayBadDebt(address asset, address[] memory users) external override onlyRole(CLEANUP) {
function batchRepayBadDebt(address asset, address[] memory users, bool useATokens)
external
override
onlyRole(CLEANUP)
{
(uint256 totalDebtAmount, uint256[] memory debtAmounts) = getBadDebtAmount(asset, users);

ICollector(COLLECTOR).transfer(IERC20Col(asset), address(this), totalDebtAmount);
IERC20(asset).forceApprove(address(POOL), totalDebtAmount);
_pullFundsAndApprove(asset, totalDebtAmount, useATokens);

for (uint256 i = 0; i < users.length; i++) {
POOL.repay({asset: asset, amount: debtAmounts[i], interestRateMode: 2, onBehalfOf: users[i]});
}

uint256 balanceLeft = IERC20(asset).balanceOf(address(this));
if (balanceLeft != 0) IERC20(asset).transfer(COLLECTOR, balanceLeft);
_transferExcessToCollector(asset);
}

/// @inheritdoc IBatchRepayBadDebtSteward
function rescueToken(address token) external override {
_emergencyTokenTransfer(token, COLLECTOR, type(uint256).max);
}

/// @inheritdoc IBatchRepayBadDebtSteward
function rescueEth() external override {
_emergencyEtherTransfer(COLLECTOR, address(this).balance);
}

/* PUBLIC FUNCTIONS */

/// @inheritdoc IBatchRepayBadDebtSteward
function batchLiquidateWithMaxCap(
address debtAsset,
address collateralAsset,
address[] memory users,
uint256 maxDebtTokenAmount
) public override onlyRole(CLEANUP) {
ICollector(COLLECTOR).transfer(IERC20Col(debtAsset), address(this), maxDebtTokenAmount);
IERC20(debtAsset).forceApprove(address(POOL), maxDebtTokenAmount);
function batchLiquidate(address debtAsset, address collateralAsset, address[] memory users, bool useAToken)
external
override
onlyRole(CLEANUP)
{
// this is an over approximation as not necessarily all bad debt can be liquidated
// the excess is transfered back to the collector
(uint256 maxDebtAmount,) = getDebtAmount(debtAsset, users);
_pullFundsAndApprove(debtAsset, maxDebtAmount, useAToken);
IERC20(debtAsset).forceApprove(address(POOL), maxDebtAmount);

for (uint256 i = 0; i < users.length; i++) {
POOL.liquidationCall({
Expand All @@ -132,16 +115,22 @@ contract BatchRepayBadDebtSteward is IBatchRepayBadDebtSteward, RescuableBase, M
});
}

// transfer back surplus
uint256 balanceAfter = IERC20(debtAsset).balanceOf(address(this));
if (balanceAfter != 0) {
IERC20(debtAsset).safeTransfer(COLLECTOR, balanceAfter);
}
// the excess is always in the underlying
_transferExcessToCollector(debtAsset);

// transfer back liquidated assets
address collateralAToken = POOL.getReserveAToken(collateralAsset);
uint256 collateralATokenBalance = IERC20(collateralAToken).balanceOf(address(this));
IERC20(collateralAToken).safeTransfer(COLLECTOR, collateralATokenBalance);
_transferExcessToCollector(collateralAToken);
}

/// @inheritdoc IBatchRepayBadDebtSteward
function rescueToken(address token) external override {
_emergencyTokenTransfer(token, COLLECTOR, type(uint256).max);
}

/// @inheritdoc IBatchRepayBadDebtSteward
function rescueEth() external override {
_emergencyEtherTransfer(COLLECTOR, address(this).balance);
}

/* PUBLIC VIEW FUNCTIONS */
Expand Down Expand Up @@ -202,4 +191,23 @@ contract BatchRepayBadDebtSteward is IBatchRepayBadDebtSteward, RescuableBase, M

return (totalDebtAmount, debtAmounts);
}

function _pullFundsAndApprove(address asset, uint256 amount, bool unwrapAToken) internal {
if (unwrapAToken) {
address aToken = POOL.getReserveAToken(asset);
// 1 wei surplus to account for rounding on multiple operations
ICollector(COLLECTOR).transfer(IERC20Col(aToken), address(this), amount + 1);
POOL.withdraw(asset, type(uint256).max, address(this));
} else {
ICollector(COLLECTOR).transfer(IERC20Col(asset), address(this), amount);
}
IERC20(asset).forceApprove(address(POOL), amount);
}

function _transferExcessToCollector(address asset) internal {
uint256 balanceAfter = IERC20(asset).balanceOf(address(this));
if (balanceAfter != 0) {
IERC20(asset).safeTransfer(COLLECTOR, balanceAfter);
}
}
}
24 changes: 6 additions & 18 deletions src/maintenance/interfaces/IBatchRepayBadDebtSteward.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,35 +31,23 @@ interface IBatchRepayBadDebtSteward is IRescuableBase, IAccessControl {

/* EXTERNAL FUNCTIONS */

/// @notice Liquidates all the users
/// @param debtAsset The address of the debt asset
/// @param collateralAsset The address of the collateral asset that will be liquidated
/// @param users The addresses of the users to liquidate
/// @dev This amount is pulled from the Aave collector. The contract sends
/// any surplus back to the collector.
function batchLiquidate(address debtAsset, address collateralAsset, address[] memory users) external;

/// @notice Liquidates all the users with a max debt amount to be liquidated
/// @param debtAsset The address of the debt asset
/// @param collateralAsset The address of the collateral asset that will be liquidated
/// @param users The addresses of the users to liquidate
/// @param maxDebtTokenAmount The maximum amount of debt tokens to be liquidated
/// @dev This amount is pulled from the Aave collector. The contract sends
/// any surplus back to the collector.
function batchLiquidateWithMaxCap(
address debtAsset,
address collateralAsset,
address[] memory users,
uint256 maxDebtTokenAmount
) external;
/// @param useATokens If true the token will pull aTokens from the collector.
/// If false it will pull the underlying.
function batchLiquidate(address debtAsset, address collateralAsset, address[] memory users, bool useATokens) external;

/// @notice Repays all the bad debt of users
/// @dev Will revert if the user has a collateral or no debt.
/// Repayed amount is pulled from the Aave collector. The contract sends
/// any surplus back to the collector.
/// @param asset The address of an asset to repay
/// @param users The addresses of users to repay
function batchRepayBadDebt(address asset, address[] calldata users) external;
/// @param useATokens If true the token will pull aTokens from the collector.
/// If false it will pull the underlying.
function batchRepayBadDebt(address asset, address[] calldata users, bool useATokens) external;

/// @notice Rescues the tokens
/// @param token The address of the token to rescue
Expand Down
59 changes: 2 additions & 57 deletions tests/gas/maintenance/BatchRepayBadDebtSteward.gas.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -181,48 +181,6 @@ contract BatchRepayBadDebtStewardGasTest is BatchRepayBadDebtStewardBaseTest {
vm.snapshotGasLastCall("BatchRepayBadDebtSteward", "function batchLiquidate: with 6 users");
}

function test_batchLiquidateWithMaxCap_zero_users() public {
_callBatchLiquidateWithMaxCapWithNumberOfUsers(0);

vm.snapshotGasLastCall("BatchRepayBadDebtSteward", "function batchLiquidateWithMaxCap: with 0 users");
}

function test_batchLiquidateWithMaxCap_one_user() public {
_callBatchLiquidateWithMaxCapWithNumberOfUsers(1);

vm.snapshotGasLastCall("BatchRepayBadDebtSteward", "function batchLiquidateWithMaxCap: with 1 user");
}

function test_batchLiquidateWithMaxCap_two_users() public {
_callBatchLiquidateWithMaxCapWithNumberOfUsers(2);

vm.snapshotGasLastCall("BatchRepayBadDebtSteward", "function batchLiquidateWithMaxCap: with 2 users");
}

function test_batchLiquidateWithMaxCap_three_users() public {
_callBatchLiquidateWithMaxCapWithNumberOfUsers(3);

vm.snapshotGasLastCall("BatchRepayBadDebtSteward", "function batchLiquidateWithMaxCap: with 3 users");
}

function test_batchLiquidateWithMaxCap_four_users() public {
_callBatchLiquidateWithMaxCapWithNumberOfUsers(4);

vm.snapshotGasLastCall("BatchRepayBadDebtSteward", "function batchLiquidateWithMaxCap: with 4 users");
}

function test_batchLiquidateWithMaxCap_five_users() public {
_callBatchLiquidateWithMaxCapWithNumberOfUsers(5);

vm.snapshotGasLastCall("BatchRepayBadDebtSteward", "function batchLiquidateWithMaxCap: with 5 users");
}

function test_batchLiquidateWithMaxCap_six_users() public {
_callBatchLiquidateWithMaxCapWithNumberOfUsers(6);

vm.snapshotGasLastCall("BatchRepayBadDebtSteward", "function batchLiquidateWithMaxCap: with 6 users");
}

function _callGetBadDebtAmountWithNumberOfUsers(uint256 userAmount) private view {
address[] memory users = new address[](userAmount);
for (uint256 i = 0; i < userAmount; ++i) {
Expand Down Expand Up @@ -251,7 +209,7 @@ contract BatchRepayBadDebtStewardGasTest is BatchRepayBadDebtStewardBaseTest {
deal(assetUnderlying, collector, mintAmount);

vm.prank(guardian);
steward.batchRepayBadDebt(assetUnderlying, users);
steward.batchRepayBadDebt(assetUnderlying, users, false);
}

function _callBatchLiquidateWithNumberOfUsers(uint256 userAmount) private {
Expand All @@ -264,19 +222,6 @@ contract BatchRepayBadDebtStewardGasTest is BatchRepayBadDebtStewardBaseTest {
deal(assetUnderlying, collector, mintAmount);

vm.prank(guardian);
steward.batchLiquidate(assetUnderlying, collateralEligibleForLiquidations, users);
}

function _callBatchLiquidateWithMaxCapWithNumberOfUsers(uint256 userAmount) private {
address[] memory users = new address[](userAmount);
for (uint256 i = 0; i < userAmount; ++i) {
users[i] = usersEligibleForLiquidations[i];
}

uint256 mintAmount = 1_000_000e18;
deal(assetUnderlying, collector, mintAmount);

vm.prank(guardian);
steward.batchLiquidateWithMaxCap(assetUnderlying, collateralEligibleForLiquidations, users, totalDebtToLiquidate);
steward.batchLiquidate(assetUnderlying, collateralEligibleForLiquidations, users, false);
}
}
Loading

0 comments on commit 5296130

Please sign in to comment.