Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
secrets:
ETHEREUM_PROVIDER_URL: ${{ secrets.ETHEREUM_PROVIDER_URL }}
ARBITRUM_PROVIDER_URL: ${{ secrets.ARBITRUM_PROVIDER_URL }}
BASE_PROVIDER_URL: ${{ secrets.BASE_PROVIDER_URL }}
with:
test-options: '--no-match-path "test/fork/*"'
fork-test-options: '--match-path "test/fork/*"'
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
secrets:
ETHEREUM_PROVIDER_URL: ${{ secrets.ETHEREUM_PROVIDER_URL }}
ARBITRUM_PROVIDER_URL: ${{ secrets.ARBITRUM_PROVIDER_URL }}
BASE_PROVIDER_URL: ${{ secrets.BASE_PROVIDER_URL }}
with:
test-options: '--no-match-path "test/fork/*"'
fork-test-options: '--match-path "test/fork/*"'
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/foundry-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ on:
required: true
ARBITRUM_PROVIDER_URL:
required: true
BASE_PROVIDER_URL:
required: true

inputs:

Expand Down Expand Up @@ -59,8 +61,8 @@ jobs:
node-version: ${{ inputs.node-version }}

- name: Set up node_modules cache
# from tag: v4.0.2
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9
# from tag: v4.2.3
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684
with:
path: "**/node_modules"
key: ${{ runner.os }}-modules-${{ hashFiles('**/package-lock.json') }}
Expand All @@ -85,6 +87,7 @@ jobs:
env:
ETHEREUM_PROVIDER_URL: ${{ secrets.ETHEREUM_PROVIDER_URL }}
ARBITRUM_PROVIDER_URL: ${{ secrets.ARBITRUM_PROVIDER_URL }}
BASE_PROVIDER_URL: ${{ secrets.BASE_PROVIDER_URL }}
run: |
forge test ${{ inputs.test-options }} -vvv
id: test
Expand All @@ -94,6 +97,7 @@ jobs:
env:
ETHEREUM_PROVIDER_URL: ${{ secrets.ETHEREUM_PROVIDER_URL }}
ARBITRUM_PROVIDER_URL: ${{ secrets.ARBITRUM_PROVIDER_URL }}
BASE_PROVIDER_URL: ${{ secrets.BASE_PROVIDER_URL }}
run: |
forge test ${{ inputs.fork-test-options }} --fork-url ${{ secrets.ETHEREUM_PROVIDER_URL }} -vvv
id: fork-test
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ interface IAmmPoolsServiceUsdcBaseV1 is IProvideLiquidityEvents {
function provideLiquidityUsdcToAmmPoolUsdc(address beneficiary, uint256 assetAmount) external payable;

function redeemFromAmmPoolUsdc(address beneficiary, uint256 ipTokenAmount) external;

function rebalanceBetweenAmmTreasuryAndAssetManagementUsdc() external;
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,8 @@ contract AmmPoolsServiceUsdcBaseV1 is IAmmPoolsServiceUsdcBaseV1, AmmPoolsServic
function redeemFromAmmPoolUsdc(address beneficiary, uint256 ipTokenAmount) external {
_redeem(beneficiary, ipTokenAmount);
}

function rebalanceBetweenAmmTreasuryAndAssetManagementUsdc() external override {
_rebalanceBetweenAmmTreasuryAndAssetManagement();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.26;

import {IAmmPoolsServiceWstEthBaseV1} from "./IAmmPoolsServiceWstEthBaseV1.sol";

/// @title Interface of the AmmPoolsServiceWstEth contract V2.
interface IAmmPoolsServiceWstEthBaseV2 is IAmmPoolsServiceWstEthBaseV1 {
/// @notice Rebalances wstETH assets between the AmmTreasury and the AssetManagement, based on configuration stored
/// in the `AmmPoolsParamsValue.ammTreasuryAndAssetManagementRatio` field.
/// @dev Emits {Deposit} or {Withdraw} event from AssetManagement depends on current asset balance on AmmTreasury and AssetManagement.
/// @dev Emits {Transfer} from ERC20 asset.
function rebalanceBetweenAmmTreasuryAndAssetManagementWstEth() external;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {StorageLibBaseV1} from "../../libraries/StorageLibBaseV1.sol";

/// @dev It is not recommended to use service contract directly, should be used only through IporProtocolRouter.
/// @dev Service can be safely used directly only if you are sure that methods will not touch any storage variables.
/// @dev Close Swap Service for wstEth pool - no asset management support
/// @dev Close Swap Service for wstEth pool - Asset Management IS NOT supported in this contract.
contract AmmCloseSwapServiceWstEthBaseV1 is AmmCloseSwapServiceBaseV1, IAmmCloseSwapServiceWstEth {
using IporContractValidator for address;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.26;

import {IAmmCloseSwapServiceWstEth} from "../../../interfaces/IAmmCloseSwapServiceWstEth.sol";
import {AmmCloseSwapServiceBaseV2} from "../../../base/amm/services/AmmCloseSwapServiceBaseV2.sol";
import {StorageLibBaseV1} from "../../libraries/StorageLibBaseV1.sol";
import {IporContractValidator} from "../../../libraries/IporContractValidator.sol";
import {AmmTypes} from "../../../interfaces/types/AmmTypes.sol";
import {IAmmCloseSwapLens} from "../../../interfaces/IAmmCloseSwapLens.sol";


/// @dev It is not recommended to use service contract directly, should be used only through IporProtocolRouter.
/// @dev Service can be safely used directly only if you are sure that methods will not touch any storage variables.
/// @dev Close Swap Service for wstEth pool - Asset Management (PlasmaVault from Ipor Fusion) and rebalancing between AMM Treasury
/// and Asset Management (PlasmaVault from Ipor Fusion) IS supported in this contract.
contract AmmCloseSwapServiceWstEthBaseV2 is AmmCloseSwapServiceBaseV2, IAmmCloseSwapServiceWstEth {
using IporContractValidator for address;

constructor(
IAmmCloseSwapLens.AmmCloseSwapServicePoolConfiguration memory poolCfg,
address iporOracle_
) AmmCloseSwapServiceBaseV2(poolCfg, iporOracle_) {}

function closeSwapsWstEth(
address beneficiary,
uint256[] memory payFixedSwapIds,
uint256[] memory receiveFixedSwapIds,
AmmTypes.CloseSwapRiskIndicatorsInput calldata riskIndicatorsInput
)
external
override
returns (
AmmTypes.IporSwapClosingResult[] memory closedPayFixedSwaps,
AmmTypes.IporSwapClosingResult[] memory closedReceiveFixedSwaps
)
{
(closedPayFixedSwaps, closedReceiveFixedSwaps) = _closeSwaps(
beneficiary,
payFixedSwapIds,
receiveFixedSwapIds,
riskIndicatorsInput
);
}

function emergencyCloseSwapsWstEth(
uint256[] memory payFixedSwapIds,
uint256[] memory receiveFixedSwapIds,
AmmTypes.CloseSwapRiskIndicatorsInput calldata riskIndicatorsInput
)
external
override
returns (
AmmTypes.IporSwapClosingResult[] memory closedPayFixedSwaps,
AmmTypes.IporSwapClosingResult[] memory closedReceiveFixedSwaps
)
{
(closedPayFixedSwaps, closedReceiveFixedSwaps) = _emergencyCloseSwaps(
payFixedSwapIds,
receiveFixedSwapIds,
riskIndicatorsInput
);
}

function _getMessageSigner() internal view override returns (address) {
return StorageLibBaseV1.getMessageSignerStorage().value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import "../../../governance/AmmConfigurationManager.sol";
import "../../../base/interfaces/IAmmTreasuryBaseV1.sol";

/// @dev It is not recommended to use service contract directly, should be used only through IporProtocolRouter.
/// @dev Asset Management is NOT supported in this contract. Rebalancing between AMM Treasury and Asset Management is NOT supported in this contract.
contract AmmPoolsServiceWstEthBaseV1 is IAmmPoolsServiceWstEthBaseV1 {
using IporContractValidator for address;
using SafeERC20 for IERC20;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.26;

import {IAmmPoolsServiceWstEthBaseV2} from "../../../base/amm-wstEth/interfaces/IAmmPoolsServiceWstEthBaseV2.sol";
import {AmmPoolsServiceBaseV1} from "../../../base/amm/services/AmmPoolsServiceBaseV1.sol";

/// @dev It is not recommended to use service contract directly, should be used only through IporProtocolRouter.
/// @dev Asset Management is supported in this contract. Rebalancing between AMM Treasury and Asset Management IS supported in this contract.
contract AmmPoolsServiceWstEthBaseV2 is IAmmPoolsServiceWstEthBaseV2, AmmPoolsServiceBaseV1 {
constructor(
address asset_,
address ipToken_,
address ammTreasury_,
address ammStorage_,
address ammAssetManagement_,
address iporOracle_,
address iporProtocolRouter_,
uint256 redeemFeeRate_,
uint256 autoRebalanceThresholdMultiplier_
)
AmmPoolsServiceBaseV1(
asset_,
ipToken_,
ammTreasury_,
ammStorage_,
ammAssetManagement_,
iporOracle_,
iporProtocolRouter_,
redeemFeeRate_,
autoRebalanceThresholdMultiplier_
)
{}

function provideLiquidityWstEth(address beneficiary, uint256 assetAmount) external payable override {
_provideLiquidity(beneficiary, assetAmount);
}

function redeemFromAmmPoolWstEth(address beneficiary, uint256 ipTokenAmount) external override {
_redeem(beneficiary, ipTokenAmount);
}

function rebalanceBetweenAmmTreasuryAndAssetManagementWstEth() external override {
_rebalanceBetweenAmmTreasuryAndAssetManagement();
}
}
2 changes: 1 addition & 1 deletion contracts/base/amm/services/AmmPoolsServiceBaseV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ contract AmmPoolsServiceBaseV1 is IProvideLiquidityEvents {
);
}

function rebalanceBetweenAmmTreasuryAndAssetManagement() external {
function _rebalanceBetweenAmmTreasuryAndAssetManagement() internal virtual {
require(
AmmConfigurationManager.isAppointedToRebalanceInAmm(asset, msg.sender),
AmmPoolsErrors.CALLER_NOT_APPOINTED_TO_REBALANCE
Expand Down
7 changes: 5 additions & 2 deletions contracts/chains/base/router/IporProtocolRouterBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {IAmmPoolsServiceUsdcBaseV1} from "../../../base/amm-usdc/interfaces/IAmm
import {IAmmOpenSwapServiceWstEth} from "../../../interfaces/IAmmOpenSwapServiceWstEth.sol";
import {IAmmCloseSwapServiceWstEth} from "../../../interfaces/IAmmCloseSwapServiceWstEth.sol";
import {IAmmPoolsServiceWstEthBaseV1} from "../../../base/amm-wstEth/interfaces/IAmmPoolsServiceWstEthBaseV1.sol";
import {IAmmPoolsServiceWstEthBaseV2} from "../../../base/amm-wstEth/interfaces/IAmmPoolsServiceWstEthBaseV2.sol";
import {IAmmOpenSwapServiceUsdcBaseV1} from "../../../base/amm-usdc/interfaces/IAmmOpenSwapServiceUsdcBaseV1.sol";
import {IAmmCloseSwapServiceUsdc} from "../../../interfaces/IAmmCloseSwapServiceUsdc.sol";
import {IAmmPoolsServiceUsdm} from "../../../amm-usdm/interfaces/IAmmPoolsServiceUsdm.sol";
Expand Down Expand Up @@ -117,7 +118,8 @@ contract IporProtocolRouterBase is IporProtocolRouterAbstract {
return servicesCfg.ammCloseSwapService;
} else if (
_checkFunctionSigAndIsNotPause(sig, IAmmPoolsServiceWstEthBaseV1.provideLiquidityWstEth.selector) ||
_checkFunctionSigAndIsNotPause(sig, IAmmPoolsServiceWstEthBaseV1.redeemFromAmmPoolWstEth.selector)
_checkFunctionSigAndIsNotPause(sig, IAmmPoolsServiceWstEthBaseV1.redeemFromAmmPoolWstEth.selector) ||
_checkFunctionSigAndIsNotPause(sig, IAmmPoolsServiceWstEthBaseV2.rebalanceBetweenAmmTreasuryAndAssetManagementWstEth.selector)
) {
if (batchOperation == 0) {
_nonReentrantBefore();
Expand All @@ -128,7 +130,8 @@ contract IporProtocolRouterBase is IporProtocolRouterAbstract {
return servicesCfg.ammPoolsService;
} else if (
_checkFunctionSigAndIsNotPause(sig, IAmmPoolsServiceUsdcBaseV1.provideLiquidityUsdcToAmmPoolUsdc.selector) ||
_checkFunctionSigAndIsNotPause(sig, IAmmPoolsServiceUsdcBaseV1.redeemFromAmmPoolUsdc.selector)
_checkFunctionSigAndIsNotPause(sig, IAmmPoolsServiceUsdcBaseV1.redeemFromAmmPoolUsdc.selector) ||
_checkFunctionSigAndIsNotPause(sig, IAmmPoolsServiceUsdcBaseV1.rebalanceBetweenAmmTreasuryAndAssetManagementUsdc.selector)
) {
if (batchOperation == 0) {
_nonReentrantBefore();
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IAmmGovernanceService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ interface IAmmGovernanceService {
/// @param asset Address of asset representing specific pool
/// @param newMaxLiquidityPoolBalance New max liquidity pool balance threshold. Value represented WITHOUT 18 decimals.
/// @param newAutoRebalanceThreshold New auto rebalance threshold (for stablecoins represented in thousands). Value represented WITHOUT 18 decimals. For stablecoins value represents multiplication of 1000.
/// @param newAmmTreasuryAndAssetManagementRatio New AMM Treasury and Asset Management ratio, represented WITHOUT 18 decimals, value represents percentage with 2 decimals. Example: 65% = 6500, 99,99% = 9999
/// @param newAmmTreasuryAndAssetManagementRatio New AMM Treasury and Asset Management ratio, represented WITHOUT 18 decimals, value represents percentage with 2 decimals. Example: 65% = 6500, 99,99% = 9999. The value determines what percentage of total funds remains in AMM treasury, while the rest goes to asset management.
function setAmmPoolsParams(
address asset,
uint32 newMaxLiquidityPoolBalance,
Expand Down
84 changes: 84 additions & 0 deletions test/amm/AmmClosingSwaps.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ contract AmmClosingSwaps is TestCommons, DataUtils {
IporProtocolFactory.IporProtocolConfig private _cfg;

function setUp() public {

_admin = address(this);
_buyer = _getUserAddress(1);
_community = _getUserAddress(2);
Expand All @@ -30,6 +31,89 @@ contract AmmClosingSwaps is TestCommons, DataUtils {
_cfg.spread28DaysTestCase = BuilderUtils.Spread28DaysTestCase.CASE0;
}

/**
* @dev This test verifies that a swap cannot be closed by a legitimate liquidator
* if the swap beneficiary is a blacklisted address.
*
* Conditions:
* 1. Swap asset: USDT or USDC.
* 2. Swap beneficiary: Blacklisted address.
*
* Run this test using:
* ```
* forge test -vvvv --match-test testCannotCloseSwapByLiquidatorAfterMaturity
* ```
*
* Test files: https://drive.google.com/file/d/1TjVhquYDqCowjRs_NLMdNF8-nkZGvm3D/view?usp=sharing
*
*/
function testCannotClosePayFixedAsLiquidatorAfterMaturity() public {
//given
_iporProtocol = _iporProtocolFactory.getUsdtInstance(_cfg);
MockTestnetToken asset = _iporProtocol.asset;

uint256 liquidityAmount = 1_000_000 * 1e6;
uint256 totalAmount = 10_000 * 1e6;
uint256 acceptableFixedInterestRate = 10 * 10 ** 16;
uint256 leverage = 100 * 10 ** 18;

//
// This address is a banned address from https://dune.com/phabc/usdt---banned-addresses
//
address maliciousSwapBeneficiary = 0xcaCa5575eB423183bA4B6EE3aA9fc2cB488aEEEE;

asset.addToBlackList(maliciousSwapBeneficiary);

asset.approve(address(_iporProtocol.router), liquidityAmount);
_iporProtocol.ammPoolsService.provideLiquidityUsdt(_admin, liquidityAmount);

asset.transfer(_buyer, totalAmount);

vm.prank(_buyer);
asset.approve(address(_iporProtocol.router), totalAmount);

uint256 buyerBalanceBefore = _iporProtocol.asset.balanceOf(_buyer);
uint256 adminBalanceBefore = _iporProtocol.asset.balanceOf(_admin);
uint256 liquidatorBalanceBefore = _iporProtocol.asset.balanceOf(_liquidator);

vm.startPrank(_buyer);
uint256 swapId = _iporProtocol.ammOpenSwapService.openSwapPayFixed28daysUsdt(
maliciousSwapBeneficiary, // @audit: Malicious user passes a banned address here.
totalAmount,
acceptableFixedInterestRate,
leverage,
getRiskIndicatorsInputs(0)
);
vm.stopPrank();

vm.warp(100 + 28 days + 1 seconds);

_iporProtocol.ammGovernanceService.addSwapLiquidator(address(_iporProtocol.asset), _liquidator);

uint256[] memory swapPfIds = new uint256[](1);
swapPfIds[0] = 1;
uint256[] memory swapRfIds = new uint256[](0);

//when
vm.startPrank(_liquidator);
_iporProtocol.ammCloseSwapServiceUsdt.closeSwapsUsdt(
_liquidator,
swapPfIds,
swapRfIds,
getCloseRiskIndicatorsInputs(address(_iporProtocol.asset), IporTypes.SwapTenor.DAYS_28)
);
vm.stopPrank();

//then
uint256 buyerBalanceAfter = _iporProtocol.asset.balanceOf(_buyer);
uint256 adminBalanceAfter = _iporProtocol.asset.balanceOf(_admin);
uint256 liquidatorBalanceAfter = _iporProtocol.asset.balanceOf(_liquidator);

// assertEq(buyerBalanceBefore - buyerBalanceAfter, 73075873);
// assertEq(adminBalanceAfter - adminBalanceBefore, 0);
assertEq(liquidatorBalanceAfter - liquidatorBalanceBefore, 25000000);
}

function testShouldAddSwapLiquidatorAsIporOwner() public {
//given
_iporProtocol = _iporProtocolFactory.getUsdtInstance(_cfg);
Expand Down
Loading
Loading