From 14f38fff2e917225b266f51c447bdab64d321b04 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Wed, 27 Sep 2023 17:04:21 +0800 Subject: [PATCH 01/14] fix: shortfalls using treasury Signed-off-by: GopherJ --- contracts/interfaces/IPoolParameters.sol | 6 + contracts/interfaces/IWstETH.sol | 8 + .../protocol/libraries/logic/SupplyLogic.sol | 8 +- .../protocol/libraries/types/DataTypes.sol | 1 + contracts/protocol/pool/PoolCore.sol | 3 +- contracts/protocol/pool/PoolParameters.sol | 242 ++++++++++++++++++ 6 files changed, 264 insertions(+), 4 deletions(-) create mode 100644 contracts/interfaces/IWstETH.sol diff --git a/contracts/interfaces/IPoolParameters.sol b/contracts/interfaces/IPoolParameters.sol index aa8691ee3..becfc1dd5 100644 --- a/contracts/interfaces/IPoolParameters.sol +++ b/contracts/interfaces/IPoolParameters.sol @@ -102,6 +102,12 @@ interface IPoolParameters { **/ function mintToTreasury(address[] calldata assets) external; + /** + * @notice Fix shortfalls by sending reserves to xTokens + * @param assets The list of reserves for which the minting needs to be executed + **/ + function fixShortfalls(address[] calldata assets) external; + /** * @notice Rescue and transfer tokens locked in this contract * @param assetType The asset type of the token diff --git a/contracts/interfaces/IWstETH.sol b/contracts/interfaces/IWstETH.sol new file mode 100644 index 000000000..ae08f9ed5 --- /dev/null +++ b/contracts/interfaces/IWstETH.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.0; + +interface IWstETH { + function wrap(uint256 _stETHAmount) external returns (uint256); + + function unwrap(uint256 _wstETHAmount) external returns (uint256); +} diff --git a/contracts/protocol/libraries/logic/SupplyLogic.sol b/contracts/protocol/libraries/logic/SupplyLogic.sol index 50d7f1c52..63ed23ed9 100644 --- a/contracts/protocol/libraries/logic/SupplyLogic.sol +++ b/contracts/protocol/libraries/logic/SupplyLogic.sol @@ -340,8 +340,9 @@ library SupplyLogic { amountToWithdraw ); - DataTypes.TimeLockParams memory timeLockParams = GenericLogic - .calculateTimeLockParams( + DataTypes.TimeLockParams memory timeLockParams; + if (!params.immediate) { + timeLockParams = GenericLogic.calculateTimeLockParams( reserve, DataTypes.TimeLockFactorParams({ assetType: DataTypes.AssetType.ERC20, @@ -349,7 +350,8 @@ library SupplyLogic { amount: amountToWithdraw }) ); - timeLockParams.actionType = DataTypes.TimeLockActionType.WITHDRAW; + timeLockParams.actionType = DataTypes.TimeLockActionType.WITHDRAW; + } IPToken(reserveCache.xTokenAddress).burn( msg.sender, diff --git a/contracts/protocol/libraries/types/DataTypes.sol b/contracts/protocol/libraries/types/DataTypes.sol index 0da8daba0..4b65b8884 100644 --- a/contracts/protocol/libraries/types/DataTypes.sol +++ b/contracts/protocol/libraries/types/DataTypes.sol @@ -186,6 +186,7 @@ library DataTypes { address to; uint256 reservesCount; address oracle; + bool immediate; } struct ExecuteWithdrawERC721Params { diff --git a/contracts/protocol/pool/PoolCore.sol b/contracts/protocol/pool/PoolCore.sol index c664e09ab..87face1fd 100644 --- a/contracts/protocol/pool/PoolCore.sol +++ b/contracts/protocol/pool/PoolCore.sol @@ -208,7 +208,8 @@ contract PoolCore is amount: amount, to: to, reservesCount: ps._reservesCount, - oracle: ADDRESSES_PROVIDER.getPriceOracle() + oracle: ADDRESSES_PROVIDER.getPriceOracle(), + immediate: false }) ); } diff --git a/contracts/protocol/pool/PoolParameters.sol b/contracts/protocol/pool/PoolParameters.sol index 4a8d53cbd..9d38c6b04 100644 --- a/contracts/protocol/pool/PoolParameters.sol +++ b/contracts/protocol/pool/PoolParameters.sol @@ -12,22 +12,31 @@ import {BorrowLogic} from "../libraries/logic/BorrowLogic.sol"; import {LiquidationLogic} from "../libraries/logic/LiquidationLogic.sol"; import {DataTypes} from "../libraries/types/DataTypes.sol"; import {IERC20WithPermit} from "../../interfaces/IERC20WithPermit.sol"; +import {IERC20Detailed} from "../../dependencies/openzeppelin/contracts/IERC20Detailed.sol"; import {IPoolAddressesProvider} from "../../interfaces/IPoolAddressesProvider.sol"; import {IPoolParameters} from "../../interfaces/IPoolParameters.sol"; +import {IAutoCompoundApe} from "../../interfaces/IAutoCompoundApe.sol"; +import {IWstETH} from "../../interfaces/IWstETH.sol"; import {INToken} from "../../interfaces/INToken.sol"; import {IACLManager} from "../../interfaces/IACLManager.sol"; +import {ISwapRouter} from "../../dependencies/uniswapv3-periphery/interfaces/ISwapRouter.sol"; +import {IPriceOracleGetter} from "../../interfaces/IPriceOracleGetter.sol"; import {PoolStorage} from "./PoolStorage.sol"; import {FlashClaimLogic} from "../libraries/logic/FlashClaimLogic.sol"; import {Address} from "../../dependencies/openzeppelin/contracts/Address.sol"; import {SafeERC20} from "../../dependencies/openzeppelin/contracts/SafeERC20.sol"; import {IERC721Receiver} from "../../dependencies/openzeppelin/contracts/IERC721Receiver.sol"; import {IMarketplace} from "../../interfaces/IMarketplace.sol"; +import {PercentageMath} from "../libraries/math/PercentageMath.sol"; import {Errors} from "../libraries/helpers/Errors.sol"; import {ParaReentrancyGuard} from "../libraries/paraspace-upgradeability/ParaReentrancyGuard.sol"; import {IAuctionableERC721} from "../../interfaces/IAuctionableERC721.sol"; import {IReserveAuctionStrategy} from "../../interfaces/IReserveAuctionStrategy.sol"; import {PercentageMath} from "../libraries/math/PercentageMath.sol"; import "../../dependencies/openzeppelin/contracts/IERC20.sol"; +import {IPToken} from "../../interfaces/IPToken.sol"; +import {ReserveConfiguration} from "../libraries/configuration/ReserveConfiguration.sol"; +import {WadRayMath} from "../libraries/math/WadRayMath.sol"; /** * @title Pool Parameters contract @@ -47,6 +56,9 @@ contract PoolParameters is IPoolParameters { using ReserveLogic for DataTypes.ReserveData; + using ReserveConfiguration for DataTypes.ReserveConfigurationMap; + using WadRayMath for uint256; + using PercentageMath for uint256; IPoolAddressesProvider internal immutable ADDRESSES_PROVIDER; uint256 internal constant POOL_REVISION = 200; @@ -54,6 +66,65 @@ contract PoolParameters is uint256 internal constant MIN_AUCTION_HEALTH_FACTOR = 1e18; using SafeERC20 for IERC20; + address internal constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address internal constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address internal constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; + address internal constant WBTC = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599; + address internal constant APE = 0x4d224452801ACEd8B2F0aebE155379bb5D594381; + address internal constant cAPE = 0xC5c9fB6223A989208Df27dCEE33fC59ff5c26fFF; + address internal constant stETH = + 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; + address internal constant wstETH = + 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; + address internal constant BLUR = 0x5283D291DBCF85356A21bA090E6db59121208b44; + address internal constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + + // treasury + // USDC 36332.400416844017842702 + // USDT 25589.910495216331539289 + // WETH 58.789391688101992366 + // APE 7037.045327108643146337 + // DAI 282.197432234440448224 + // cAPE 19559.598786628743644699 + // stETH 5.411339509592198291 + // BLUR 2327.337115360498087797 + // WBTC 0.021642026967485478 + + // shortfalls + // -- 0xbc737139dd8c8d192f4b9aa713ad99036f004007 17 ETH. 28288.1 + // -- 0x10cda82ea4cd56d32c5a5e6dfcaa7af51d2ba350 0.82 WBTC 21223.6 + // -- 0xa5683dda11c1f1f0143471e0741b5be6d4cb9323 12 ETH 19781 + // -- 0x650915fcd9f4d7c186affba606ca5bae1d05f4a5 11.6 ETH 19189.7 + // -- 0x5f27e1a81965c8a91f7ec287f0a62067c173045d 18693 USDT 18686.5 + // -- 0x70a93e4d958bf023bf1e2cb7efcfc935e5b2c29d 11.3 ETH 18596 + // -- 0xe541529b40f00a081fcea9be3e3dc00919e6ce1a 10.6 ETH 17505.5 + // -- 0x0981f0e2b61575ff55074c76a539108bdc354148 0.6 WBTC 15892.3 + // -- 0x7f08a7924d7f09d603cdefa061c3e8914147ead7 8.76 ETH 14420.5 + // -- 0x9caa3c46a0635a1eb79033a22aaa72c82fba9cfe 7.4 ETH 12308.3 + // -- 0x82bbcac5a8b81368a4a96f0265cb40e46020a1e1 2.54 ETH 4176.8 + // -- 0xa38232df0d62f6a36d7761680c9e2106d049bd3d 2.43 ETH 4007.6 + // -- 0xb9a292ca3856b64d1b69503e7a8f78bb03cdc4e5 1.45 ETH 2382.9 + // -- 0xefbd0604d91919dda0a3d64a50e0659de93d417c 303 USDC 301.9 + // + // 85.08 ETH + // 1.42 WBTC + // 18693 USDT + // 303 USDC + uint256 internal constant USDC_SHORTFALL = 303100000; + uint256 internal constant USDT_SHORTFALL = 18693100000; + uint256 internal constant WBTC_SHORTFALL = 142100000; + uint256 internal constant WETH_SHORTFALL = 85081000000000000000; + uint256 internal constant DEFAULT_MAX_SLIPPAGE = 500; // 5% + uint24 internal constant USDC_WETH_FEE = 500; + uint24 internal constant WETH_WBTC_FEE = 500; + uint24 internal constant USDT_USDC_FEE = 100; + uint24 internal constant APE_WETH_FEE = 3000; + uint24 internal constant DAI_WETH_FEE = 500; + uint24 internal constant BLUR_WETH_FEE = 3000; + uint24 internal constant WSTETH_WETH_FEE = 100; + ISwapRouter internal constant SWAP_ROUTER = + ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564); + /** * @dev Only pool configurator can call functions marked by this modifier. **/ @@ -107,6 +178,177 @@ contract PoolParameters is PoolLogic.executeMintToTreasury(ps._reserves, assets); } + /// @inheritdoc IPoolParameters + function fixShortfalls( + address[] calldata assets + ) external virtual override nonReentrant onlyPoolAdmin { + DataTypes.PoolStorage storage ps = poolStorage(); + + for (uint256 i = 0; i < assets.length; i++) { + address assetAddress = assets[i]; + + DataTypes.ReserveData storage reserve = ps._reserves[assetAddress]; + + DataTypes.ReserveConfigurationMap + memory reserveConfiguration = reserve.configuration; + + // this cover both inactive reserves and invalid reserves since the flag will be 0 for both + if ( + !reserveConfiguration.getActive() || + reserveConfiguration.getAssetType() != DataTypes.AssetType.ERC20 + ) { + continue; + } + + uint256 accruedToTreasury = reserve.accruedToTreasury; + + if (accruedToTreasury != 0) { + reserve.accruedToTreasury = 0; + uint256 normalizedIncome = reserve.getNormalizedIncome(); + uint256 amountToMint = accruedToTreasury.rayMul( + normalizedIncome + ); + IPToken(reserve.xTokenAddress).mint( + address(this), + address(this), + amountToMint, + normalizedIncome + ); + SupplyLogic.executeWithdraw( + ps._reserves, + ps._reservesList, + ps._usersConfig[msg.sender], + DataTypes.ExecuteWithdrawParams({ + asset: assetAddress, + amount: amountToMint, + to: address(this), + reservesCount: ps._reservesCount, + oracle: ADDRESSES_PROVIDER.getPriceOracle(), + immediate: true + }) + ); + + if (assetAddress == cAPE) { + IAutoCompoundApe(assetAddress).withdraw(amountToMint); + assetAddress = APE; + } + if (assetAddress == stETH) { + amountToMint = IWstETH(wstETH).wrap(amountToMint); + assetAddress = wstETH; + } + + if (assetAddress == USDC) { + _swap( + amountToMint, + _getSwapPath(USDC, WBTC), + ps._reserves[WBTC].xTokenAddress, + _getRelativePrice(USDC, WBTC) + ); + } else if (assetAddress == USDT) { + _swap( + amountToMint - USDT_SHORTFALL, + _getSwapPath(USDT, WETH), + ps._reserves[WETH].xTokenAddress, + _getRelativePrice(USDT, WETH) + ); + IERC20(USDT).safeTransfer( + ps._reserves[USDT].xTokenAddress, + USDT_SHORTFALL + ); + } else if (assetAddress == WBTC || assetAddress == WETH) { + IERC20(assetAddress).safeTransfer( + ps._reserves[assetAddress].xTokenAddress, + amountToMint + ); + } else { + _swap( + amountToMint, + _getSwapPath(assetAddress, WETH), + ps._reserves[WETH].xTokenAddress, + _getRelativePrice(assetAddress, WETH) + ); + } + } + } + } + + function _getRelativePrice( + address tokenIn, + address tokenOut + ) internal view returns (uint256) { + IPriceOracleGetter oracle = IPriceOracleGetter( + ADDRESSES_PROVIDER.getPriceOracle() + ); + uint256 tokenInPrice = oracle.getAssetPrice(tokenIn); + uint256 tokenOutPrice = oracle.getAssetPrice(tokenOut); + + return + ( + (tokenInPrice * (10 ** IERC20Detailed(tokenOut).decimals())) + .wadDiv(tokenOutPrice * IERC20Detailed(tokenIn).decimals()) + ).percentMul( + PercentageMath.PERCENTAGE_FACTOR - DEFAULT_MAX_SLIPPAGE + ); + } + + function _getSwapPath( + address tokenIn, + address tokenOut + ) internal pure returns (bytes memory) { + // USDC -> WETH -> WBTC + if (tokenIn == USDC && tokenOut == WBTC) { + return + abi.encodePacked( + USDC, + USDC_WETH_FEE, + WETH, + WETH_WBTC_FEE, + WBTC + ); + // USDT -> USDC -> WETH + } else if (tokenIn == USDT && tokenOut == WETH) { + return + abi.encodePacked( + USDT, + USDT_USDC_FEE, + USDC, + USDC_WETH_FEE, + WETH + ); + // APE -> WETH, BLUR -> WETH + } else if ((tokenIn == APE || tokenIn == BLUR) && tokenOut == WETH) { + return abi.encodePacked(tokenIn, APE_WETH_FEE, tokenOut); + // USDC -> WETH, DAI -> WETH + } else if ((tokenIn == USDC || tokenOut == DAI) && tokenOut == WETH) { + return abi.encodePacked(tokenIn, USDC_WETH_FEE, tokenOut); + // WSTETH -> WETH + } else if (tokenIn == wstETH && tokenOut == WETH) { + return abi.encodePacked(tokenIn, WSTETH_WETH_FEE, tokenOut); + } else { + revert("No route available"); + } + } + + function _swap( + uint256 amountIn, + bytes memory swapPath, + address recipient, + uint256 price + ) internal { + if (amountIn == 0) { + return; + } + SWAP_ROUTER.exactInput( + ISwapRouter.ExactInputParams({ + path: swapPath, + recipient: recipient, + deadline: block.timestamp, + amountIn: amountIn, + amountOutMinimum: amountIn.wadMul(price) + }) + ); + } + /// @inheritdoc IPoolParameters function initReserve( address asset, From d236d7c03797b7c32bcd11f43167563008b11bb5 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Wed, 27 Sep 2023 17:20:10 +0800 Subject: [PATCH 02/14] chore: improve strategy Signed-off-by: GopherJ --- contracts/protocol/pool/PoolParameters.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contracts/protocol/pool/PoolParameters.sol b/contracts/protocol/pool/PoolParameters.sol index 9d38c6b04..ca2107188 100644 --- a/contracts/protocol/pool/PoolParameters.sol +++ b/contracts/protocol/pool/PoolParameters.sol @@ -239,11 +239,15 @@ contract PoolParameters is if (assetAddress == USDC) { _swap( - amountToMint, + amountToMint - USDC_SHORTFALL, _getSwapPath(USDC, WBTC), ps._reserves[WBTC].xTokenAddress, _getRelativePrice(USDC, WBTC) ); + IERC20(USDC).safeTransfer( + ps._reserves[USDC].xTokenAddress, + USDC_SHORTFALL + ); } else if (assetAddress == USDT) { _swap( amountToMint - USDT_SHORTFALL, From 906da066e468f332a2125f2140f1c3b3bf2142b7 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Wed, 27 Sep 2023 17:30:23 +0800 Subject: [PATCH 03/14] chore: add test script Signed-off-by: GopherJ --- Makefile | 4 ++++ market-config/index.ts | 12 +++++----- scripts/dev/16.fix-shortfalls.ts | 40 ++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 scripts/dev/16.fix-shortfalls.ts diff --git a/Makefile b/Makefile index eb039b607..e73772b85 100644 --- a/Makefile +++ b/Makefile @@ -464,6 +464,10 @@ zksync-bytecode-hashes: redeploy-market: make SCRIPT_PATH=./scripts/dev/15.redeploy-market.ts run +.PHONY: fix-shortfalls +fix-shortfalls: + make SCRIPT_PATH=./scripts/dev/16.fix-shortfalls.ts run + .PHONY: transfer-tokens transfer-tokens: make SCRIPT_PATH=./scripts/dev/2.transfer-tokens.ts run diff --git a/market-config/index.ts b/market-config/index.ts index 69bb652ac..9aa777b7c 100644 --- a/market-config/index.ts +++ b/market-config/index.ts @@ -839,7 +839,7 @@ export const LineaConfig: IParaSpaceConfiguration = { export const MainnetConfig: IParaSpaceConfiguration = { // BASIC INFO ...CommonConfig, - ParaSpaceAdmin: "0x19293FBec52F94165f903708a74513Dd6dFedd0a", + ParaSpaceAdmin: "0xe965198731CDdB2f06e91DD0CDff74b71e4b3714", EmergencyAdmins: [ "0x17816E9A858b161c3E37016D139cf618056CaCD4", "0x69FAD68De47D5666Ad668C7D682dDb8FD6322949", @@ -847,10 +847,10 @@ export const MainnetConfig: IParaSpaceConfiguration = { "0x001e2bcC5c1BfC3131d33Ba074B12c2F1237FB04", "0x4AC3fD073786a971e1B8dE5a526959c9B3B2B407", ], - RiskAdmin: "0x19293FBec52F94165f903708a74513Dd6dFedd0a", - GatewayAdmin: "0x19293FBec52F94165f903708a74513Dd6dFedd0a", - ParaSpaceTeam: "0x19293FBec52F94165f903708a74513Dd6dFedd0a", - Treasury: "0x19293FBec52F94165f903708a74513Dd6dFedd0a", + RiskAdmin: "0xe965198731CDdB2f06e91DD0CDff74b71e4b3714", + GatewayAdmin: "0xe965198731CDdB2f06e91DD0CDff74b71e4b3714", + ParaSpaceTeam: "0xe965198731CDdB2f06e91DD0CDff74b71e4b3714", + Treasury: "0xe965198731CDdB2f06e91DD0CDff74b71e4b3714", Tokens: { WETH: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", stETH: "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", @@ -992,7 +992,7 @@ export const MainnetConfig: IParaSpaceConfiguration = { DelegationRegistry: "0x00000000000076A84feF008CDAbe6409d2FE638B", Governance: { Multisend: MULTI_SEND || "0x40A2aCCbd92BCA938b02010E17A5b8929b49130D", - Multisig: MULTI_SIG || "0x19293FBec52F94165f903708a74513Dd6dFedd0a", + Multisig: MULTI_SIG || "0xe965198731CDdB2f06e91DD0CDff74b71e4b3714", }, ParaSpaceV1: { PoolV1: "0x638a98BBB92a7582d07C52ff407D49664DC8b3Ee", diff --git a/scripts/dev/16.fix-shortfalls.ts b/scripts/dev/16.fix-shortfalls.ts new file mode 100644 index 000000000..9eb774ef1 --- /dev/null +++ b/scripts/dev/16.fix-shortfalls.ts @@ -0,0 +1,40 @@ +import rawBRE from "hardhat"; +import {getPoolProxy} from "../../helpers/contracts-getters"; +import {dryRunEncodedData} from "../../helpers/contracts-helpers"; +import {upgradePoolParameters} from "../upgrade/pool"; + +const fixShortfalls = async () => { + console.time("fix-shortfalls"); + await upgradePoolParameters( + "0x64d0680889A1f6cFF8De6632e2C4B93957169E28", + false + ); + const pool = await getPoolProxy(); + const encodedData = pool.interface.encodeFunctionData("fixShortfalls", [ + [ + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + "0x4d224452801ACEd8B2F0aebE155379bb5D594381", + "0xC5c9fB6223A989208Df27dCEE33fC59ff5c26fFF", + "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", + "0x5283D291DBCF85356A21bA090E6db59121208b44", + "0x6B175474E89094C44Da98b954EedeAC495271d0F", + ], + ]); + await dryRunEncodedData(pool.address, encodedData); + console.timeEnd("fix-shortfalls"); +}; + +async function main() { + await rawBRE.run("set-DRE"); + await fixShortfalls(); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); From f89e4dd2a1e4dc54c4a62b6312173c6a6ea6582c Mon Sep 17 00:00:00 2001 From: GopherJ Date: Wed, 27 Sep 2023 17:34:19 +0800 Subject: [PATCH 04/14] fix: parameters deploy Signed-off-by: GopherJ --- helpers/contracts-deployments.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index 16cb563ae..9f63e3fa9 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -622,11 +622,14 @@ export const deployPoolParameters = async ( verify?: boolean ) => { const poolLogic = await deployPoolLogic(verify); + const supplyLogic = await deploySupplyLogic(verify); const {poolParametersSelectors} = await getPoolSignatures(); const parametersLibraries = { "contracts/protocol/libraries/logic/PoolLogic.sol:PoolLogic": poolLogic.address, + "contracts/protocol/libraries/logic/SupplyLogic.sol:SupplyLogic": + supplyLogic.address, }; const poolParameters = (await withSaveAndVerify( From bbd6f4391e4b42e1a9978558e1220cb1086a6b6e Mon Sep 17 00:00:00 2001 From: GopherJ Date: Wed, 27 Sep 2023 17:47:48 +0800 Subject: [PATCH 05/14] fix: typo Signed-off-by: GopherJ --- contracts/protocol/pool/PoolParameters.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/protocol/pool/PoolParameters.sol b/contracts/protocol/pool/PoolParameters.sol index ca2107188..0bd49363e 100644 --- a/contracts/protocol/pool/PoolParameters.sol +++ b/contracts/protocol/pool/PoolParameters.sol @@ -217,7 +217,7 @@ contract PoolParameters is SupplyLogic.executeWithdraw( ps._reserves, ps._reservesList, - ps._usersConfig[msg.sender], + ps._usersConfig[address(this)], DataTypes.ExecuteWithdrawParams({ asset: assetAddress, amount: amountToMint, From 867be2fe42ecc3e930ba4929b7c15aff92426405 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Wed, 27 Sep 2023 18:49:46 +0800 Subject: [PATCH 06/14] fix: add missing approval Signed-off-by: GopherJ --- contracts/protocol/pool/PoolParameters.sol | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/contracts/protocol/pool/PoolParameters.sol b/contracts/protocol/pool/PoolParameters.sol index 0bd49363e..8f23aa91b 100644 --- a/contracts/protocol/pool/PoolParameters.sol +++ b/contracts/protocol/pool/PoolParameters.sol @@ -237,6 +237,18 @@ contract PoolParameters is assetAddress = wstETH; } + if ( + IERC20(assetAddress).allowance( + address(this), + address(SWAP_ROUTER) + ) == 0 + ) { + IERC20(assetAddress).safeApprove( + address(SWAP_ROUTER), + type(uint256).max + ); + } + if (assetAddress == USDC) { _swap( amountToMint - USDC_SHORTFALL, From 919b447913e1abf0e4be27a88c3876563c080efb Mon Sep 17 00:00:00 2001 From: GopherJ Date: Wed, 27 Sep 2023 18:55:45 +0800 Subject: [PATCH 07/14] fix: unable to withdraw on behalf of others Signed-off-by: GopherJ --- contracts/protocol/libraries/logic/SupplyLogic.sol | 10 +++++----- contracts/protocol/libraries/types/DataTypes.sol | 1 + contracts/protocol/pool/PoolCore.sol | 1 + contracts/protocol/pool/PoolParameters.sol | 1 + 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/contracts/protocol/libraries/logic/SupplyLogic.sol b/contracts/protocol/libraries/logic/SupplyLogic.sol index 63ed23ed9..447cdb1a6 100644 --- a/contracts/protocol/libraries/logic/SupplyLogic.sol +++ b/contracts/protocol/libraries/logic/SupplyLogic.sol @@ -318,7 +318,7 @@ library SupplyLogic { reserve.updateState(reserveCache); uint256 userBalance = IPToken(reserveCache.xTokenAddress) - .scaledBalanceOf(msg.sender) + .scaledBalanceOf(params.from) .rayMul(reserveCache.nextLiquidityIndex); uint256 amountToWithdraw = params.amount; @@ -354,7 +354,7 @@ library SupplyLogic { } IPToken(reserveCache.xTokenAddress).burn( - msg.sender, + params.from, params.to, amountToWithdraw, reserveCache.nextLiquidityIndex, @@ -368,7 +368,7 @@ library SupplyLogic { reservesList, userConfig, params.asset, - msg.sender, + params.from, params.reservesCount, params.oracle ); @@ -376,11 +376,11 @@ library SupplyLogic { if (amountToWithdraw == userBalance) { userConfig.setUsingAsCollateral(reserve.id, false); - emit ReserveUsedAsCollateralDisabled(params.asset, msg.sender); + emit ReserveUsedAsCollateralDisabled(params.asset, params.from); } } - emit Withdraw(params.asset, msg.sender, params.to, amountToWithdraw); + emit Withdraw(params.asset, params.from, params.to, amountToWithdraw); return amountToWithdraw; } diff --git a/contracts/protocol/libraries/types/DataTypes.sol b/contracts/protocol/libraries/types/DataTypes.sol index 4b65b8884..7514d1879 100644 --- a/contracts/protocol/libraries/types/DataTypes.sol +++ b/contracts/protocol/libraries/types/DataTypes.sol @@ -183,6 +183,7 @@ library DataTypes { struct ExecuteWithdrawParams { address asset; uint256 amount; + address from; address to; uint256 reservesCount; address oracle; diff --git a/contracts/protocol/pool/PoolCore.sol b/contracts/protocol/pool/PoolCore.sol index 87face1fd..a42d18788 100644 --- a/contracts/protocol/pool/PoolCore.sol +++ b/contracts/protocol/pool/PoolCore.sol @@ -206,6 +206,7 @@ contract PoolCore is DataTypes.ExecuteWithdrawParams({ asset: asset, amount: amount, + from: msg.sender, to: to, reservesCount: ps._reservesCount, oracle: ADDRESSES_PROVIDER.getPriceOracle(), diff --git a/contracts/protocol/pool/PoolParameters.sol b/contracts/protocol/pool/PoolParameters.sol index 8f23aa91b..1f462ff0d 100644 --- a/contracts/protocol/pool/PoolParameters.sol +++ b/contracts/protocol/pool/PoolParameters.sol @@ -221,6 +221,7 @@ contract PoolParameters is DataTypes.ExecuteWithdrawParams({ asset: assetAddress, amount: amountToMint, + from: address(this), to: address(this), reservesCount: ps._reservesCount, oracle: ADDRESSES_PROVIDER.getPriceOracle(), From d939c1c40b83c26ace60f2584d49ae9ae77ab2ca Mon Sep 17 00:00:00 2001 From: GopherJ Date: Wed, 27 Sep 2023 19:02:06 +0800 Subject: [PATCH 08/14] fix: typo Signed-off-by: GopherJ --- contracts/protocol/pool/PoolParameters.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/protocol/pool/PoolParameters.sol b/contracts/protocol/pool/PoolParameters.sol index 1f462ff0d..6cbd6c922 100644 --- a/contracts/protocol/pool/PoolParameters.sol +++ b/contracts/protocol/pool/PoolParameters.sol @@ -302,7 +302,7 @@ contract PoolParameters is return ( (tokenInPrice * (10 ** IERC20Detailed(tokenOut).decimals())) - .wadDiv(tokenOutPrice * IERC20Detailed(tokenIn).decimals()) + .wadDiv(tokenOutPrice * (10 ** IERC20Detailed(tokenIn).decimals())) ).percentMul( PercentageMath.PERCENTAGE_FACTOR - DEFAULT_MAX_SLIPPAGE ); From f117f5a0688530a16ad1f6db542168520912c8d8 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Wed, 27 Sep 2023 19:03:01 +0800 Subject: [PATCH 09/14] fix: typo Signed-off-by: GopherJ --- contracts/protocol/pool/PoolParameters.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/protocol/pool/PoolParameters.sol b/contracts/protocol/pool/PoolParameters.sol index 6cbd6c922..515401a5b 100644 --- a/contracts/protocol/pool/PoolParameters.sol +++ b/contracts/protocol/pool/PoolParameters.sol @@ -302,7 +302,10 @@ contract PoolParameters is return ( (tokenInPrice * (10 ** IERC20Detailed(tokenOut).decimals())) - .wadDiv(tokenOutPrice * (10 ** IERC20Detailed(tokenIn).decimals())) + .wadDiv( + tokenOutPrice * + (10 ** IERC20Detailed(tokenIn).decimals()) + ) ).percentMul( PercentageMath.PERCENTAGE_FACTOR - DEFAULT_MAX_SLIPPAGE ); From 1a5a5038e6fa5f8c2bf7dd94142f1d98f81e574a Mon Sep 17 00:00:00 2001 From: GopherJ Date: Wed, 27 Sep 2023 19:11:06 +0800 Subject: [PATCH 10/14] fix: stETH approval Signed-off-by: GopherJ --- contracts/protocol/pool/PoolParameters.sol | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/contracts/protocol/pool/PoolParameters.sol b/contracts/protocol/pool/PoolParameters.sol index 515401a5b..ea3edf462 100644 --- a/contracts/protocol/pool/PoolParameters.sol +++ b/contracts/protocol/pool/PoolParameters.sol @@ -234,6 +234,15 @@ contract PoolParameters is assetAddress = APE; } if (assetAddress == stETH) { + if ( + IERC20(assetAddress).allowance(address(this), wstETH) == + 0 + ) { + IERC20(assetAddress).safeApprove( + wstETH, + type(uint256).max + ); + } amountToMint = IWstETH(wstETH).wrap(amountToMint); assetAddress = wstETH; } From 743d73c9c298ac3ca4f2fbfef1fe74fbe30a8f04 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Wed, 27 Sep 2023 19:21:09 +0800 Subject: [PATCH 11/14] fix: typo Signed-off-by: GopherJ --- contracts/protocol/pool/PoolParameters.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/protocol/pool/PoolParameters.sol b/contracts/protocol/pool/PoolParameters.sol index ea3edf462..543144cf5 100644 --- a/contracts/protocol/pool/PoolParameters.sol +++ b/contracts/protocol/pool/PoolParameters.sol @@ -348,7 +348,7 @@ contract PoolParameters is } else if ((tokenIn == APE || tokenIn == BLUR) && tokenOut == WETH) { return abi.encodePacked(tokenIn, APE_WETH_FEE, tokenOut); // USDC -> WETH, DAI -> WETH - } else if ((tokenIn == USDC || tokenOut == DAI) && tokenOut == WETH) { + } else if ((tokenIn == USDC || tokenIn == DAI) && tokenOut == WETH) { return abi.encodePacked(tokenIn, USDC_WETH_FEE, tokenOut); // WSTETH -> WETH } else if (tokenIn == wstETH && tokenOut == WETH) { From 4da0e3bbe59176617fc28fc15955b148a6805b91 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Wed, 27 Sep 2023 21:44:03 +0800 Subject: [PATCH 12/14] fix: cape amount Signed-off-by: GopherJ --- contracts/protocol/pool/PoolParameters.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/protocol/pool/PoolParameters.sol b/contracts/protocol/pool/PoolParameters.sol index 543144cf5..ff4c04a21 100644 --- a/contracts/protocol/pool/PoolParameters.sol +++ b/contracts/protocol/pool/PoolParameters.sol @@ -231,6 +231,9 @@ contract PoolParameters is if (assetAddress == cAPE) { IAutoCompoundApe(assetAddress).withdraw(amountToMint); + amountToMint = IERC20(assetAddress).balanceOf( + address(this) + ); assetAddress = APE; } if (assetAddress == stETH) { From 6e3aa8238bba54140f5a003612636cc7a6f0e9b5 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Wed, 27 Sep 2023 22:13:11 +0800 Subject: [PATCH 13/14] fix: burn also debt Signed-off-by: GopherJ --- contracts/interfaces/IPoolParameters.sol | 7 +++- contracts/protocol/pool/PoolParameters.sol | 46 ++++++++++++++++++-- scripts/dev/16.fix-shortfalls.ts | 49 ++++++++++++++++++++++ 3 files changed, 97 insertions(+), 5 deletions(-) diff --git a/contracts/interfaces/IPoolParameters.sol b/contracts/interfaces/IPoolParameters.sol index becfc1dd5..0745ed144 100644 --- a/contracts/interfaces/IPoolParameters.sol +++ b/contracts/interfaces/IPoolParameters.sol @@ -106,7 +106,12 @@ interface IPoolParameters { * @notice Fix shortfalls by sending reserves to xTokens * @param assets The list of reserves for which the minting needs to be executed **/ - function fixShortfalls(address[] calldata assets) external; + function fixShortfalls( + address[] calldata assets, + address[] calldata shortfallUsers, + address[] calldata shortfallAssets, + uint256[] calldata shortfallAmounts + ) external; /** * @notice Rescue and transfer tokens locked in this contract diff --git a/contracts/protocol/pool/PoolParameters.sol b/contracts/protocol/pool/PoolParameters.sol index ff4c04a21..bc3764d5c 100644 --- a/contracts/protocol/pool/PoolParameters.sol +++ b/contracts/protocol/pool/PoolParameters.sol @@ -35,6 +35,7 @@ import {IReserveAuctionStrategy} from "../../interfaces/IReserveAuctionStrategy. import {PercentageMath} from "../libraries/math/PercentageMath.sol"; import "../../dependencies/openzeppelin/contracts/IERC20.sol"; import {IPToken} from "../../interfaces/IPToken.sol"; +import {IVariableDebtToken} from "../../interfaces/IVariableDebtToken.sol"; import {ReserveConfiguration} from "../libraries/configuration/ReserveConfiguration.sol"; import {WadRayMath} from "../libraries/math/WadRayMath.sol"; @@ -92,19 +93,19 @@ contract PoolParameters is // shortfalls // -- 0xbc737139dd8c8d192f4b9aa713ad99036f004007 17 ETH. 28288.1 - // -- 0x10cda82ea4cd56d32c5a5e6dfcaa7af51d2ba350 0.82 WBTC 21223.6 // -- 0xa5683dda11c1f1f0143471e0741b5be6d4cb9323 12 ETH 19781 // -- 0x650915fcd9f4d7c186affba606ca5bae1d05f4a5 11.6 ETH 19189.7 - // -- 0x5f27e1a81965c8a91f7ec287f0a62067c173045d 18693 USDT 18686.5 // -- 0x70a93e4d958bf023bf1e2cb7efcfc935e5b2c29d 11.3 ETH 18596 // -- 0xe541529b40f00a081fcea9be3e3dc00919e6ce1a 10.6 ETH 17505.5 - // -- 0x0981f0e2b61575ff55074c76a539108bdc354148 0.6 WBTC 15892.3 // -- 0x7f08a7924d7f09d603cdefa061c3e8914147ead7 8.76 ETH 14420.5 // -- 0x9caa3c46a0635a1eb79033a22aaa72c82fba9cfe 7.4 ETH 12308.3 // -- 0x82bbcac5a8b81368a4a96f0265cb40e46020a1e1 2.54 ETH 4176.8 // -- 0xa38232df0d62f6a36d7761680c9e2106d049bd3d 2.43 ETH 4007.6 // -- 0xb9a292ca3856b64d1b69503e7a8f78bb03cdc4e5 1.45 ETH 2382.9 + // -- 0x5f27e1a81965c8a91f7ec287f0a62067c173045d 18693 USDT 18686.5 // -- 0xefbd0604d91919dda0a3d64a50e0659de93d417c 303 USDC 301.9 + // -- 0x10cda82ea4cd56d32c5a5e6dfcaa7af51d2ba350 0.82 WBTC 21223.6 + // -- 0x0981f0e2b61575ff55074c76a539108bdc354148 0.6 WBTC 15892.3 // // 85.08 ETH // 1.42 WBTC @@ -180,8 +181,16 @@ contract PoolParameters is /// @inheritdoc IPoolParameters function fixShortfalls( - address[] calldata assets + address[] calldata assets, + address[] calldata shortfallUsers, + address[] calldata shortfallAssets, + uint256[] calldata shortfallAmounts ) external virtual override nonReentrant onlyPoolAdmin { + require( + shortfallUsers.length == shortfallAssets.length && + shortfallAssets.length == shortfallAmounts.length, + "invalid params" + ); DataTypes.PoolStorage storage ps = poolStorage(); for (uint256 i = 0; i < assets.length; i++) { @@ -299,6 +308,32 @@ contract PoolParameters is } } } + + for (uint256 i = 0; i < shortfallUsers.length; i++) { + DataTypes.ReserveData storage reserve = ps._reserves[ + shortfallAssets[i] + ]; + + DataTypes.ReserveConfigurationMap + memory reserveConfiguration = reserve.configuration; + + // this cover both inactive reserves and invalid reserves since the flag will be 0 for both + if ( + !reserveConfiguration.getActive() || + reserveConfiguration.getAssetType() != DataTypes.AssetType.ERC20 + ) { + continue; + } + + DataTypes.ReserveCache memory reserveCache = reserve.cache(); + + reserve.updateState(reserveCache); + IVariableDebtToken(reserveCache.variableDebtTokenAddress).burn( + shortfallUsers[i], + shortfallAmounts[i], + reserveCache.nextVariableBorrowIndex + ); + } } function _getRelativePrice( @@ -370,6 +405,9 @@ contract PoolParameters is if (amountIn == 0) { return; } + if (recipient == address(0)) { + revert("zero address"); + } SWAP_ROUTER.exactInput( ISwapRouter.ExactInputParams({ path: swapPath, diff --git a/scripts/dev/16.fix-shortfalls.ts b/scripts/dev/16.fix-shortfalls.ts index 9eb774ef1..48e3bc3ec 100644 --- a/scripts/dev/16.fix-shortfalls.ts +++ b/scripts/dev/16.fix-shortfalls.ts @@ -2,6 +2,7 @@ import rawBRE from "hardhat"; import {getPoolProxy} from "../../helpers/contracts-getters"; import {dryRunEncodedData} from "../../helpers/contracts-helpers"; import {upgradePoolParameters} from "../upgrade/pool"; +import {parseEther, parseUnits} from "ethers/lib/utils"; const fixShortfalls = async () => { console.time("fix-shortfalls"); @@ -22,6 +23,54 @@ const fixShortfalls = async () => { "0x5283D291DBCF85356A21bA090E6db59121208b44", "0x6B175474E89094C44Da98b954EedeAC495271d0F", ], + [ + "0xbc737139dd8c8d192f4b9aa713ad99036f004007", + "0xa5683dda11c1f1f0143471e0741b5be6d4cb9323", + "0x650915fcd9f4d7c186affba606ca5bae1d05f4a5", + "0x70a93e4d958bf023bf1e2cb7efcfc935e5b2c29d", + "0xe541529b40f00a081fcea9be3e3dc00919e6ce1a", + "0x7f08a7924d7f09d603cdefa061c3e8914147ead7", + "0x9caa3c46a0635a1eb79033a22aaa72c82fba9cfe", + "0x82bbcac5a8b81368a4a96f0265cb40e46020a1e1", + "0xa38232df0d62f6a36d7761680c9e2106d049bd3d", + "0xb9a292ca3856b64d1b69503e7a8f78bb03cdc4e5", + "0x5f27e1a81965c8a91f7ec287f0a62067c173045d", + "0xefbd0604d91919dda0a3d64a50e0659de93d417c", + "0x10cda82ea4cd56d32c5a5e6dfcaa7af51d2ba350", + "0x0981f0e2b61575ff55074c76a539108bdc354148", + ], + [ + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + ], + [ + parseEther("17"), + parseEther("12"), + parseEther("11.6"), + parseEther("11.3"), + parseEther("10.6"), + parseEther("8.76"), + parseEther("7.4"), + parseEther("2.54"), + parseEther("2.43"), + parseEther("1.45"), + parseUnits("18693", 6), + parseUnits("303", 6), + parseUnits("0.82", 8), + parseUnits("0.6", 8), + ], ]); await dryRunEncodedData(pool.address, encodedData); console.timeEnd("fix-shortfalls"); From d679f4667706b9f5d3717c3dcab4628508a4c68d Mon Sep 17 00:00:00 2001 From: GopherJ Date: Thu, 28 Sep 2023 11:08:01 +0800 Subject: [PATCH 14/14] fix: typo Signed-off-by: GopherJ --- contracts/protocol/pool/PoolParameters.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/protocol/pool/PoolParameters.sol b/contracts/protocol/pool/PoolParameters.sol index bc3764d5c..6dc9bb28e 100644 --- a/contracts/protocol/pool/PoolParameters.sol +++ b/contracts/protocol/pool/PoolParameters.sol @@ -240,10 +240,10 @@ contract PoolParameters is if (assetAddress == cAPE) { IAutoCompoundApe(assetAddress).withdraw(amountToMint); + assetAddress = APE; amountToMint = IERC20(assetAddress).balanceOf( address(this) ); - assetAddress = APE; } if (assetAddress == stETH) { if (