Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: liquidate erc721 with credit #307

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
feat: liquidate erc721 with credit
Signed-off-by: GopherJ <[email protected]>
  • Loading branch information
0x8f701 committed Dec 29, 2022
commit ec976e2493eb04e9d7739a6d0aca5ee4c4358e98
5 changes: 4 additions & 1 deletion contracts/interfaces/IPoolCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ interface IPoolCore {
* @param liquidationAsset The address of the underlying borrowed asset to be repaid with the liquidation
* @param borrower The address of the borrower getting liquidated
* @param liquidationAmount The debt amount of borrowed `asset` the liquidator wants to cover
* @param creditAmount The credit amount of liquidation `asset` the liquidator wants to use
* @param liquidatedCollateralTokenId The token id of ERC721 asset received by the liquidator
* @param liquidator The address of the liquidator
* @param receiveNToken True if the liquidators wants to receive the collateral NTokens, `false` if he wants
Expand All @@ -154,6 +155,7 @@ interface IPoolCore {
address indexed liquidationAsset,
address indexed borrower,
uint256 liquidationAmount,
uint256 creditAmount,
uint256 liquidatedCollateralTokenId,
address liquidator,
bool receiveNToken
Expand Down Expand Up @@ -439,7 +441,8 @@ interface IPoolCore {
address collateralAsset,
address user,
uint256 collateralTokenId,
uint256 liquidationAmount,
uint256 maxLiquidationAmount,
uint256 creditAmount,
bool receiveNToken
) external payable;

Expand Down
1 change: 1 addition & 0 deletions contracts/mocks/upgradeability/MockPoolCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ contract PoolCoreV2 is
address,
uint256,
uint256,
uint256,
bool
) external payable virtual override nonReentrant {
revert(EMEGENCY_DISABLE_CALL);
Expand Down
122 changes: 97 additions & 25 deletions contracts/protocol/libraries/logic/LiquidationLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {Helpers} from "../../libraries/helpers/Helpers.sol";
import {DataTypes} from "../../libraries/types/DataTypes.sol";
import {ReserveLogic} from "./ReserveLogic.sol";
import {SupplyLogic} from "./SupplyLogic.sol";
import {BorrowLogic} from "./BorrowLogic.sol";
import {ValidationLogic} from "./ValidationLogic.sol";
import {GenericLogic} from "./GenericLogic.sol";
import {UserConfiguration} from "../../libraries/configuration/UserConfiguration.sol";
Expand Down Expand Up @@ -86,6 +87,7 @@ library LiquidationLogic {
address indexed liquidationAsset,
address indexed borrower,
uint256 liquidationAmount,
uint256 creditAmount,
uint256 liquidatedCollateralTokenId,
address liquidator,
bool receiveNToken
Expand Down Expand Up @@ -297,10 +299,12 @@ library LiquidationLogic {
DataTypes.ReserveData storage liquidationAssetReserve = reservesData[
params.liquidationAsset
];
DataTypes.UserConfigurationMap storage userConfig = usersConfig[
DataTypes.UserConfigurationMap storage borrowerConfig = usersConfig[
params.borrower
];

DataTypes.UserConfigurationMap storage liquidatorConfig = usersConfig[
params.liquidator
];
vars.liquidationAssetReserveId = liquidationAssetReserve.id;
vars.liquidationAssetReserveCache = liquidationAssetReserve.cache();
// liquidationAssetReserve.updateState(vars.liquidationAssetReserveCache);
Expand All @@ -323,7 +327,7 @@ library LiquidationLogic {
reservesData,
reservesList,
DataTypes.CalculateUserAccountDataParams({
userConfig: userConfig,
userConfig: borrowerConfig,
reservesCount: params.reservesCount,
user: params.borrower,
oracle: params.priceOracle
Expand All @@ -350,7 +354,7 @@ library LiquidationLogic {

ValidationLogic.validateLiquidateERC721(
reservesData,
userConfig,
borrowerConfig,
collateralReserve,
DataTypes.ValidateLiquidateERC721Params({
liquidationAssetReserveCache: vars.liquidationAssetReserveCache,
Expand All @@ -359,6 +363,7 @@ library LiquidationLogic {
borrower: params.borrower,
globalDebt: vars.userGlobalDebt,
actualLiquidationAmount: vars.actualLiquidationAmount,
creditAmount: params.creditAmount,
maxLiquidationAmount: params.liquidationAmount,
healthFactor: vars.healthFactor,
weth: params.weth,
Expand All @@ -382,12 +387,10 @@ library LiquidationLogic {
);
}

_supplyNewCollateral(reservesData, userConfig, params, vars);

// If the collateral being liquidated is equal to the user balance,
// we set the currency as not being used as collateral anymore
if (vars.userCollateral == 1) {
userConfig.setUsingAsCollateral(collateralReserve.id, false);
borrowerConfig.setUsingAsCollateral(collateralReserve.id, false);
emit ReserveUsedAsCollateralDisabled(
params.collateralAsset,
params.borrower
Expand All @@ -405,20 +408,27 @@ library LiquidationLogic {
}

if (params.receiveXToken) {
INToken(vars.collateralXToken).transferOnLiquidation(
params.borrower,
params.liquidator,
params.collateralTokenId
);
_liquidateNTokens(reservesData, liquidatorConfig, params, vars);
} else {
_burnCollateralNTokens(params, vars);
}

_supplyLiquidateTokens(
reservesData,
reservesList,
liquidationAssetReserve,
borrowerConfig,
liquidatorConfig,
params,
vars
);

emit LiquidateERC721(
params.collateralAsset,
params.liquidationAsset,
params.borrower,
vars.actualLiquidationAmount,
params.creditAmount,
params.collateralTokenId,
params.liquidator,
params.receiveXToken
Expand Down Expand Up @@ -515,6 +525,37 @@ library LiquidationLogic {
}
}

/**
* @notice Liquidates the user xTokens by transferring them to the liquidator.
* @dev The function also checks the state of the liquidator and activates the xToken as collateral
* @param liquidatorConfig The liquidator configuration that track the supplied/borrowed assets
* @param params The additional parameters needed to execute the liquidation function
* @param vars The executeLiquidateERC20() function local vars
*/
function _liquidateNTokens(
mapping(address => DataTypes.ReserveData) storage reservesData,
DataTypes.UserConfigurationMap storage liquidatorConfig,
DataTypes.ExecuteLiquidateParams memory params,
ExecuteLiquidateLocalVars memory vars
) internal {
INToken nToken = INToken(vars.collateralXToken);
nToken.transferOnLiquidation(
params.borrower,
params.liquidator,
params.collateralTokenId
);

uint256[] memory tokenIds = new uint256[](1);
tokenIds[0] = params.collateralTokenId;
SupplyLogic.executeCollateralizeERC721(
reservesData,
liquidatorConfig,
params.collateralAsset,
tokenIds,
params.liquidator
);
}

/**
* @notice Burns the debt tokens of the user up to the amount being repaid by the liquidator.
* @dev The function alters the `liquidationAssetReserveCache` state in `vars` to update the debt related data.
Expand Down Expand Up @@ -557,22 +598,52 @@ library LiquidationLogic {
}

/**
* @notice Supply new collateral for taking out of borrower's another collateral
* @param userConfig The user configuration that track the supplied/borrowed assets
* @notice Supply liquidation tokens for taking out of borrower's another collateral
* @param reservesData The state of all the reserves
* @param reservesList The addresses of all the active reserves
* @param liquidationAssetReserve The data of the liquidation asset reserve
* @param borrowerConfig The borrower configuration that track the supplied/borrowed assets
* @param liquidatorConfig The liquidator configuration that track the supplied/borrowed assets
* @param params The additional parameters needed to execute the liquidation function
* @param vars the executeLiquidateERC20() function local vars
*/
function _supplyNewCollateral(
function _supplyLiquidateTokens(
mapping(address => DataTypes.ReserveData) storage reservesData,
DataTypes.UserConfigurationMap storage userConfig,
mapping(uint256 => address) storage reservesList,
DataTypes.ReserveData storage liquidationAssetReserve,
DataTypes.UserConfigurationMap storage borrowerConfig,
DataTypes.UserConfigurationMap storage liquidatorConfig,
DataTypes.ExecuteLiquidateParams memory params,
ExecuteLiquidateLocalVars memory vars
) internal {
_depositETH(params, vars);

// Borrow creditAmount out using liquidated NToken's credit
if (params.creditAmount != 0) {
ValidationLogic.validateFlashloanSimple(liquidationAssetReserve);
IPToken(vars.liquidationAssetReserveCache.xTokenAddress)
.transferUnderlyingTo(vars.payer, params.creditAmount);
BorrowLogic.executeBorrow(
reservesData,
reservesList,
liquidatorConfig,
DataTypes.ExecuteBorrowParams({
asset: params.liquidationAsset,
user: params.liquidator,
onBehalfOf: params.liquidator,
amount: params.creditAmount,
referralCode: 0,
releaseUnderlying: false,
reservesCount: params.reservesCount,
oracle: params.priceOracle,
priceOracleSentinel: params.priceOracleSentinel
})
);
}

SupplyLogic.executeSupply(
reservesData,
userConfig,
borrowerConfig,
DataTypes.ExecuteSupplyParams({
asset: params.liquidationAsset,
amount: vars.actualLiquidationAmount -
Expand All @@ -583,8 +654,10 @@ library LiquidationLogic {
})
);

if (!userConfig.isUsingAsCollateral(vars.liquidationAssetReserveId)) {
userConfig.setUsingAsCollateral(
if (
!borrowerConfig.isUsingAsCollateral(vars.liquidationAssetReserveId)
) {
borrowerConfig.setUsingAsCollateral(
vars.liquidationAssetReserveId,
true
);
Expand Down Expand Up @@ -871,12 +944,11 @@ library LiquidationLogic {
vars.payer = msg.sender;
} else {
vars.payer = address(this);
IWETH(params.weth).deposit{value: vars.actualLiquidationAmount}();
if (msg.value > vars.actualLiquidationAmount) {
Address.sendValue(
payable(msg.sender),
msg.value - vars.actualLiquidationAmount
);
uint256 downpayment = vars.actualLiquidationAmount -
params.creditAmount;
IWETH(params.weth).deposit{value: downpayment}();
if (msg.value > downpayment) {
Address.sendValue(payable(msg.sender), msg.value - downpayment);
}
}
}
Expand Down
7 changes: 5 additions & 2 deletions contracts/protocol/libraries/logic/ValidationLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -675,8 +675,11 @@ library ValidationLogic {
);

require(
params.maxLiquidationAmount >= params.actualLiquidationAmount &&
(msg.value == 0 || msg.value >= params.maxLiquidationAmount),
params.creditAmount <= params.actualLiquidationAmount &&
params.maxLiquidationAmount >= params.actualLiquidationAmount &&
(msg.value == 0 ||
msg.value >=
params.maxLiquidationAmount - params.creditAmount),
Errors.LIQUIDATION_AMOUNT_NOT_ENOUGH
);

Expand Down
2 changes: 2 additions & 0 deletions contracts/protocol/libraries/types/DataTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ library DataTypes {
struct ExecuteLiquidateParams {
uint256 reservesCount;
uint256 liquidationAmount;
uint256 creditAmount;
uint256 collateralTokenId;
uint256 auctionRecoveryHealthFactor;
address weth;
Expand Down Expand Up @@ -251,6 +252,7 @@ library DataTypes {
uint256 tokenId;
address weth;
uint256 actualLiquidationAmount;
uint256 creditAmount;
uint256 maxLiquidationAmount;
uint256 auctionRecoveryHealthFactor;
address priceOracleSentinel;
Expand Down
3 changes: 3 additions & 0 deletions contracts/protocol/pool/PoolCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@ contract PoolCore is
DataTypes.ExecuteLiquidateParams({
reservesCount: ps._reservesCount,
liquidationAmount: liquidationAmount,
creditAmount: 0,
auctionRecoveryHealthFactor: ps._auctionRecoveryHealthFactor,
weth: ADDRESSES_PROVIDER.getWETH(),
collateralAsset: collateralAsset,
Expand All @@ -460,6 +461,7 @@ contract PoolCore is
address borrower,
uint256 collateralTokenId,
uint256 maxLiquidationAmount,
uint256 creditAmount,
bool receiveNToken
) external payable virtual override nonReentrant {
DataTypes.PoolStorage storage ps = poolStorage();
Expand All @@ -471,6 +473,7 @@ contract PoolCore is
DataTypes.ExecuteLiquidateParams({
reservesCount: ps._reservesCount,
liquidationAmount: maxLiquidationAmount,
creditAmount: creditAmount,
auctionRecoveryHealthFactor: ps._auctionRecoveryHealthFactor,
weth: ADDRESSES_PROVIDER.getWETH(),
collateralAsset: collateralAsset,
Expand Down
2 changes: 2 additions & 0 deletions helpers/contracts-deployments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,8 @@ export const deployPoolCoreLibraries = async (
{
["contracts/protocol/libraries/logic/SupplyLogic.sol:SupplyLogic"]:
supplyLogic.address,
["contracts/protocol/libraries/logic/BorrowLogic.sol:BorrowLogic"]:
borrowLogic.address,
},
verify
);
Expand Down
1 change: 1 addition & 0 deletions test/_pool_ape_staking.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1262,6 +1262,7 @@ describe("APE Coin Staking Test", () => {
user1.address,
0,
await convertToCurrencyDecimals(weth.address, "13"),
0,
false,
{gasLimit: 5000000}
)
Expand Down
Loading