From 17a9e2af202a313ea734a556af3f4460d3e8c795 Mon Sep 17 00:00:00 2001 From: Suryansh <39276431+0xsuryansh@users.noreply.github.com> Date: Fri, 24 Jan 2025 22:03:00 +0530 Subject: [PATCH] CCIP-4428 Decouple LiquidityManager tests with LockReleaseTokenPool (#16017) * decouple LiquidityManager tests with LockReleaseTokenPool + Test Rename * split test files * [Bot] Update changeset file with jira issues * prettier run * liquiditymanager test refactor * reference events from interface / contracts + solidity version + spacing * prettier * remove duplicate events * prettier fix * fix * pretty --------- Co-authored-by: app-token-issuer-infra-releng[bot] <120227048+app-token-issuer-infra-releng[bot]@users.noreply.github.com> --- contracts/.changeset/five-brooms-knock.md | 9 + .../liquiditymanager.gas-snapshot | 42 +- .../test/LiquidityManager.addLiquidity.t.sol | 24 + ...LiquidityManager.rebalanceLiquidity.t.sol} | 558 +++--------------- .../test/LiquidityManager.receive.t.sol | 21 + .../LiquidityManager.removeLiquidity.t.sol | 39 ++ .../test/LiquidityManager.report.t.sol | 18 + ...idityManager.setCrossChainRebalancer.t.sol | 137 +++++ .../LiquidityManager.setFinanceRole.t.sol | 25 + ...tyManager.setLocalLiquidityContainer.t.sol | 32 + ...LiquidityManager.setMinimumLiquidity.t.sol | 23 + .../test/LiquidityManager.withdrawERC20.t.sol | 31 + .../LiquidityManager.withdrawNative.t.sol | 34 ++ .../test/LiquidityManagerBaseTest.t.sol | 6 +- .../test/LiquidityManagerSetup.t.sol | 51 ++ .../test/mocks/MockTokenPool.sol | 31 + 16 files changed, 556 insertions(+), 525 deletions(-) create mode 100644 contracts/.changeset/five-brooms-knock.md create mode 100644 contracts/src/v0.8/liquiditymanager/test/LiquidityManager.addLiquidity.t.sol rename contracts/src/v0.8/liquiditymanager/test/{LiquidityManager.t.sol => LiquidityManager.rebalanceLiquidity.t.sol} (53%) create mode 100644 contracts/src/v0.8/liquiditymanager/test/LiquidityManager.receive.t.sol create mode 100644 contracts/src/v0.8/liquiditymanager/test/LiquidityManager.removeLiquidity.t.sol create mode 100644 contracts/src/v0.8/liquiditymanager/test/LiquidityManager.report.t.sol create mode 100644 contracts/src/v0.8/liquiditymanager/test/LiquidityManager.setCrossChainRebalancer.t.sol create mode 100644 contracts/src/v0.8/liquiditymanager/test/LiquidityManager.setFinanceRole.t.sol create mode 100644 contracts/src/v0.8/liquiditymanager/test/LiquidityManager.setLocalLiquidityContainer.t.sol create mode 100644 contracts/src/v0.8/liquiditymanager/test/LiquidityManager.setMinimumLiquidity.t.sol create mode 100644 contracts/src/v0.8/liquiditymanager/test/LiquidityManager.withdrawERC20.t.sol create mode 100644 contracts/src/v0.8/liquiditymanager/test/LiquidityManager.withdrawNative.t.sol create mode 100644 contracts/src/v0.8/liquiditymanager/test/LiquidityManagerSetup.t.sol create mode 100644 contracts/src/v0.8/liquiditymanager/test/mocks/MockTokenPool.sol diff --git a/contracts/.changeset/five-brooms-knock.md b/contracts/.changeset/five-brooms-knock.md new file mode 100644 index 00000000000..6ba2f1b4b97 --- /dev/null +++ b/contracts/.changeset/five-brooms-knock.md @@ -0,0 +1,9 @@ +--- +'@chainlink/contracts': patch +--- + +#internal decouple LiquidityManager tests with LockReleaseTokenPool + Test Rename + +PR issue: CCIP-4428 + +Solidity Review issue: CCIP-3966 \ No newline at end of file diff --git a/contracts/gas-snapshots/liquiditymanager.gas-snapshot b/contracts/gas-snapshots/liquiditymanager.gas-snapshot index bc545b69d38..09ac4e7101b 100644 --- a/contracts/gas-snapshots/liquiditymanager.gas-snapshot +++ b/contracts/gas-snapshots/liquiditymanager.gas-snapshot @@ -1,31 +1,17 @@ -LiquidityManager__report:test_EmptyReportReverts() (gas: 11181) -LiquidityManager_addLiquidity:test_addLiquiditySuccess() (gas: 279198) -LiquidityManager_rebalanceLiquidity:test_InsufficientLiquidityReverts() (gas: 206764) -LiquidityManager_rebalanceLiquidity:test_InvalidRemoteChainReverts() (gas: 192374) -LiquidityManager_rebalanceLiquidity:test_rebalanceBetweenPoolsSuccess() (gas: 9141798) -LiquidityManager_rebalanceLiquidity:test_rebalanceBetweenPoolsSuccess_AlreadyFinalized() (gas: 9435757) -LiquidityManager_rebalanceLiquidity:test_rebalanceBetweenPools_MultiStageFinalization() (gas: 9430897) -LiquidityManager_rebalanceLiquidity:test_rebalanceBetweenPools_NativeRewrap() (gas: 9360730) -LiquidityManager_rebalanceLiquidity:test_rebalanceLiquiditySuccess() (gas: 382928) -LiquidityManager_receive:test_receive_success() (gas: 21182) -LiquidityManager_removeLiquidity:test_InsufficientLiquidityReverts() (gas: 184959) -LiquidityManager_removeLiquidity:test_OnlyFinanceRoleReverts() (gas: 10872) -LiquidityManager_removeLiquidity:test_removeLiquiditySuccess() (gas: 236361) -LiquidityManager_setCrossChainRebalancer:test_OnlyOwnerReverts() (gas: 17005) -LiquidityManager_setCrossChainRebalancer:test_ZeroAddressReverts() (gas: 21669) -LiquidityManager_setCrossChainRebalancer:test_ZeroChainSelectorReverts() (gas: 13099) -LiquidityManager_setCrossChainRebalancer:test_setCrossChainRebalancerSuccess() (gas: 162186) -LiquidityManager_setFinanceRole:test_OnlyOwnerReverts() (gas: 10987) -LiquidityManager_setFinanceRole:test_setFinanceRoleSuccess() (gas: 21836) -LiquidityManager_setLocalLiquidityContainer:test_OnlyOwnerReverts() (gas: 11030) -LiquidityManager_setLocalLiquidityContainer:test_ReverstWhen_CalledWithTheZeroAddress() (gas: 10621) -LiquidityManager_setLocalLiquidityContainer:test_setLocalLiquidityContainerSuccess() (gas: 3976150) -LiquidityManager_setMinimumLiquidity:test_OnlyOwnerReverts() (gas: 10925) -LiquidityManager_setMinimumLiquidity:test_setMinimumLiquiditySuccess() (gas: 36389) -LiquidityManager_withdrawERC20:test_withdrawERC20Reverts() (gas: 180396) -LiquidityManager_withdrawERC20:test_withdrawERC20Success() (gas: 205895) -LiquidityManager_withdrawNative:test_OnlyFinanceRoleReverts() (gas: 13077) -LiquidityManager_withdrawNative:test_withdrawNative_success() (gas: 51407) +LiquidityManager_addLiquidity:test_addLiquidity() (gas: 280708) +LiquidityManager_rebalanceLiquidity:test_rebalanceLiquidity() (gas: 380353) +LiquidityManager_rebalanceLiquidity:test_rebalanceLiquidity_BetweenPools() (gas: 9141742) +LiquidityManager_rebalanceLiquidity:test_rebalanceLiquidity_BetweenPools_AlreadyFinalized() (gas: 5843918) +LiquidityManager_rebalanceLiquidity:test_rebalanceLiquidity_NativeRewrap() (gas: 5766947) +LiquidityManager_rebalanceLiquidity:test_rebalanceLiquidity_RebalanceBetweenPoolsMultiStageFinalization() (gas: 5839103) +LiquidityManager_receive:test_receive() (gas: 24490) +LiquidityManager_removeLiquidity:test_removeLiquiditySuccess() (gas: 237114) +LiquidityManager_setCrossChainRebalancer:test_setCrossChainRebalancer() (gas: 162164) +LiquidityManager_setFinanceRole:test_setFinanceRole() (gas: 21792) +LiquidityManager_setLocalLiquidityContainer:test_setLocalLiquidityContainer() (gas: 406009) +LiquidityManager_setMinimumLiquidity:test_setMinimumLiquidity() (gas: 36433) +LiquidityManager_withdrawERC20:test_withdrawERC20() (gas: 205876) +LiquidityManager_withdrawNative:test_withdrawNative() (gas: 55522) OCR3Base_setOCR3Config:testFMustBePositiveReverts() (gas: 12245) OCR3Base_setOCR3Config:testFTooHighReverts() (gas: 12429) OCR3Base_setOCR3Config:testOracleOutOfRegisterReverts() (gas: 14847) diff --git a/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.addLiquidity.t.sol b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.addLiquidity.t.sol new file mode 100644 index 00000000000..86b59695873 --- /dev/null +++ b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.addLiquidity.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.24; + +import "./LiquidityManagerSetup.t.sol"; +import {LiquidityManager} from "../LiquidityManager.sol"; + +contract LiquidityManager_addLiquidity is LiquidityManagerSetup { + function test_addLiquidity() external { + address caller = STRANGER; + changePrank(caller); + + uint256 amount = 12345679; + deal(address(s_l1Token), caller, amount); + + s_l1Token.approve(address(s_liquidityManager), amount); + + vm.expectEmit(); + emit LiquidityManager.LiquidityAddedToContainer(caller, amount); + + s_liquidityManager.addLiquidity(amount); + + assertEq(s_l1Token.balanceOf(address(s_lockReleaseTokenPool)), amount); + } +} diff --git a/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.t.sol b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.rebalanceLiquidity.t.sol similarity index 53% rename from contracts/src/v0.8/liquiditymanager/test/LiquidityManager.t.sol rename to contracts/src/v0.8/liquiditymanager/test/LiquidityManager.rebalanceLiquidity.t.sol index 088e36c4f0e..e54b7861810 100644 --- a/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.t.sol +++ b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.rebalanceLiquidity.t.sol @@ -1,172 +1,21 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.24; +pragma solidity ^0.8.24; import {ILiquidityManager} from "../interfaces/ILiquidityManager.sol"; -import {IBridgeAdapter} from "../interfaces/IBridge.sol"; +import {ILiquidityContainer} from "../interfaces/ILiquidityContainer.sol"; -import {LockReleaseTokenPool} from "../../ccip/pools/LockReleaseTokenPool.sol"; +import {LiquidityManagerSetup} from "./LiquidityManagerSetup.t.sol"; import {LiquidityManager} from "../LiquidityManager.sol"; -import {MockL1BridgeAdapter} from "./mocks/MockBridgeAdapter.sol"; -import {LiquidityManagerBaseTest} from "./LiquidityManagerBaseTest.t.sol"; import {LiquidityManagerHelper} from "./helpers/LiquidityManagerHelper.sol"; +import {MockL1BridgeAdapter} from "./mocks/MockBridgeAdapter.sol"; +import {MockLockReleaseTokenPool} from "./mocks/MockTokenPool.sol"; import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; -// FOUNDRY_PROFILE=liquiditymanager forge test --match-path src/v0.8/liquiditymanager/test/LiquidityManager.t.sol - -contract LiquidityManagerSetup is LiquidityManagerBaseTest { - event FinalizationStepCompleted( - uint64 indexed ocrSeqNum, - uint64 indexed remoteChainSelector, - bytes bridgeSpecificData - ); - event LiquidityTransferred( - uint64 indexed ocrSeqNum, - uint64 indexed fromChainSelector, - uint64 indexed toChainSelector, - address to, - uint256 amount, - bytes bridgeSpecificPayload, - bytes bridgeReturnData - ); - event FinalizationFailed( - uint64 indexed ocrSeqNum, - uint64 indexed remoteChainSelector, - bytes bridgeSpecificData, - bytes reason - ); - event FinanceRoleSet(address financeRole); - event LiquidityAddedToContainer(address indexed provider, uint256 indexed amount); - event LiquidityRemovedFromContainer(address indexed remover, uint256 indexed amount); - // Liquidity container event - event LiquidityAdded(address indexed provider, uint256 indexed amount); - event LiquidityRemoved(address indexed remover, uint256 indexed amount); - - error NonceAlreadyUsed(uint256 nonce); - - LiquidityManagerHelper internal s_liquidityManager; - LockReleaseTokenPool internal s_lockReleaseTokenPool; - MockL1BridgeAdapter internal s_bridgeAdapter; - - // LiquidityManager that rebalances weth. - LiquidityManagerHelper internal s_wethRebalancer; - LockReleaseTokenPool internal s_wethLockReleaseTokenPool; - MockL1BridgeAdapter internal s_wethBridgeAdapter; - - function setUp() public virtual override { - LiquidityManagerBaseTest.setUp(); - - s_bridgeAdapter = new MockL1BridgeAdapter(s_l1Token, false); - s_lockReleaseTokenPool = new LockReleaseTokenPool( - s_l1Token, - DEFAULT_TOKEN_DECIMALS, - new address[](0), - address(1), - true, - address(123) - ); - s_liquidityManager = new LiquidityManagerHelper( - s_l1Token, - i_localChainSelector, - s_lockReleaseTokenPool, - 0, - FINANCE - ); - - s_lockReleaseTokenPool.setRebalancer(address(s_liquidityManager)); - - s_wethBridgeAdapter = new MockL1BridgeAdapter(IERC20(address(s_l1Weth)), true); - s_wethLockReleaseTokenPool = new LockReleaseTokenPool( - IERC20(address(s_l1Weth)), - DEFAULT_TOKEN_DECIMALS, - new address[](0), - address(1), - true, - address(123) - ); - s_wethRebalancer = new LiquidityManagerHelper( - IERC20(address(s_l1Weth)), - i_localChainSelector, - s_wethLockReleaseTokenPool, - 0, - FINANCE - ); - - s_wethLockReleaseTokenPool.setRebalancer(address(s_wethRebalancer)); - } -} - -contract LiquidityManager_addLiquidity is LiquidityManagerSetup { - function test_addLiquiditySuccess() external { - address caller = STRANGER; - vm.startPrank(caller); - - uint256 amount = 12345679; - deal(address(s_l1Token), caller, amount); - - s_l1Token.approve(address(s_liquidityManager), amount); - - vm.expectEmit(); - emit LiquidityAddedToContainer(caller, amount); - - s_liquidityManager.addLiquidity(amount); - - assertEq(s_l1Token.balanceOf(address(s_lockReleaseTokenPool)), amount); - } -} - -contract LiquidityManager_removeLiquidity is LiquidityManagerSetup { - function test_removeLiquiditySuccess() external { - uint256 amount = 12345679; - deal(address(s_l1Token), address(s_lockReleaseTokenPool), amount); - - vm.expectEmit(); - emit LiquidityRemovedFromContainer(FINANCE, amount); - - vm.startPrank(FINANCE); - s_liquidityManager.removeLiquidity(amount); - - assertEq(s_l1Token.balanceOf(address(s_liquidityManager)), 0); - } - - function test_InsufficientLiquidityReverts() external { - uint256 balance = 923; - uint256 requested = balance + 1; - - deal(address(s_l1Token), address(s_lockReleaseTokenPool), balance); - - vm.expectRevert(abi.encodeWithSelector(LiquidityManager.InsufficientLiquidity.selector, requested, balance, 0)); - - vm.startPrank(FINANCE); - s_liquidityManager.removeLiquidity(requested); - } - - function test_OnlyFinanceRoleReverts() external { - vm.stopPrank(); - - vm.expectRevert(LiquidityManager.OnlyFinanceRole.selector); - - s_liquidityManager.removeLiquidity(123); - } -} - -contract LiquidityManager__report is LiquidityManagerSetup { - function test_EmptyReportReverts() external { - ILiquidityManager.LiquidityInstructions memory instructions = ILiquidityManager.LiquidityInstructions({ - sendLiquidityParams: new ILiquidityManager.SendLiquidityParams[](0), - receiveLiquidityParams: new ILiquidityManager.ReceiveLiquidityParams[](0) - }); - - vm.expectRevert(LiquidityManager.EmptyReport.selector); - - s_liquidityManager.report(abi.encode(instructions), 123); - } -} - contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { uint256 internal constant AMOUNT = 12345679; - function test_rebalanceLiquiditySuccess() external { + function test_rebalanceLiquidity() external { deal(address(s_l1Token), address(s_lockReleaseTokenPool), AMOUNT); LiquidityManager.CrossChainRebalancerArgs[] memory args = new LiquidityManager.CrossChainRebalancerArgs[](1); @@ -180,17 +29,17 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { s_liquidityManager.setCrossChainRebalancers(args); vm.expectEmit(); - emit Transfer(address(s_lockReleaseTokenPool), address(s_liquidityManager), AMOUNT); + emit IERC20.Transfer(address(s_lockReleaseTokenPool), address(s_liquidityManager), AMOUNT); vm.expectEmit(); - emit Approval(address(s_liquidityManager), address(s_bridgeAdapter), AMOUNT); + emit IERC20.Approval(address(s_liquidityManager), address(s_bridgeAdapter), AMOUNT); vm.expectEmit(); - emit Transfer(address(s_liquidityManager), address(s_bridgeAdapter), AMOUNT); + emit IERC20.Transfer(address(s_liquidityManager), address(s_bridgeAdapter), AMOUNT); vm.expectEmit(); bytes memory encodedNonce = abi.encode(uint256(1)); - emit LiquidityTransferred( + emit LiquidityManager.LiquidityTransferred( type(uint64).max, i_localChainSelector, i_remoteChainSelector, @@ -212,7 +61,7 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { /// the local Liquidity manager is the bridge adapter of the remote liquidity manager /// and the other way around for the remote liquidity manager. This allows us to /// rebalance funds between the two liquidity managers on the same chain. - function test_rebalanceBetweenPoolsSuccess() external { + function test_rebalanceLiquidity_BetweenPools() external { uint256 amount = 12345670; s_liquidityManager = new LiquidityManagerHelper(s_l1Token, i_localChainSelector, s_bridgeAdapter, 0, FINANCE); @@ -273,24 +122,14 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { assertEq(s_l1Token.balanceOf(address(mockRemoteBridgeAdapter)), amount / 2); } - function test_rebalanceBetweenPoolsSuccess_AlreadyFinalized() external { + function test_rebalanceLiquidity_BetweenPools_AlreadyFinalized() external { // set up a rebalancer on another chain, an "L2". // note we use the L1 bridge adapter because it has the reverting logic // when finalization is already done. MockL1BridgeAdapter remoteBridgeAdapter = new MockL1BridgeAdapter(s_l2Token, false); - LockReleaseTokenPool remotePool = new LockReleaseTokenPool( - s_l2Token, - DEFAULT_TOKEN_DECIMALS, - new address[](0), - address(1), - true, - address(123) - ); + MockLockReleaseTokenPool remotePool = new MockLockReleaseTokenPool(s_l2Token); LiquidityManager remoteRebalancer = new LiquidityManager(s_l2Token, i_remoteChainSelector, remotePool, 0, FINANCE); - // set rebalancer role on the pool. - remotePool.setRebalancer(address(remoteRebalancer)); - // set up the cross chain rebalancer on "L1". LiquidityManager.CrossChainRebalancerArgs[] memory args = new LiquidityManager.CrossChainRebalancerArgs[](1); args[0] = ILiquidityManager.CrossChainRebalancerArgs({ @@ -325,9 +164,9 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { bytes memory bridgeSendReturnData = abi.encode(nonce); bytes memory bridgeSpecificPayload = bytes(""); vm.expectEmit(); - emit LiquidityRemoved(address(remoteRebalancer), AMOUNT); + emit ILiquidityContainer.LiquidityRemoved(address(remoteRebalancer), AMOUNT); vm.expectEmit(); - emit LiquidityTransferred( + emit LiquidityManager.LiquidityTransferred( maxSeqNum, i_remoteChainSelector, i_localChainSelector, @@ -350,12 +189,14 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { action: MockL1BridgeAdapter.FinalizationAction.ProveWithdrawal, data: abi.encode(provePayload) }); + bool fundsAvailable = s_bridgeAdapter.finalizeWithdrawERC20( address(0), address(s_liquidityManager), abi.encode(payload) ); assertFalse(fundsAvailable, "fundsAvailable must be false"); + MockL1BridgeAdapter.FinalizePayload memory finalizePayload = MockL1BridgeAdapter.FinalizePayload({ nonce: nonce, amount: AMOUNT @@ -378,16 +219,18 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { // try to finalize on L1 again // bytes memory revertData = abi.encodeWithSelector(NonceAlreadyUsed.selector, nonce); vm.expectEmit(); - emit FinalizationFailed( + emit LiquidityManager.FinalizationFailed( maxSeqNum, i_remoteChainSelector, abi.encode(payload), abi.encodeWithSelector(NonceAlreadyUsed.selector, nonce) ); + vm.expectEmit(); - emit LiquidityAdded(address(s_liquidityManager), AMOUNT); + emit ILiquidityContainer.LiquidityAdded(address(s_liquidityManager), AMOUNT); + vm.expectEmit(); - emit LiquidityTransferred( + emit LiquidityManager.LiquidityTransferred( maxSeqNum, i_remoteChainSelector, i_localChainSelector, @@ -396,6 +239,7 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { abi.encode(payload), bytes("") ); + s_liquidityManager.receiveLiquidity(i_remoteChainSelector, AMOUNT, false, abi.encode(payload)); // available balance on the rebalancer has been injected into the token pool. @@ -403,24 +247,14 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { assertEq(s_l1Token.balanceOf(address(s_lockReleaseTokenPool)), AMOUNT, "lockReleaseTokenPool balance"); } - function test_rebalanceBetweenPools_MultiStageFinalization() external { + function test_rebalanceLiquidity_RebalanceBetweenPoolsMultiStageFinalization() external { // set up a rebalancer on another chain, an "L2". // note we use the L1 bridge adapter because it has the reverting logic // when finalization is already done. MockL1BridgeAdapter remoteBridgeAdapter = new MockL1BridgeAdapter(s_l2Token, false); - LockReleaseTokenPool remotePool = new LockReleaseTokenPool( - s_l2Token, - DEFAULT_TOKEN_DECIMALS, - new address[](0), - address(1), - true, - address(123) - ); + MockLockReleaseTokenPool remotePool = new MockLockReleaseTokenPool(s_l2Token); LiquidityManager remoteRebalancer = new LiquidityManager(s_l2Token, i_remoteChainSelector, remotePool, 0, FINANCE); - // set rebalancer role on the pool. - remotePool.setRebalancer(address(remoteRebalancer)); - // set up the cross chain rebalancer on "L1". LiquidityManager.CrossChainRebalancerArgs[] memory args = new LiquidityManager.CrossChainRebalancerArgs[](1); args[0] = ILiquidityManager.CrossChainRebalancerArgs({ @@ -455,10 +289,12 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { uint64 maxSeqNum = type(uint64).max; bytes memory bridgeSendReturnData = abi.encode(nonce); bytes memory bridgeSpecificPayload = bytes(""); + vm.expectEmit(); - emit LiquidityRemoved(address(remoteRebalancer), AMOUNT); + emit ILiquidityContainer.LiquidityRemoved(address(remoteRebalancer), AMOUNT); + vm.expectEmit(); - emit LiquidityTransferred( + emit LiquidityManager.LiquidityTransferred( maxSeqNum, i_remoteChainSelector, i_localChainSelector, @@ -467,6 +303,7 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { bridgeSpecificPayload, bridgeSendReturnData ); + vm.startPrank(FINANCE); remoteRebalancer.rebalanceLiquidity(i_localChainSelector, AMOUNT, 0, bridgeSpecificPayload); @@ -476,13 +313,16 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { // prove withdrawal on the L1 bridge adapter, through the rebalancer. uint256 balanceBeforeProve = s_l1Token.balanceOf(address(s_lockReleaseTokenPool)); + MockL1BridgeAdapter.ProvePayload memory provePayload = MockL1BridgeAdapter.ProvePayload({nonce: nonce}); MockL1BridgeAdapter.Payload memory payload = MockL1BridgeAdapter.Payload({ action: MockL1BridgeAdapter.FinalizationAction.ProveWithdrawal, data: abi.encode(provePayload) }); + vm.expectEmit(); - emit FinalizationStepCompleted(maxSeqNum, i_remoteChainSelector, abi.encode(payload)); + emit LiquidityManager.FinalizationStepCompleted(maxSeqNum, i_remoteChainSelector, abi.encode(payload)); + s_liquidityManager.receiveLiquidity(i_remoteChainSelector, AMOUNT, false, abi.encode(payload)); // s_liquidityManager should have no tokens. @@ -499,14 +339,17 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { nonce: nonce, amount: AMOUNT }); + payload = MockL1BridgeAdapter.Payload({ action: MockL1BridgeAdapter.FinalizationAction.FinalizeWithdrawal, data: abi.encode(finalizePayload) }); + vm.expectEmit(); - emit LiquidityAdded(address(s_liquidityManager), AMOUNT); + emit ILiquidityContainer.LiquidityAdded(address(s_liquidityManager), AMOUNT); + vm.expectEmit(); - emit LiquidityTransferred( + emit LiquidityManager.LiquidityTransferred( maxSeqNum, i_remoteChainSelector, i_localChainSelector, @@ -515,6 +358,7 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { abi.encode(payload), bytes("") ); + s_liquidityManager.receiveLiquidity(i_remoteChainSelector, AMOUNT, false, abi.encode(payload)); // s_liquidityManager should have no tokens. @@ -527,17 +371,10 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { ); } - function test_rebalanceBetweenPools_NativeRewrap() external { + function test_rebalanceLiquidity_NativeRewrap() external { // set up a rebalancer similar to the above on another chain, an "L2". MockL1BridgeAdapter remoteBridgeAdapter = new MockL1BridgeAdapter(IERC20(address(s_l2Weth)), true); - LockReleaseTokenPool remotePool = new LockReleaseTokenPool( - IERC20(address(s_l2Weth)), - DEFAULT_TOKEN_DECIMALS, - new address[](0), - address(1), - true, - address(123) - ); + MockLockReleaseTokenPool remotePool = new MockLockReleaseTokenPool(IERC20(address(s_l2Weth))); LiquidityManager remoteRebalancer = new LiquidityManager( IERC20(address(s_l2Weth)), i_remoteChainSelector, @@ -546,9 +383,6 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { FINANCE ); - // set rebalancer role on the pool. - remotePool.setRebalancer(address(remoteRebalancer)); - // set up the cross chain rebalancer on "L1". LiquidityManager.CrossChainRebalancerArgs[] memory args = new LiquidityManager.CrossChainRebalancerArgs[](1); args[0] = ILiquidityManager.CrossChainRebalancerArgs({ @@ -592,10 +426,12 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { uint64 maxSeqNum = type(uint64).max; bytes memory bridgeSendReturnData = abi.encode(nonce); bytes memory bridgeSpecificPayload = bytes(""); + vm.expectEmit(); - emit LiquidityRemoved(address(remoteRebalancer), AMOUNT); + emit ILiquidityContainer.LiquidityRemoved(address(remoteRebalancer), AMOUNT); + vm.expectEmit(); - emit LiquidityTransferred( + emit LiquidityManager.LiquidityTransferred( maxSeqNum, i_remoteChainSelector, i_localChainSelector, @@ -604,6 +440,7 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { bridgeSpecificPayload, bridgeSendReturnData ); + remoteRebalancer.rebalanceLiquidity(i_localChainSelector, AMOUNT, 0, bridgeSpecificPayload); // available liquidity has been moved to the remote bridge adapter from the token pool. @@ -611,23 +448,26 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { assertEq(s_l2Weth.balanceOf(address(remotePool)), 0, "remotePool balance"); // prove withdrawal on the L1 bridge adapter, through the rebalancer. - uint256 balanceBeforeProve = s_l1Weth.balanceOf(address(s_wethLockReleaseTokenPool)); + uint256 balanceBeforeProve = s_l1Weth.balanceOf(address(s_wethMockLockReleaseTokenPool)); + MockL1BridgeAdapter.ProvePayload memory provePayload = MockL1BridgeAdapter.ProvePayload({nonce: nonce}); MockL1BridgeAdapter.Payload memory payload = MockL1BridgeAdapter.Payload({ action: MockL1BridgeAdapter.FinalizationAction.ProveWithdrawal, data: abi.encode(provePayload) }); + vm.expectEmit(); - emit FinalizationStepCompleted(maxSeqNum, i_remoteChainSelector, abi.encode(payload)); + emit LiquidityManager.FinalizationStepCompleted(maxSeqNum, i_remoteChainSelector, abi.encode(payload)); + s_wethRebalancer.receiveLiquidity(i_remoteChainSelector, AMOUNT, false, abi.encode(payload)); // s_wethRebalancer should have no tokens. assertEq(s_l1Weth.balanceOf(address(s_wethRebalancer)), 0, "rebalancer balance 1"); - // balance of s_wethLockReleaseTokenPool should be unchanged since no liquidity got added yet. + // balance of s_wethMockTokenPool should be unchanged since no liquidity got added yet. assertEq( - s_l1Weth.balanceOf(address(s_wethLockReleaseTokenPool)), + s_l1Weth.balanceOf(address(s_wethMockLockReleaseTokenPool)), balanceBeforeProve, - "s_wethLockReleaseTokenPool balance should be unchanged" + "s_wethMockLockReleaseTokenPool balance should be unchanged" ); // finalize withdrawal on the L1 bridge adapter, through the rebalancer. @@ -635,14 +475,17 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { nonce: nonce, amount: AMOUNT }); + payload = MockL1BridgeAdapter.Payload({ action: MockL1BridgeAdapter.FinalizationAction.FinalizeWithdrawal, data: abi.encode(finalizePayload) }); + vm.expectEmit(); - emit LiquidityAdded(address(s_wethRebalancer), AMOUNT); + emit ILiquidityContainer.LiquidityAdded(address(s_wethRebalancer), AMOUNT); + vm.expectEmit(); - emit LiquidityTransferred( + emit LiquidityManager.LiquidityTransferred( maxSeqNum, i_remoteChainSelector, i_localChainSelector, @@ -651,23 +494,24 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { abi.encode(payload), bytes("") ); + s_wethRebalancer.receiveLiquidity(i_remoteChainSelector, AMOUNT, true, abi.encode(payload)); // s_wethRebalancer should have no tokens. assertEq(s_l1Weth.balanceOf(address(s_wethRebalancer)), 0, "rebalancer balance 2"); // s_wethRebalancer should have no native tokens. assertEq(address(s_wethRebalancer).balance, 0, "rebalancer native balance should be zero"); - // balance of s_wethLockReleaseTokenPool should be updated + // balance of s_wethMockLockReleaseTokenPool should be updated assertEq( - s_l1Weth.balanceOf(address(s_wethLockReleaseTokenPool)), + s_l1Weth.balanceOf(address(s_wethMockLockReleaseTokenPool)), balanceBeforeProve + AMOUNT, - "s_wethLockReleaseTokenPool balance should be updated" + "s_wethMockLockReleaseTokenPool balance should be updated" ); } // Reverts - function test_InsufficientLiquidityReverts() external { + function test_rebalanceLiquidity_RevertWhen_InsufficientLiquidity() external { s_liquidityManager.setMinimumLiquidity(3); deal(address(s_l1Token), address(s_lockReleaseTokenPool), AMOUNT); vm.expectRevert(abi.encodeWithSelector(LiquidityManager.InsufficientLiquidity.selector, AMOUNT, AMOUNT, 3)); @@ -676,7 +520,7 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { s_liquidityManager.rebalanceLiquidity(0, AMOUNT, 0, bytes("")); } - function test_InvalidRemoteChainReverts() external { + function test_rebalanceLiquidity_RevertWhen_InvalidRemoteChain() external { deal(address(s_l1Token), address(s_lockReleaseTokenPool), AMOUNT); vm.expectRevert(abi.encodeWithSelector(LiquidityManager.InvalidRemoteChain.selector, i_remoteChainSelector)); @@ -685,273 +529,3 @@ contract LiquidityManager_rebalanceLiquidity is LiquidityManagerSetup { s_liquidityManager.rebalanceLiquidity(i_remoteChainSelector, AMOUNT, 0, bytes("")); } } - -contract LiquidityManager_setCrossChainRebalancer is LiquidityManagerSetup { - event CrossChainRebalancerSet( - uint64 indexed remoteChainSelector, - IBridgeAdapter localBridge, - address remoteToken, - address remoteRebalancer, - bool enabled - ); - - function test_setCrossChainRebalancerSuccess() external { - address newRebalancer = address(23892423); - uint64 remoteChainSelector = 12301293; - - uint64[] memory supportedChains = s_liquidityManager.getSupportedDestChains(); - assertEq(supportedChains.length, 0); - - LiquidityManager.CrossChainRebalancerArgs[] memory args = new LiquidityManager.CrossChainRebalancerArgs[](1); - args[0] = ILiquidityManager.CrossChainRebalancerArgs({ - remoteRebalancer: newRebalancer, - localBridge: s_bridgeAdapter, - remoteToken: address(190490124908), - remoteChainSelector: remoteChainSelector, - enabled: true - }); - - vm.expectEmit(); - emit CrossChainRebalancerSet( - remoteChainSelector, - args[0].localBridge, - args[0].remoteToken, - newRebalancer, - args[0].enabled - ); - - s_liquidityManager.setCrossChainRebalancers(args); - - assertEq(s_liquidityManager.getCrossChainRebalancer(remoteChainSelector).remoteRebalancer, newRebalancer); - - LiquidityManager.CrossChainRebalancerArgs[] memory got = s_liquidityManager.getAllCrossChainRebalancers(); - assertEq(got.length, 1); - assertEq(got[0].remoteRebalancer, args[0].remoteRebalancer); - assertEq(address(got[0].localBridge), address(args[0].localBridge)); - assertEq(got[0].remoteToken, args[0].remoteToken); - assertEq(got[0].remoteChainSelector, args[0].remoteChainSelector); - assertEq(got[0].enabled, args[0].enabled); - - supportedChains = s_liquidityManager.getSupportedDestChains(); - assertEq(supportedChains.length, 1); - assertEq(supportedChains[0], remoteChainSelector); - - address anotherRebalancer = address(123); - args[0].remoteRebalancer = anotherRebalancer; - - vm.expectEmit(); - emit CrossChainRebalancerSet( - remoteChainSelector, - args[0].localBridge, - args[0].remoteToken, - anotherRebalancer, - args[0].enabled - ); - - s_liquidityManager.setCrossChainRebalancer(args[0]); - - assertEq(s_liquidityManager.getCrossChainRebalancer(remoteChainSelector).remoteRebalancer, anotherRebalancer); - - supportedChains = s_liquidityManager.getSupportedDestChains(); - assertEq(supportedChains.length, 1); - assertEq(supportedChains[0], remoteChainSelector); - } - - function test_ZeroChainSelectorReverts() external { - LiquidityManager.CrossChainRebalancerArgs memory arg = ILiquidityManager.CrossChainRebalancerArgs({ - remoteRebalancer: address(9), - localBridge: s_bridgeAdapter, - remoteToken: address(190490124908), - remoteChainSelector: 0, - enabled: true - }); - - vm.expectRevert(LiquidityManager.ZeroChainSelector.selector); - - s_liquidityManager.setCrossChainRebalancer(arg); - } - - function test_ZeroAddressReverts() external { - LiquidityManager.CrossChainRebalancerArgs memory arg = ILiquidityManager.CrossChainRebalancerArgs({ - remoteRebalancer: address(0), - localBridge: s_bridgeAdapter, - remoteToken: address(190490124908), - remoteChainSelector: 123, - enabled: true - }); - - vm.expectRevert(LiquidityManager.ZeroAddress.selector); - - s_liquidityManager.setCrossChainRebalancer(arg); - - arg.remoteRebalancer = address(9); - arg.localBridge = IBridgeAdapter(address(0)); - - vm.expectRevert(LiquidityManager.ZeroAddress.selector); - - s_liquidityManager.setCrossChainRebalancer(arg); - - arg.localBridge = s_bridgeAdapter; - arg.remoteToken = address(0); - - vm.expectRevert(LiquidityManager.ZeroAddress.selector); - - s_liquidityManager.setCrossChainRebalancer(arg); - } - - function test_OnlyOwnerReverts() external { - vm.stopPrank(); - - vm.expectRevert("Only callable by owner"); - - // Test the entrypoint that takes a list - s_liquidityManager.setCrossChainRebalancers(new LiquidityManager.CrossChainRebalancerArgs[](0)); - - vm.expectRevert("Only callable by owner"); - - // Test the entrypoint that takes a single item - s_liquidityManager.setCrossChainRebalancer( - ILiquidityManager.CrossChainRebalancerArgs({ - remoteRebalancer: address(9), - localBridge: s_bridgeAdapter, - remoteToken: address(190490124908), - remoteChainSelector: 124, - enabled: true - }) - ); - } -} - -contract LiquidityManager_setLocalLiquidityContainer is LiquidityManagerSetup { - event LiquidityContainerSet(address indexed newLiquidityContainer); - - function test_setLocalLiquidityContainerSuccess() external { - LockReleaseTokenPool newPool = new LockReleaseTokenPool( - s_l1Token, - DEFAULT_TOKEN_DECIMALS, - new address[](0), - address(1), - true, - address(123) - ); - - vm.expectEmit(); - emit LiquidityContainerSet(address(newPool)); - - s_liquidityManager.setLocalLiquidityContainer(newPool); - - assertEq(s_liquidityManager.getLocalLiquidityContainer(), address(newPool)); - } - - function test_OnlyOwnerReverts() external { - vm.stopPrank(); - - vm.expectRevert("Only callable by owner"); - - s_liquidityManager.setLocalLiquidityContainer(LockReleaseTokenPool(address(1))); - } - - function test_ReverstWhen_CalledWithTheZeroAddress() external { - vm.expectRevert(LiquidityManager.ZeroAddress.selector); - s_liquidityManager.setLocalLiquidityContainer(LockReleaseTokenPool(address(0))); - } -} - -contract LiquidityManager_setMinimumLiquidity is LiquidityManagerSetup { - event MinimumLiquiditySet(uint256 oldBalance, uint256 newBalance); - - function test_setMinimumLiquiditySuccess() external { - vm.expectEmit(); - emit MinimumLiquiditySet(uint256(0), uint256(1000)); - s_liquidityManager.setMinimumLiquidity(1000); - assertEq(s_liquidityManager.getMinimumLiquidity(), uint256(1000)); - } - - function test_OnlyOwnerReverts() external { - vm.stopPrank(); - vm.expectRevert("Only callable by owner"); - s_liquidityManager.setMinimumLiquidity(uint256(1000)); - } -} - -contract LiquidityManager_setFinanceRole is LiquidityManagerSetup { - event MinimumLiquiditySet(uint256 oldBalance, uint256 newBalance); - - function test_setFinanceRoleSuccess() external { - vm.expectEmit(); - address newFinanceRole = makeAddr("newFinanceRole"); - assertEq(s_liquidityManager.getFinanceRole(), FINANCE); - emit FinanceRoleSet(newFinanceRole); - s_liquidityManager.setFinanceRole(newFinanceRole); - assertEq(s_liquidityManager.getFinanceRole(), newFinanceRole); - } - - function test_OnlyOwnerReverts() external { - vm.stopPrank(); - vm.expectRevert("Only callable by owner"); - s_liquidityManager.setFinanceRole(address(1)); - } -} - -contract LiquidityManager_withdrawNative is LiquidityManagerSetup { - event NativeWithdrawn(uint256 amount, address destination); - - address private receiver = makeAddr("receiver"); - - function setUp() public override { - super.setUp(); - vm.deal(address(s_liquidityManager), 1); - } - - function test_withdrawNative_success() external { - assertEq(receiver.balance, 0); - vm.expectEmit(); - emit NativeWithdrawn(1, receiver); - vm.startPrank(FINANCE); - s_liquidityManager.withdrawNative(1, payable(receiver)); - assertEq(receiver.balance, 1); - } - - function test_OnlyFinanceRoleReverts() external { - vm.stopPrank(); - vm.expectRevert(LiquidityManager.OnlyFinanceRole.selector); - s_liquidityManager.withdrawNative(1, payable(receiver)); - } -} - -contract LiquidityManager_receive is LiquidityManagerSetup { - event NativeDeposited(uint256 amount, address depositor); - - address private depositor = makeAddr("depositor"); - - function test_receive_success() external { - vm.deal(depositor, 100); - uint256 before = address(s_liquidityManager).balance; - vm.expectEmit(); - emit NativeDeposited(100, depositor); - vm.startPrank(depositor); - payable(address(s_liquidityManager)).transfer(100); - assertEq(address(s_liquidityManager).balance, before + 100); - } -} - -contract LiquidityManager_withdrawERC20 is LiquidityManagerSetup { - function test_withdrawERC20Success() external { - uint256 amount = 100; - deal(address(s_otherToken), address(s_liquidityManager), amount); - assertEq(s_otherToken.balanceOf(address(1)), 0); - assertEq(s_otherToken.balanceOf(address(s_liquidityManager)), amount); - vm.startPrank(FINANCE); - s_liquidityManager.withdrawERC20(address(s_otherToken), amount, address(1)); - assertEq(s_otherToken.balanceOf(address(1)), amount); - assertEq(s_otherToken.balanceOf(address(s_liquidityManager)), 0); - } - - function test_withdrawERC20Reverts() external { - uint256 amount = 100; - deal(address(s_otherToken), address(s_liquidityManager), amount); - vm.startPrank(STRANGER); - vm.expectRevert(LiquidityManager.OnlyFinanceRole.selector); - s_liquidityManager.withdrawERC20(address(s_otherToken), amount, address(1)); - } -} diff --git a/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.receive.t.sol b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.receive.t.sol new file mode 100644 index 00000000000..c7ba7619d9e --- /dev/null +++ b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.receive.t.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.24; + +import {LiquidityManagerSetup} from "./LiquidityManagerSetup.t.sol"; +import {LiquidityManager} from "../LiquidityManager.sol"; + +contract LiquidityManager_receive is LiquidityManagerSetup { + function test_receive() external { + address depositor = makeAddr("depositor"); + vm.deal(depositor, 100); + uint256 before = address(s_liquidityManager).balance; + + vm.expectEmit(); + emit LiquidityManager.NativeDeposited(100, depositor); + + changePrank(depositor); + payable(address(s_liquidityManager)).transfer(100); + + assertEq(address(s_liquidityManager).balance, before + 100); + } +} diff --git a/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.removeLiquidity.t.sol b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.removeLiquidity.t.sol new file mode 100644 index 00000000000..1db9d33d7ab --- /dev/null +++ b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.removeLiquidity.t.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.24; + +import "./LiquidityManagerSetup.t.sol"; + +contract LiquidityManager_removeLiquidity is LiquidityManagerSetup { + function test_removeLiquiditySuccess() external { + uint256 amount = 12345679; + deal(address(s_l1Token), address(s_lockReleaseTokenPool), amount); + + vm.expectEmit(); + emit LiquidityManager.LiquidityRemovedFromContainer(FINANCE, amount); + + changePrank(FINANCE); + s_liquidityManager.removeLiquidity(amount); + + assertEq(s_l1Token.balanceOf(address(s_liquidityManager)), 0); + } + + function test_removeLiquidity_RevertWhen_InsufficientLiquidity() external { + uint256 balance = 923; + uint256 requested = balance + 1; + + deal(address(s_l1Token), address(s_lockReleaseTokenPool), balance); + + vm.expectRevert(abi.encodeWithSelector(LiquidityManager.InsufficientLiquidity.selector, requested, balance, 0)); + + changePrank(FINANCE); + s_liquidityManager.removeLiquidity(requested); + } + + function test_removeLiquidity_RevertWhen_NotFinanceRole() external { + vm.stopPrank(); + + vm.expectRevert(LiquidityManager.OnlyFinanceRole.selector); + + s_liquidityManager.removeLiquidity(123); + } +} diff --git a/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.report.t.sol b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.report.t.sol new file mode 100644 index 00000000000..b971b9d90fe --- /dev/null +++ b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.report.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.24; + +import "../interfaces/ILiquidityManager.sol"; +import "./LiquidityManagerSetup.t.sol"; + +contract LiquidityManager_report is LiquidityManagerSetup { + function test_report_RevertWhen_EmptyReport() external { + ILiquidityManager.LiquidityInstructions memory instructions = ILiquidityManager.LiquidityInstructions({ + sendLiquidityParams: new ILiquidityManager.SendLiquidityParams[](0), + receiveLiquidityParams: new ILiquidityManager.ReceiveLiquidityParams[](0) + }); + + vm.expectRevert(LiquidityManager.EmptyReport.selector); + + s_liquidityManager.report(abi.encode(instructions), 123); + } +} diff --git a/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.setCrossChainRebalancer.t.sol b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.setCrossChainRebalancer.t.sol new file mode 100644 index 00000000000..67bc508238e --- /dev/null +++ b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.setCrossChainRebalancer.t.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.24; + +import {IBridgeAdapter} from "../interfaces/IBridge.sol"; +import {ILiquidityManager} from "../interfaces/ILiquidityManager.sol"; + +import {LiquidityManagerSetup} from "./LiquidityManagerSetup.t.sol"; +import {LiquidityManager} from "../LiquidityManager.sol"; + +contract LiquidityManager_setCrossChainRebalancer is LiquidityManagerSetup { + function test_setCrossChainRebalancer() external { + address newRebalancer = address(23892423); + uint64 remoteChainSelector = 12301293; + + uint64[] memory supportedChains = s_liquidityManager.getSupportedDestChains(); + assertEq(supportedChains.length, 0); + + LiquidityManager.CrossChainRebalancerArgs[] memory args = new LiquidityManager.CrossChainRebalancerArgs[](1); + args[0] = ILiquidityManager.CrossChainRebalancerArgs({ + remoteRebalancer: newRebalancer, + localBridge: s_bridgeAdapter, + remoteToken: address(190490124908), + remoteChainSelector: remoteChainSelector, + enabled: true + }); + + vm.expectEmit(); + emit LiquidityManager.CrossChainRebalancerSet( + remoteChainSelector, + args[0].localBridge, + args[0].remoteToken, + newRebalancer, + args[0].enabled + ); + + s_liquidityManager.setCrossChainRebalancers(args); + + assertEq(s_liquidityManager.getCrossChainRebalancer(remoteChainSelector).remoteRebalancer, newRebalancer); + + LiquidityManager.CrossChainRebalancerArgs[] memory crossChainRebalancers = s_liquidityManager + .getAllCrossChainRebalancers(); + assertEq(crossChainRebalancers.length, 1); + assertEq(crossChainRebalancers[0].remoteRebalancer, args[0].remoteRebalancer); + assertEq(address(crossChainRebalancers[0].localBridge), address(args[0].localBridge)); + assertEq(crossChainRebalancers[0].remoteToken, args[0].remoteToken); + assertEq(crossChainRebalancers[0].remoteChainSelector, args[0].remoteChainSelector); + assertEq(crossChainRebalancers[0].enabled, args[0].enabled); + + supportedChains = s_liquidityManager.getSupportedDestChains(); + assertEq(supportedChains.length, 1); + assertEq(supportedChains[0], remoteChainSelector); + + address anotherRebalancer = address(123); + args[0].remoteRebalancer = anotherRebalancer; + + vm.expectEmit(); + emit LiquidityManager.CrossChainRebalancerSet( + remoteChainSelector, + args[0].localBridge, + args[0].remoteToken, + anotherRebalancer, + args[0].enabled + ); + + s_liquidityManager.setCrossChainRebalancer(args[0]); + + assertEq(s_liquidityManager.getCrossChainRebalancer(remoteChainSelector).remoteRebalancer, anotherRebalancer); + + supportedChains = s_liquidityManager.getSupportedDestChains(); + assertEq(supportedChains.length, 1); + assertEq(supportedChains[0], remoteChainSelector); + } + + function test_setCrossChainRebalancer_RevertWhen_ZeroChainSelector() external { + LiquidityManager.CrossChainRebalancerArgs memory arg = ILiquidityManager.CrossChainRebalancerArgs({ + remoteRebalancer: address(9), + localBridge: s_bridgeAdapter, + remoteToken: address(190490124908), + remoteChainSelector: 0, + enabled: true + }); + + vm.expectRevert(LiquidityManager.ZeroChainSelector.selector); + + s_liquidityManager.setCrossChainRebalancer(arg); + } + + function test_setCrossChainRebalancer_RevertWhen_ZeroAddressRemoteRebalancer() external { + LiquidityManager.CrossChainRebalancerArgs memory arg = ILiquidityManager.CrossChainRebalancerArgs({ + remoteRebalancer: address(0), + localBridge: s_bridgeAdapter, + remoteToken: address(190490124908), + remoteChainSelector: 123, + enabled: true + }); + + vm.expectRevert(LiquidityManager.ZeroAddress.selector); + + s_liquidityManager.setCrossChainRebalancer(arg); + + arg.remoteRebalancer = address(9); + arg.localBridge = IBridgeAdapter(address(0)); + + vm.expectRevert(LiquidityManager.ZeroAddress.selector); + + s_liquidityManager.setCrossChainRebalancer(arg); + + arg.localBridge = s_bridgeAdapter; + arg.remoteToken = address(0); + + vm.expectRevert(LiquidityManager.ZeroAddress.selector); + + s_liquidityManager.setCrossChainRebalancer(arg); + } + + function test_setCrossChainRebalancer_RevertWhen_CallerNotOwner() external { + vm.stopPrank(); + + vm.expectRevert("Only callable by owner"); + + // Test the entrypoint that takes a list + s_liquidityManager.setCrossChainRebalancers(new LiquidityManager.CrossChainRebalancerArgs[](0)); + + vm.expectRevert("Only callable by owner"); + + // Test the entrypoint that takes a single item + s_liquidityManager.setCrossChainRebalancer( + ILiquidityManager.CrossChainRebalancerArgs({ + remoteRebalancer: address(9), + localBridge: s_bridgeAdapter, + remoteToken: address(190490124908), + remoteChainSelector: 124, + enabled: true + }) + ); + } +} diff --git a/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.setFinanceRole.t.sol b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.setFinanceRole.t.sol new file mode 100644 index 00000000000..4462f1a0f56 --- /dev/null +++ b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.setFinanceRole.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.24; + +import {LiquidityManagerSetup} from "./LiquidityManagerSetup.t.sol"; +import {LiquidityManager} from "../LiquidityManager.sol"; + +contract LiquidityManager_setFinanceRole is LiquidityManagerSetup { + function test_setFinanceRole() external { + vm.expectEmit(); + address newFinanceRole = makeAddr("newFinanceRole"); + assertEq(s_liquidityManager.getFinanceRole(), FINANCE); + emit LiquidityManager.FinanceRoleSet(newFinanceRole); + + s_liquidityManager.setFinanceRole(newFinanceRole); + + assertEq(s_liquidityManager.getFinanceRole(), newFinanceRole); + } + + function test_setFinanceRole_RevertWhen_CallerNotOwner() external { + vm.stopPrank(); + vm.expectRevert("Only callable by owner"); + + s_liquidityManager.setFinanceRole(address(1)); + } +} diff --git a/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.setLocalLiquidityContainer.t.sol b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.setLocalLiquidityContainer.t.sol new file mode 100644 index 00000000000..d5fa5368fb6 --- /dev/null +++ b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.setLocalLiquidityContainer.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.24; + +import {LiquidityManagerSetup} from "./LiquidityManagerSetup.t.sol"; +import {MockLockReleaseTokenPool} from "./mocks/MockTokenPool.sol"; +import {LiquidityManager} from "../LiquidityManager.sol"; + +contract LiquidityManager_setLocalLiquidityContainer is LiquidityManagerSetup { + function test_setLocalLiquidityContainer() external { + MockLockReleaseTokenPool newPool = new MockLockReleaseTokenPool(s_l1Token); + + vm.expectEmit(); + emit LiquidityManager.LiquidityContainerSet(address(newPool)); + + s_liquidityManager.setLocalLiquidityContainer(newPool); + + assertEq(s_liquidityManager.getLocalLiquidityContainer(), address(newPool)); + } + + function test_setLocalLiquidityContainer_RevertWhen_CallerNotOwner() external { + vm.stopPrank(); + + vm.expectRevert("Only callable by owner"); + + s_liquidityManager.setLocalLiquidityContainer(MockLockReleaseTokenPool(address(1))); + } + + function test_setLocalLiquidityContainer_RevertWhen_CalledWithTheZeroAddress() external { + vm.expectRevert(LiquidityManager.ZeroAddress.selector); + s_liquidityManager.setLocalLiquidityContainer(MockLockReleaseTokenPool(address(0))); + } +} diff --git a/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.setMinimumLiquidity.t.sol b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.setMinimumLiquidity.t.sol new file mode 100644 index 00000000000..c024b805943 --- /dev/null +++ b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.setMinimumLiquidity.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.24; + +import {LiquidityManagerSetup} from "./LiquidityManagerSetup.t.sol"; +import {LiquidityManager} from "../LiquidityManager.sol"; + +contract LiquidityManager_setMinimumLiquidity is LiquidityManagerSetup { + function test_setMinimumLiquidity() external { + vm.expectEmit(); + emit LiquidityManager.MinimumLiquiditySet(uint256(0), uint256(1000)); + + s_liquidityManager.setMinimumLiquidity(1000); + + assertEq(s_liquidityManager.getMinimumLiquidity(), uint256(1000)); + } + + function test_setMinimumLiquidity_RevertWhen_CallerNotOwner() external { + vm.stopPrank(); + vm.expectRevert("Only callable by owner"); + + s_liquidityManager.setMinimumLiquidity(uint256(1000)); + } +} diff --git a/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.withdrawERC20.t.sol b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.withdrawERC20.t.sol new file mode 100644 index 00000000000..165b3c64bf7 --- /dev/null +++ b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.withdrawERC20.t.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.24; + +import {LiquidityManagerSetup} from "./LiquidityManagerSetup.t.sol"; +import {LiquidityManager} from "../LiquidityManager.sol"; + +contract LiquidityManager_withdrawERC20 is LiquidityManagerSetup { + function test_withdrawERC20() external { + uint256 amount = 100; + deal(address(s_otherToken), address(s_liquidityManager), amount); + + assertEq(s_otherToken.balanceOf(address(1)), 0); + assertEq(s_otherToken.balanceOf(address(s_liquidityManager)), amount); + + vm.startPrank(FINANCE); + s_liquidityManager.withdrawERC20(address(s_otherToken), amount, address(1)); + + assertEq(s_otherToken.balanceOf(address(1)), amount); + assertEq(s_otherToken.balanceOf(address(s_liquidityManager)), 0); + } + + function test_withdrawERC20_RevertWhen_InvalidCondition() external { + uint256 amount = 100; + deal(address(s_otherToken), address(s_liquidityManager), amount); + + vm.startPrank(STRANGER); + vm.expectRevert(LiquidityManager.OnlyFinanceRole.selector); + + s_liquidityManager.withdrawERC20(address(s_otherToken), amount, address(1)); + } +} diff --git a/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.withdrawNative.t.sol b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.withdrawNative.t.sol new file mode 100644 index 00000000000..1cfbc32be44 --- /dev/null +++ b/contracts/src/v0.8/liquiditymanager/test/LiquidityManager.withdrawNative.t.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.24; + +import {LiquidityManager} from "../LiquidityManager.sol"; +import {LiquidityManagerSetup} from "./LiquidityManagerSetup.t.sol"; + +contract LiquidityManager_withdrawNative is LiquidityManagerSetup { + address private receiver = makeAddr("receiver"); + + function setUp() public override { + super.setUp(); + vm.deal(address(s_liquidityManager), 1); + } + + function test_withdrawNative() external { + assertEq(receiver.balance, 0); + + vm.expectEmit(); + emit LiquidityManager.NativeWithdrawn(1, receiver); + + changePrank(FINANCE); + s_liquidityManager.withdrawNative(1, payable(receiver)); + + assertEq(receiver.balance, 1); + } + + function test_withdrawNative_RevertWhen_NotFinanceRole() external { + vm.stopPrank(); + + vm.expectRevert(LiquidityManager.OnlyFinanceRole.selector); + + s_liquidityManager.withdrawNative(1, payable(receiver)); + } +} diff --git a/contracts/src/v0.8/liquiditymanager/test/LiquidityManagerBaseTest.t.sol b/contracts/src/v0.8/liquiditymanager/test/LiquidityManagerBaseTest.t.sol index 48492699473..5a7dda0607f 100644 --- a/contracts/src/v0.8/liquiditymanager/test/LiquidityManagerBaseTest.t.sol +++ b/contracts/src/v0.8/liquiditymanager/test/LiquidityManagerBaseTest.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.24; +pragma solidity ^0.8.24; import {Test} from "forge-std/Test.sol"; @@ -9,10 +9,6 @@ import {ERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/E import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; contract LiquidityManagerBaseTest is Test { - // ERC20 events - event Transfer(address indexed from, address indexed to, uint256 value); - event Approval(address indexed owner, address indexed spender, uint256 value); - IERC20 internal s_l1Token; IERC20 internal s_l2Token; IERC20 internal s_otherToken; diff --git a/contracts/src/v0.8/liquiditymanager/test/LiquidityManagerSetup.t.sol b/contracts/src/v0.8/liquiditymanager/test/LiquidityManagerSetup.t.sol new file mode 100644 index 00000000000..bcbc1ef7068 --- /dev/null +++ b/contracts/src/v0.8/liquiditymanager/test/LiquidityManagerSetup.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.24; + +import {LiquidityManager} from "../LiquidityManager.sol"; +import {MockL1BridgeAdapter} from "./mocks/MockBridgeAdapter.sol"; +import {LiquidityManagerBaseTest} from "./LiquidityManagerBaseTest.t.sol"; +import {LiquidityManagerHelper} from "./helpers/LiquidityManagerHelper.sol"; +import {MockLockReleaseTokenPool} from "./mocks/MockTokenPool.sol"; + +import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; + +contract LiquidityManagerSetup is LiquidityManagerBaseTest { + error NonceAlreadyUsed(uint256 nonce); + + LiquidityManagerHelper internal s_liquidityManager; + MockLockReleaseTokenPool internal s_lockReleaseTokenPool; + MockL1BridgeAdapter internal s_bridgeAdapter; + + // LiquidityManager that rebalances weth. + LiquidityManagerHelper internal s_wethRebalancer; + MockLockReleaseTokenPool internal s_wethMockLockReleaseTokenPool; + MockL1BridgeAdapter internal s_wethBridgeAdapter; + + function setUp() public virtual override { + LiquidityManagerBaseTest.setUp(); + + s_bridgeAdapter = new MockL1BridgeAdapter(s_l1Token, false); + + s_lockReleaseTokenPool = new MockLockReleaseTokenPool(s_l1Token); + + s_liquidityManager = new LiquidityManagerHelper( + s_l1Token, + i_localChainSelector, + s_lockReleaseTokenPool, + 0, + FINANCE + ); + + s_wethBridgeAdapter = new MockL1BridgeAdapter(IERC20(address(s_l1Weth)), true); + + s_wethMockLockReleaseTokenPool = new MockLockReleaseTokenPool(IERC20(address(s_l1Weth))); + + s_wethRebalancer = new LiquidityManagerHelper( + IERC20(address(s_l1Weth)), + i_localChainSelector, + s_wethMockLockReleaseTokenPool, + 0, + FINANCE + ); + } +} diff --git a/contracts/src/v0.8/liquiditymanager/test/mocks/MockTokenPool.sol b/contracts/src/v0.8/liquiditymanager/test/mocks/MockTokenPool.sol new file mode 100644 index 00000000000..6f190806747 --- /dev/null +++ b/contracts/src/v0.8/liquiditymanager/test/mocks/MockTokenPool.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {ILiquidityContainer} from "../../interfaces/ILiquidityContainer.sol"; + +import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; + +contract MockLockReleaseTokenPool is ILiquidityContainer { + using SafeERC20 for IERC20; + + IERC20 public immutable i_localToken; + + constructor(IERC20 localToken) { + i_localToken = localToken; + } + + /// @notice Provide additional liquidity to the container. + /// @dev Should emit LiquidityAdded + function provideLiquidity(uint256 amount) external { + i_localToken.safeTransferFrom(msg.sender, address(this), amount); + emit LiquidityAdded(msg.sender, amount); + } + + /// @notice Withdraws liquidity from the container to the msg sender + /// @dev Should emit LiquidityRemoved + function withdrawLiquidity(uint256 amount) external { + i_localToken.safeTransfer(msg.sender, amount); + emit LiquidityRemoved(msg.sender, amount); + } +}