From c90ca06f3c155cb8863f1bf1ff014b87cd00db5a Mon Sep 17 00:00:00 2001 From: Mario Date: Thu, 13 Nov 2025 14:55:20 +0100 Subject: [PATCH 1/2] CR fix. Spread apropriate for pool. --- contracts/amm/libraries/types/AmmInternalTypes.sol | 2 +- .../chains/ethereum/amm-commons/AmmCloseSwapLens.sol | 6 ++---- contracts/interfaces/ILiquidityMiningLens.sol | 2 +- test/utils/factory/IporProtocolFactory.sol | 12 ++++-------- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/contracts/amm/libraries/types/AmmInternalTypes.sol b/contracts/amm/libraries/types/AmmInternalTypes.sol index 884f537e5..f53ed1350 100644 --- a/contracts/amm/libraries/types/AmmInternalTypes.sol +++ b/contracts/amm/libraries/types/AmmInternalTypes.sol @@ -68,7 +68,7 @@ library AmmInternalTypes { struct OpenSwapItem { /// @notice Swap ID uint32 swapId; - /// @notcie Next swap ID in linked list + /// @notice Next swap ID in linked list uint32 nextSwapId; /// @notice Previous swap ID in linked list uint32 previousSwapId; diff --git a/contracts/chains/ethereum/amm-commons/AmmCloseSwapLens.sol b/contracts/chains/ethereum/amm-commons/AmmCloseSwapLens.sol index 89cafcbec..93e9ad7d2 100644 --- a/contracts/chains/ethereum/amm-commons/AmmCloseSwapLens.sol +++ b/contracts/chains/ethereum/amm-commons/AmmCloseSwapLens.sol @@ -24,12 +24,10 @@ contract AmmCloseSwapLens is IAmmCloseSwapLens { address public immutable iporOracle; address public immutable messageSigner; - address public immutable spreadRouter; - constructor(address iporOracle_, address messageSigner_, address spreadRouter_) { + constructor(address iporOracle_, address messageSigner_) { iporOracle = iporOracle_.checkAddress(); messageSigner = messageSigner_.checkAddress(); - spreadRouter = spreadRouter_.checkAddress(); } function getAmmCloseSwapServicePoolConfiguration( @@ -134,7 +132,7 @@ contract AmmCloseSwapLens is IAmmCloseSwapLens { ) = SwapCloseLogicLib.calculateSwapUnwindWhenUnwindRequired( AmmTypes.UnwindParams({ messageSigner: messageSigner, - spreadRouter: spreadRouter, + spreadRouter: poolCfg.spread, ammStorage: poolCfg.ammStorage, ammTreasury: poolCfg.ammTreasury, direction: direction, diff --git a/contracts/interfaces/ILiquidityMiningLens.sol b/contracts/interfaces/ILiquidityMiningLens.sol index 0babe1e21..45c880fb3 100644 --- a/contracts/interfaces/ILiquidityMiningLens.sol +++ b/contracts/interfaces/ILiquidityMiningLens.sol @@ -41,7 +41,7 @@ interface ILiquidityMiningLens { uint128 compositeMultiplierCumulativePrevBlock; /// @notice lpToken account's balance uint128 lpTokenBalance; - /// @notive PowerUp is a result of logarithmic equastion, + /// @notice PowerUp is a result of logarithmic equastion, /// @dev powerUp < 100 *10^18 uint72 powerUp; /// @notice balance of Power Tokens delegated to LiquidityMining diff --git a/test/utils/factory/IporProtocolFactory.sol b/test/utils/factory/IporProtocolFactory.sol index 5b08d6b78..211391496 100644 --- a/test/utils/factory/IporProtocolFactory.sol +++ b/test/utils/factory/IporProtocolFactory.sol @@ -785,8 +785,7 @@ contract IporProtocolFactory is Test { deployerContracts.ammCloseSwapLens = address( new AmmCloseSwapLens({ iporOracle_: address(amm.iporOracle), - messageSigner_: messageSignerAddress, - spreadRouter_: address(amm.spreadRouter) + messageSigner_: messageSignerAddress }) ); @@ -1019,8 +1018,7 @@ contract IporProtocolFactory is Test { deployerContracts.ammCloseSwapLens = address( new AmmCloseSwapLens({ iporOracle_: address(iporProtocol.iporOracle), - messageSigner_: messageSignerAddress, - spreadRouter_: address(iporProtocol.spreadRouter) + messageSigner_: messageSignerAddress }) ); @@ -1215,8 +1213,7 @@ contract IporProtocolFactory is Test { deployerContracts.ammCloseSwapLens = address( new AmmCloseSwapLens({ iporOracle_: address(iporProtocol.iporOracle), - messageSigner_: messageSignerAddress, - spreadRouter_: address(iporProtocol.spreadRouter) + messageSigner_: messageSignerAddress }) ); @@ -1413,8 +1410,7 @@ contract IporProtocolFactory is Test { deployerContracts.ammCloseSwapLens = address( new AmmCloseSwapLens({ iporOracle_: address(iporProtocol.iporOracle), - messageSigner_: messageSignerAddress, - spreadRouter_: address(iporProtocol.spreadRouter) + messageSigner_: messageSignerAddress }) ); From 95cb5ded6e424ae696d80cb69d1e15f123aa8087 Mon Sep 17 00:00:00 2001 From: Mario Date: Thu, 13 Nov 2025 15:53:42 +0100 Subject: [PATCH 2/2] CR Fix. --- .../ethereum/amm-commons/AmmCloseSwapLens.sol | 157 ++++++++++++++++-- test/utils/factory/IporProtocolFactory.sol | 28 +++- 2 files changed, 164 insertions(+), 21 deletions(-) diff --git a/contracts/chains/ethereum/amm-commons/AmmCloseSwapLens.sol b/contracts/chains/ethereum/amm-commons/AmmCloseSwapLens.sol index 93e9ad7d2..89c0075ec 100644 --- a/contracts/chains/ethereum/amm-commons/AmmCloseSwapLens.sol +++ b/contracts/chains/ethereum/amm-commons/AmmCloseSwapLens.sol @@ -1,46 +1,60 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.8.26; +import "@openzeppelin/contracts/utils/Address.sol"; + import "../../../interfaces/types/IporTypes.sol"; import "../../../interfaces/types/AmmTypes.sol"; import "../../../interfaces/IIporOracle.sol"; import "../../../interfaces/IAmmCloseSwapLens.sol"; import "../../../interfaces/IAmmCloseSwapService.sol"; -import "../../../interfaces/IAmmStorage.sol"; import "../../../libraries/errors/IporErrors.sol"; import "../../../libraries/IporContractValidator.sol"; import "../../../libraries/AmmCloseSwapServicePoolConfigurationLib.sol"; -import "../../../base/amm/libraries/SwapLogicBaseV1.sol"; -import "../../../base/amm/libraries/SwapCloseLogicLibBaseV1.sol"; import "../../../amm/libraries/SwapCloseLogicLib.sol"; import "../../../base/types/AmmTypesBaseV1.sol"; -import {StorageLibBaseV1} from "../../../base/libraries/StorageLibBaseV1.sol"; +import "../../../base/amm/libraries/SwapLogicBaseV1.sol"; +import "../../../base/amm/libraries/SwapCloseLogicLibBaseV1.sol"; +import "../../../base/amm/services/AmmCloseSwapServiceBaseV1.sol"; -/// @dev Legacy AmmCloseSwapLens for DAI/USDT/USDC which uses legacy SwapCloseLogicLib (not BaseV1) /// @dev It is not recommended to use service contract directly, should be used only through IporProtocolRouter. contract AmmCloseSwapLens is IAmmCloseSwapLens { + using Address for address; using IporContractValidator for address; + using SwapLogicBaseV1 for AmmTypesBaseV1.Swap; using AmmCloseSwapServicePoolConfigurationLib for IAmmCloseSwapLens.AmmCloseSwapServicePoolConfiguration; + address public immutable usdt; + address public immutable usdc; + address public immutable dai; + address public immutable stETH; address public immutable iporOracle; - address public immutable messageSigner; - constructor(address iporOracle_, address messageSigner_) { + constructor( + address usdt_, + address usdc_, + address dai_, + address stETH_, + address iporOracle_ + ) { + usdt = usdt_.checkAddress(); + usdc = usdc_.checkAddress(); + dai = dai_.checkAddress(); + stETH = stETH_.checkAddress(); iporOracle = iporOracle_.checkAddress(); - messageSigner = messageSigner_.checkAddress(); } function getAmmCloseSwapServicePoolConfiguration( - address asset + address asset_ ) external view override returns (AmmCloseSwapServicePoolConfiguration memory) { StorageLibBaseV1.AssetServicesValue memory servicesCfg = StorageLibBaseV1.getAssetServicesStorage().value[ - asset + asset_ ]; if (servicesCfg.ammCloseSwapService != address(0)) { return IAmmCloseSwapService(servicesCfg.ammCloseSwapService).getPoolConfiguration(); } else { - revert IporErrors.UnsupportedAsset(IporErrors.ASSET_NOT_SUPPORTED, asset); + revert IporErrors.UnsupportedAsset(IporErrors.ASSET_NOT_SUPPORTED, asset_); } } @@ -52,6 +66,36 @@ contract AmmCloseSwapLens is IAmmCloseSwapLens { uint256 closeTimestamp, AmmTypes.CloseSwapRiskIndicatorsInput calldata riskIndicatorsInput ) external view override returns (AmmTypes.ClosingSwapDetails memory closingSwapDetails) { + if (asset == usdt || asset == usdc || asset == dai) { + closingSwapDetails = _getClosingSwapDetailsForStable( + asset, + account, + direction, + swapId, + closeTimestamp, + riskIndicatorsInput + ); + } else if (asset == stETH) { + closingSwapDetails = _getClosingSwapDetailsForStEth( + account, + direction, + swapId, + closeTimestamp, + riskIndicatorsInput + ); + } else { + revert IporErrors.UnsupportedAsset(IporErrors.ASSET_NOT_SUPPORTED, asset); + } + } + + function _getClosingSwapDetailsForStable( + address asset, + address account, + AmmTypes.SwapDirection direction, + uint256 swapId, + uint256 closeTimestamp, + AmmTypes.CloseSwapRiskIndicatorsInput calldata riskIndicatorsInput + ) internal view returns (AmmTypes.ClosingSwapDetails memory closingSwapDetails) { StorageLibBaseV1.AssetServicesValue memory servicesCfg = StorageLibBaseV1.getAssetServicesStorage().value[ asset ]; @@ -74,6 +118,7 @@ contract AmmCloseSwapLens is IAmmCloseSwapLens { require(swap.id > 0, AmmErrors.INCORRECT_SWAP_ID); int256 swapPnlValueToDate; + if (direction == AmmTypes.SwapDirection.PAY_FIXED_RECEIVE_FLOATING) { swapPnlValueToDate = SwapLogicBaseV1.calculatePnlPayFixed( swap.openTimestamp, @@ -84,7 +129,7 @@ contract AmmCloseSwapLens is IAmmCloseSwapLens { block.timestamp, accruedIpor.ibtPrice ); - } else { + } else if (direction == AmmTypes.SwapDirection.PAY_FLOATING_RECEIVE_FIXED) { swapPnlValueToDate = SwapLogicBaseV1.calculatePnlReceiveFixed( swap.openTimestamp, swap.collateral, @@ -94,6 +139,8 @@ contract AmmCloseSwapLens is IAmmCloseSwapLens { block.timestamp, accruedIpor.ibtPrice ); + } else { + revert(AmmErrors.UNSUPPORTED_DIRECTION); } (closingSwapDetails.closableStatus, closingSwapDetails.swapUnwindRequired) = SwapCloseLogicLibBaseV1 @@ -131,7 +178,7 @@ contract AmmCloseSwapLens is IAmmCloseSwapLens { closingSwapDetails.pnlValue ) = SwapCloseLogicLib.calculateSwapUnwindWhenUnwindRequired( AmmTypes.UnwindParams({ - messageSigner: messageSigner, + messageSigner: StorageLibBaseV1.getMessageSignerStorage().value, spreadRouter: poolCfg.spread, ammStorage: poolCfg.ammStorage, ammTreasury: poolCfg.ammTreasury, @@ -148,4 +195,88 @@ contract AmmCloseSwapLens is IAmmCloseSwapLens { closingSwapDetails.pnlValue = swapPnlValueToDate; } } + + function _getClosingSwapDetailsForStEth( + address account, + AmmTypes.SwapDirection direction, + uint256 swapId, + uint256 closeTimestamp, + AmmTypes.CloseSwapRiskIndicatorsInput calldata riskIndicatorsInput + ) internal view returns (AmmTypes.ClosingSwapDetails memory closingSwapDetails) { + StorageLibBaseV1.AssetServicesValue memory servicesCfg = StorageLibBaseV1.getAssetServicesStorage().value[ + stETH + ]; + + if (servicesCfg.ammCloseSwapService == address(0)) { + revert IporErrors.UnsupportedAsset(IporErrors.ASSET_NOT_SUPPORTED, stETH); + } + + IAmmCloseSwapLens.AmmCloseSwapServicePoolConfiguration memory poolCfg = IAmmCloseSwapService( + servicesCfg.ammCloseSwapService + ).getPoolConfiguration(); + + IporTypes.AccruedIpor memory accruedIpor = IIporOracle(iporOracle).getAccruedIndex( + block.timestamp, + poolCfg.asset + ); + + AmmTypesBaseV1.Swap memory swap = IAmmStorageBaseV1(poolCfg.ammStorage).getSwap(direction, swapId); + + require(swap.id > 0, AmmErrors.INCORRECT_SWAP_ID); + + int256 swapPnlValueToDate = swap.calculatePnl(block.timestamp, accruedIpor.ibtPrice); + + (closingSwapDetails.closableStatus, closingSwapDetails.swapUnwindRequired) = SwapCloseLogicLibBaseV1 + .getClosableStatusForSwap( + AmmTypesBaseV1.ClosableSwapInput({ + account: account, + asset: poolCfg.asset, + closeTimestamp: closeTimestamp, + swapBuyer: swap.buyer, + swapOpenTimestamp: swap.openTimestamp, + swapCollateral: swap.collateral, + swapTenor: swap.tenor, + swapState: swap.state, + swapPnlValueToDate: swapPnlValueToDate, + minLiquidationThresholdToCloseBeforeMaturityByCommunity: poolCfg + .minLiquidationThresholdToCloseBeforeMaturityByCommunity, + minLiquidationThresholdToCloseBeforeMaturityByBuyer: poolCfg + .minLiquidationThresholdToCloseBeforeMaturityByBuyer, + timeBeforeMaturityAllowedToCloseSwapByCommunity: poolCfg + .timeBeforeMaturityAllowedToCloseSwapByCommunity, + timeBeforeMaturityAllowedToCloseSwapByBuyer: poolCfg.getTimeBeforeMaturityAllowedToCloseSwapByBuyer( + swap.tenor + ), + timeAfterOpenAllowedToCloseSwapWithUnwinding: poolCfg + .getTimeAfterOpenAllowedToCloseSwapWithUnwinding(swap.tenor) + }) + ); + + if (closingSwapDetails.swapUnwindRequired == true) { + ( + closingSwapDetails.swapUnwindPnlValue, + closingSwapDetails.swapUnwindOpeningFeeAmount, + closingSwapDetails.swapUnwindFeeLPAmount, + closingSwapDetails.swapUnwindFeeTreasuryAmount, + closingSwapDetails.pnlValue + ) = SwapCloseLogicLibBaseV1.calculateSwapUnwindWhenUnwindRequired( + AmmTypesBaseV1.UnwindParams({ + asset: poolCfg.asset, + messageSigner: StorageLibBaseV1.getMessageSignerStorage().value, + spread: poolCfg.spread, + ammStorage: poolCfg.ammStorage, + ammTreasury: poolCfg.ammTreasury, + closeTimestamp: closeTimestamp, + swapPnlValueToDate: swapPnlValueToDate, + indexValue: accruedIpor.indexValue, + swap: swap, + unwindingFeeRate: poolCfg.unwindingFeeRate, + unwindingFeeTreasuryPortionRate: poolCfg.unwindingFeeTreasuryPortionRate, + riskIndicatorsInputs: riskIndicatorsInput + }) + ); + } else { + closingSwapDetails.pnlValue = swapPnlValueToDate; + } + } } diff --git a/test/utils/factory/IporProtocolFactory.sol b/test/utils/factory/IporProtocolFactory.sol index 211391496..ff85c5fe6 100644 --- a/test/utils/factory/IporProtocolFactory.sol +++ b/test/utils/factory/IporProtocolFactory.sol @@ -784,8 +784,11 @@ contract IporProtocolFactory is Test { deployerContracts.ammCloseSwapLens = address( new AmmCloseSwapLens({ - iporOracle_: address(amm.iporOracle), - messageSigner_: messageSignerAddress + usdt_: address(amm.usdt.asset), + usdc_: address(amm.usdc.asset), + dai_: address(amm.dai.asset), + stETH_: address(amm.stEth.asset), + iporOracle_: address(amm.iporOracle) }) ); @@ -1017,8 +1020,11 @@ contract IporProtocolFactory is Test { deployerContracts.ammCloseSwapLens = address( new AmmCloseSwapLens({ - iporOracle_: address(iporProtocol.iporOracle), - messageSigner_: messageSignerAddress + usdt_: address(iporProtocol.asset), + usdc_: address(_fakeAsset), + dai_: address(_fakeAsset), + stETH_: address(_fakeAsset), + iporOracle_: address(iporProtocol.iporOracle) }) ); @@ -1212,8 +1218,11 @@ contract IporProtocolFactory is Test { deployerContracts.ammCloseSwapLens = address( new AmmCloseSwapLens({ - iporOracle_: address(iporProtocol.iporOracle), - messageSigner_: messageSignerAddress + usdt_: address(_fakeAsset), + usdc_: address(iporProtocol.asset), + dai_: address(_fakeAsset), + stETH_: address(_fakeAsset), + iporOracle_: address(iporProtocol.iporOracle) }) ); @@ -1409,8 +1418,11 @@ contract IporProtocolFactory is Test { deployerContracts.ammCloseSwapLens = address( new AmmCloseSwapLens({ - iporOracle_: address(iporProtocol.iporOracle), - messageSigner_: messageSignerAddress + usdt_: address(_fakeAsset), + usdc_: address(_fakeAsset), + dai_: address(iporProtocol.asset), + stETH_: address(_fakeAsset), + iporOracle_: address(iporProtocol.iporOracle) }) );