diff --git a/test/solidity/Facets/AccessManagerFacet.t.sol b/test/solidity/Facets/AccessManagerFacet.t.sol index e74ced14b..6229864a5 100644 --- a/test/solidity/Facets/AccessManagerFacet.t.sol +++ b/test/solidity/Facets/AccessManagerFacet.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { AccessManagerFacet } from "lifi/Facets/AccessManagerFacet.sol"; -import { UnAuthorized } from "lifi/Errors/GenericErrors.sol"; +import { UnAuthorized, CannotAuthoriseSelf, OnlyContractOwner } from "lifi/Errors/GenericErrors.sol"; import { TestBase, LibAccess, console, LiFiDiamond } from "../utils/TestBase.sol"; contract RestrictedContract { @@ -22,12 +22,16 @@ contract AccessManagerFacetTest is TestBase { accessMgr = new AccessManagerFacet(); restricted = new RestrictedContract(); - bytes4[] memory functionSelectors = new bytes4[](1); - functionSelectors[0] = accessMgr.setCanExecute.selector; - addFacet(diamond, address(accessMgr), functionSelectors); + bytes4[] memory allowedFunctionSelectors = new bytes4[](2); + allowedFunctionSelectors[0] = accessMgr.setCanExecute.selector; + allowedFunctionSelectors[1] = accessMgr + .addressCanExecuteMethod + .selector; + addFacet(diamond, address(accessMgr), allowedFunctionSelectors); - functionSelectors[0] = restricted.restrictedMethod.selector; - addFacet(diamond, address(restricted), functionSelectors); + bytes4[] memory restrictedFunctionSelectors = new bytes4[](1); + restrictedFunctionSelectors[0] = restricted.restrictedMethod.selector; + addFacet(diamond, address(restricted), restrictedFunctionSelectors); accessMgr = AccessManagerFacet(address(diamond)); restricted = RestrictedContract(address(diamond)); @@ -36,23 +40,33 @@ contract AccessManagerFacetTest is TestBase { setFacetAddressInTestBase(address(accessMgr), "AccessManagerFacet"); } - function testAccessIsRestricted() public { + function test_AccessIsRestrictedFromNotOwner() public { + vm.startPrank(USER_SENDER); + vm.expectRevert(UnAuthorized.selector); - vm.prank(address(0xb33f)); + restricted.restrictedMethod(); + vm.stopPrank(); } - function testCanGrantAccess() public { + function test_CanGrantAccessByOwner() public { + vm.startPrank(USER_DIAMOND_OWNER); + accessMgr.setCanExecute( RestrictedContract.restrictedMethod.selector, address(0xb33f), true ); + + vm.stopPrank(); + vm.prank(address(0xb33f)); restricted.restrictedMethod(); } - function testCanRemoveAccess() public { + function test_CanRemoveAccessByOwner() public { + vm.startPrank(USER_DIAMOND_OWNER); + accessMgr.setCanExecute( RestrictedContract.restrictedMethod.selector, address(0xb33f), @@ -63,8 +77,135 @@ contract AccessManagerFacetTest is TestBase { address(0xb33f), false ); + + vm.stopPrank(); + vm.expectRevert(UnAuthorized.selector); + vm.prank(address(0xb33f)); restricted.restrictedMethod(); } + + function testRevert_CannotSetMethodAccessForAccessManager() public { + vm.startPrank(USER_DIAMOND_OWNER); + + vm.expectRevert(CannotAuthoriseSelf.selector); + + accessMgr.setCanExecute( + AccessManagerFacet.setCanExecute.selector, + address(accessMgr), + true + ); + + vm.stopPrank(); + } + + function testRevert_FailsIfNonOwnerTriesToGrantAccess() public { + vm.startPrank(USER_SENDER); + + vm.expectRevert(OnlyContractOwner.selector); + + accessMgr.setCanExecute( + AccessManagerFacet.setCanExecute.selector, + address(0xb33f), + true + ); + + vm.stopPrank(); + } + + function test_DefaultAccessIsFalse() public { + bool canExecute = accessMgr.addressCanExecuteMethod( + RestrictedContract.restrictedMethod.selector, + address(0xb33f) + ); + + assertEq(canExecute, false, "Default access should be false"); + } + + function test_CanCheckGrantedAccess() public { + vm.startPrank(USER_DIAMOND_OWNER); + + accessMgr.setCanExecute( + RestrictedContract.restrictedMethod.selector, + address(0xb33f), + true + ); + + vm.stopPrank(); + + bool canExecute = accessMgr.addressCanExecuteMethod( + RestrictedContract.restrictedMethod.selector, + address(0xb33f) + ); + + assertEq(canExecute, true, "Access should be granted"); + } + + function test_CanCheckRevokedAccess() public { + vm.startPrank(USER_DIAMOND_OWNER); + + accessMgr.setCanExecute( + RestrictedContract.restrictedMethod.selector, + address(0xb33f), + true + ); + + accessMgr.setCanExecute( + RestrictedContract.restrictedMethod.selector, + address(0xb33f), + false + ); + + vm.stopPrank(); + + bool canExecute = accessMgr.addressCanExecuteMethod( + RestrictedContract.restrictedMethod.selector, + address(0xb33f) + ); + + assertEq(canExecute, false, "Access should be revoked"); + } + + function test_DifferentMethodSelectorReturnsFalse() public { + vm.startPrank(USER_DIAMOND_OWNER); + + accessMgr.setCanExecute( + RestrictedContract.restrictedMethod.selector, + address(0xb33f), + true + ); + + vm.stopPrank(); + + bool canExecute = accessMgr.addressCanExecuteMethod( + bytes4(keccak256("anotherMethod()")), + address(0xb33f) + ); + + assertEq( + canExecute, + false, + "Different method selector should return false" + ); + } + + function test_DifferentExecutorReturnsFalse() public { + vm.startPrank(USER_DIAMOND_OWNER); + + accessMgr.setCanExecute( + RestrictedContract.restrictedMethod.selector, + address(0xb33f), + true + ); + + vm.stopPrank(); + + bool canExecute = accessMgr.addressCanExecuteMethod( + RestrictedContract.restrictedMethod.selector, + address(0xcafe) + ); + + assertEq(canExecute, false, "Different executor should return false"); + } } diff --git a/test/solidity/Facets/AcrossFacetPacked.t.sol b/test/solidity/Facets/AcrossFacetPacked.t.sol index 1b76b5696..33ba822ae 100644 --- a/test/solidity/Facets/AcrossFacetPacked.t.sol +++ b/test/solidity/Facets/AcrossFacetPacked.t.sol @@ -8,6 +8,7 @@ import { IAcrossSpokePool } from "lifi/Interfaces/IAcrossSpokePool.sol"; import { LibAsset, IERC20 } from "lifi/Libraries/LibAsset.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { TestBase } from "../utils/TestBase.sol"; +import { MockFailingContract } from "../utils/MockFailingContract.sol"; import { ERC20 } from "solmate/tokens/ERC20.sol"; import { LiFiDiamond } from "../utils/DiamondTest.sol"; import { console2 } from "forge-std/console2.sol"; @@ -616,4 +617,22 @@ contract AcrossFacetPackedTest is TestBase { ); vm.stopPrank(); } + + function testRevert_FailIfCallToExternalContractFails() public { + vm.startPrank(USER_DIAMOND_OWNER); + + MockFailingContract failingContract = new MockFailingContract(); + + vm.expectRevert(AcrossFacetPacked.WithdrawFailed.selector); + + acrossStandAlone.executeCallAndWithdraw( + address(failingContract), + WITHDRAW_REWARDS_CALLDATA, + ADDRESS_USDT, + address(this), + amountUSDT + ); + + vm.startPrank(USER_DIAMOND_OWNER); + } } diff --git a/test/solidity/Facets/AmarokFacetPacked.t.sol b/test/solidity/Facets/AmarokFacetPacked.t.sol index 816154b8d..420227052 100644 --- a/test/solidity/Facets/AmarokFacetPacked.t.sol +++ b/test/solidity/Facets/AmarokFacetPacked.t.sol @@ -47,7 +47,7 @@ contract AmarokFacetPackedTest is TestBase { amarokFacetPacked = new AmarokFacetPacked(amarok, address(this)); amarokStandAlone = new AmarokFacetPacked(amarok, address(this)); - bytes4[] memory functionSelectors = new bytes4[](9); + bytes4[] memory functionSelectors = new bytes4[](10); functionSelectors[0] = amarokFacetPacked.setApprovalForBridge.selector; functionSelectors[1] = amarokFacetPacked .startBridgeTokensViaAmarokERC20PackedPayFeeWithAsset @@ -73,6 +73,7 @@ contract AmarokFacetPackedTest is TestBase { functionSelectors[8] = amarokFacetPacked .decode_startBridgeTokensViaAmarokERC20PackedPayFeeWithNative .selector; + functionSelectors[9] = amarokFacetPacked.getChainIdForDomain.selector; // add facet to diamond addFacet(diamond, address(amarokFacetPacked), functionSelectors); @@ -412,7 +413,69 @@ contract AmarokFacetPackedTest is TestBase { assertEq(amarokData.relayerFee == defaultRelayerFee, true); } - function test_revert_cannotUseRelayerFeeAboveUint128Max_ERC20() public { + struct DomainChainTestCase { + uint32 domainId; + uint32 expectedChainId; + string description; + } + + function test_CanGetChainIdForValidDomains() public { + DomainChainTestCase[] memory testCases = new DomainChainTestCase[](8); + testCases[0] = DomainChainTestCase({ + domainId: 6648936, + expectedChainId: 1, + description: "ETH domainId should return chainId 1" + }); + testCases[1] = DomainChainTestCase({ + domainId: 1886350457, + expectedChainId: 137, + description: "POL domainId should return chainId 137" + }); + testCases[2] = DomainChainTestCase({ + domainId: 6450786, + expectedChainId: 56, + description: "BSC domainId should return chainId 56" + }); + testCases[3] = DomainChainTestCase({ + domainId: 1869640809, + expectedChainId: 10, + description: "OPT domainId should return chainId 10" + }); + testCases[4] = DomainChainTestCase({ + domainId: 6778479, + expectedChainId: 100, + description: "GNO domainId should return chainId 100" + }); + testCases[5] = DomainChainTestCase({ + domainId: 1634886255, + expectedChainId: 42161, + description: "ARB domainId should return chainId 42161" + }); + testCases[6] = DomainChainTestCase({ + domainId: 1818848877, + expectedChainId: 59144, + description: "LIN domainId should return chainId 59144" + }); + testCases[7] = DomainChainTestCase({ + domainId: 9999999, + expectedChainId: 0, + description: "Unknown domainId should return 0" + }); + + for (uint256 i = 0; i < testCases.length; i++) { + uint32 result = amarokFacetPacked.getChainIdForDomain( + testCases[i].domainId + ); + + assertEq( + result, + testCases[i].expectedChainId, + testCases[i].description + ); + } + } + + function testRevert_cannotUseRelayerFeeAboveUint128Max_ERC20() public { uint256 invalidRelayerFee = uint256(type(uint128).max) + 1; vm.expectRevert("relayerFee value passed too big to fit in uint128"); diff --git a/test/solidity/Facets/CBridge.t.sol b/test/solidity/Facets/CBridge.t.sol index f03867f4a..991eefcf1 100644 --- a/test/solidity/Facets/CBridge.t.sol +++ b/test/solidity/Facets/CBridge.t.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.17; -import { LibSwap, LibAllowList, TestBaseFacet, console, InvalidAmount } from "../utils/TestBaseFacet.sol"; +import { LibSwap, LibAllowList, TestBaseFacet, console, InvalidAmount, ERC20 } from "../utils/TestBaseFacet.sol"; import { CBridgeFacet } from "lifi/Facets/CBridgeFacet.sol"; import { ICBridge } from "lifi/Interfaces/ICBridge.sol"; +import { ContractCallNotAllowed, ExternalCallFailed, UnAuthorized } from "lifi/Errors/GenericErrors.sol"; // Stub CBridgeFacet Contract contract TestCBridgeFacet is CBridgeFacet { @@ -27,6 +28,12 @@ contract CBridgeFacetTest is TestBaseFacet { 0x5427FEFA711Eff984124bFBB1AB6fbf5E3DA1820; TestCBridgeFacet internal cBridge; + event CBridgeRefund( + address indexed _assetAddress, + address indexed _to, + uint256 amount + ); + function initiateBridgeTxWithFacet(bool isNative) internal override { // a) prepare the facet-specific data CBridgeFacet.CBridgeData memory data = CBridgeFacet.CBridgeData( @@ -71,13 +78,14 @@ contract CBridgeFacetTest is TestBaseFacet { function setUp() public { initTestBase(); cBridge = new TestCBridgeFacet(ICBridge(CBRIDGE_ROUTER)); - bytes4[] memory functionSelectors = new bytes4[](4); + bytes4[] memory functionSelectors = new bytes4[](5); functionSelectors[0] = cBridge.startBridgeTokensViaCBridge.selector; functionSelectors[1] = cBridge .swapAndStartBridgeTokensViaCBridge .selector; functionSelectors[2] = cBridge.addDex.selector; functionSelectors[3] = cBridge.setFunctionApprovalBySignature.selector; + functionSelectors[4] = cBridge.triggerRefund.selector; addFacet(diamond, address(cBridge), functionSelectors); @@ -195,6 +203,154 @@ contract CBridgeFacetTest is TestBaseFacet { vm.stopPrank(); } + function test_SucceedsWhenOwnerTriggersRefundWithExplicitReceiver() + public + assertBalanceChange(ADDRESS_USDT, USER_RECEIVER, 100_000) + { + vm.startPrank(USER_DIAMOND_OWNER); + + address callTo = CBRIDGE_ROUTER; + bytes memory callData = abi.encodeWithSignature("someFunction()"); + address assetAddress = ADDRESS_USDT; + address to = USER_RECEIVER; + uint256 amount = 100_000; + + deal(ADDRESS_USDT, address(cBridge), amount); + uint256 cBridgeBalanceBefore = ERC20(ADDRESS_USDT).balanceOf( + address(cBridge) + ); + + vm.mockCall(callTo, callData, abi.encode(true)); + + vm.expectEmit(true, true, true, true, address(cBridge)); + emit CBridgeRefund(assetAddress, to, amount); + + cBridge.triggerRefund( + payable(callTo), + callData, + assetAddress, + to, + amount + ); + + uint256 cBridgeBalanceAfter = ERC20(ADDRESS_USDT).balanceOf( + address(cBridge) + ); + + assertEq(cBridgeBalanceBefore - cBridgeBalanceAfter, amount); + + vm.stopPrank(); + } + + function test_SucceedsWhenOwnerTriggersRefundWithoutExplicitReceiver() + public + assertBalanceChange(ADDRESS_USDT, USER_DIAMOND_OWNER, 100_000) + { + vm.startPrank(USER_DIAMOND_OWNER); + + address callTo = CBRIDGE_ROUTER; + bytes memory callData = abi.encodeWithSignature("someFunction()"); + address assetAddress = ADDRESS_USDT; + address to = address(0); + uint256 amount = 100_000; + + deal(ADDRESS_USDT, address(cBridge), amount); + uint256 cBridgeBalanceBefore = ERC20(ADDRESS_USDT).balanceOf( + address(cBridge) + ); + + vm.mockCall(callTo, callData, abi.encode(true)); + + vm.expectEmit(true, true, true, true, address(cBridge)); + emit CBridgeRefund(assetAddress, USER_DIAMOND_OWNER, amount); + + cBridge.triggerRefund( + payable(callTo), + callData, + assetAddress, + to, + amount + ); + + uint256 cBridgeBalanceAfter = ERC20(ADDRESS_USDT).balanceOf( + address(cBridge) + ); + + assertEq(cBridgeBalanceBefore - cBridgeBalanceAfter, amount); + + vm.stopPrank(); + } + + function testRevert_FailsWhenTriggerRefundIsCalledByNonOwner() public { + vm.startPrank(USER_SENDER); + + address callTo = CBRIDGE_ROUTER; + bytes memory callData = abi.encodeWithSignature("someFunction()"); + address assetAddress = ADDRESS_USDT; + address to = USER_RECEIVER; + uint256 amount = 100 * 10 ** usdt.decimals(); + + vm.expectRevert(UnAuthorized.selector); + + cBridge.triggerRefund( + payable(callTo), + callData, + assetAddress, + to, + amount + ); + + vm.stopPrank(); + } + + function testRevert_FailsWhenTriggerRefundTryingToCallDiffrentContractThanCBridgeRouter() + public + { + vm.startPrank(USER_DIAMOND_OWNER); + + address callTo = address(0xdeadbeef); + bytes memory callData = abi.encodeWithSignature("someFunction()"); + address assetAddress = ADDRESS_USDT; + address to = USER_RECEIVER; + uint256 amount = 100 * 10 ** usdt.decimals(); + + vm.expectRevert(ContractCallNotAllowed.selector); + + cBridge.triggerRefund( + payable(callTo), + callData, + assetAddress, + to, + amount + ); + + vm.stopPrank(); + } + + function testRevert_FailsWhenTriggerRefundCallToCBridgeRouterFails() + public + { + vm.startPrank(USER_DIAMOND_OWNER); + + address callTo = CBRIDGE_ROUTER; // must match the expected `CBRIDGE_ROUTER` address + bytes memory callData = abi.encodeWithSignature("someFunction()"); + address assetAddress = ADDRESS_USDT; + address to = USER_RECEIVER; + uint256 amount = 100 * 10 ** usdt.decimals(); + + vm.expectRevert(ExternalCallFailed.selector); + + cBridge.triggerRefund( + payable(callTo), + callData, + assetAddress, + to, + amount + ); + + vm.stopPrank(); + } + function testBase_CanBridgeTokens_fuzzed(uint256 amount) public override { vm.assume(amount > 100 && amount < 100_000); super.testBase_CanBridgeTokens_fuzzed(amount); diff --git a/test/solidity/Facets/CBridgeFacetPacked.t.sol b/test/solidity/Facets/CBridgeFacetPacked.t.sol index b705f655f..033143210 100644 --- a/test/solidity/Facets/CBridgeFacetPacked.t.sol +++ b/test/solidity/Facets/CBridgeFacetPacked.t.sol @@ -7,6 +7,7 @@ import { ERC20 } from "solmate/tokens/ERC20.sol"; import { CBridgeFacetPacked } from "lifi/Facets/CBridgeFacetPacked.sol"; import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; import { LibAllowList, TestBase, console, LiFiDiamond } from "../utils/TestBase.sol"; +import { ContractCallNotAllowed, ExternalCallFailed, UnAuthorized } from "lifi/Errors/GenericErrors.sol"; contract MockLiquidityBridge is TestBase { function mockWithdraw(uint256 _amount) external { @@ -46,6 +47,12 @@ contract CBridgeFacetPackedTest is TestBase { uint256 amountUSDC; bytes packedUSDC; + event CBridgeRefund( + address indexed _assetAddress, + address indexed _to, + uint256 amount + ); + function setUp() public { customBlockNumberForForking = 58467500; customRpcUrlForForking = "ETH_NODE_URI_ARBITRUM"; @@ -490,4 +497,152 @@ contract CBridgeFacetPackedTest is TestBase { "Refund amount should be correct" ); } + + function test_TriggerRefundSucceedsWhenCalledByOwnerWithExplicitReceiver() + public + assertBalanceChange(ADDRESS_USDT, USER_RECEIVER, 100_000) + { + vm.startPrank(USER_DIAMOND_OWNER); + + address callTo = CBRIDGE_ROUTER; + bytes memory callData = abi.encodeWithSignature("someFunction()"); + address assetAddress = ADDRESS_USDT; + address to = USER_RECEIVER; + uint256 amount = 100_000; + + deal(ADDRESS_USDT, address(standAlone), amount); + uint256 cBridgeBalanceBefore = ERC20(ADDRESS_USDT).balanceOf( + address(standAlone) + ); + + vm.mockCall(callTo, callData, abi.encode(true)); + + vm.expectEmit(true, true, true, true, address(standAlone)); + emit CBridgeRefund(assetAddress, to, amount); + + standAlone.triggerRefund( + payable(callTo), + callData, + assetAddress, + to, + amount + ); + + uint256 cBridgeBalanceAfter = ERC20(ADDRESS_USDT).balanceOf( + address(standAlone) + ); + + assertEq(cBridgeBalanceBefore - cBridgeBalanceAfter, amount); + + vm.stopPrank(); + } + + function test_TriggerRefundSucceedsWhenCalledByOwnerWithoutExplicitReceiver() + public + assertBalanceChange(ADDRESS_USDT, USER_DIAMOND_OWNER, 100_000) + { + vm.startPrank(USER_DIAMOND_OWNER); + + address callTo = CBRIDGE_ROUTER; + bytes memory callData = abi.encodeWithSignature("someFunction()"); + address assetAddress = ADDRESS_USDT; + address to = address(0); + uint256 amount = 100_000; + + deal(ADDRESS_USDT, address(standAlone), amount); + uint256 cBridgeBalanceBefore = ERC20(ADDRESS_USDT).balanceOf( + address(standAlone) + ); + + vm.mockCall(callTo, callData, abi.encode(true)); + + vm.expectEmit(true, true, true, true, address(standAlone)); + emit CBridgeRefund(assetAddress, USER_DIAMOND_OWNER, amount); + + standAlone.triggerRefund( + payable(callTo), + callData, + assetAddress, + to, + amount + ); + + uint256 cBridgeBalanceAfter = ERC20(ADDRESS_USDT).balanceOf( + address(standAlone) + ); + + assertEq(cBridgeBalanceBefore - cBridgeBalanceAfter, amount); + + vm.stopPrank(); + } + + function testRevert_TriggerRefundFailsWhenCalledByNonOwner() public { + vm.startPrank(USER_SENDER); + + address callTo = CBRIDGE_ROUTER; + bytes memory callData = abi.encodeWithSignature("someFunction()"); + address assetAddress = ADDRESS_USDT; + address to = USER_RECEIVER; + uint256 amount = 100 * 10 ** usdt.decimals(); + + vm.expectRevert(UnAuthorized.selector); + + standAlone.triggerRefund( + payable(callTo), + callData, + assetAddress, + to, + amount + ); + + vm.stopPrank(); + } + + function testRevert_TriggerRefundFailsWhenTryingToCallDiffrentContractThanCBridgeRouter() + public + { + vm.startPrank(USER_DIAMOND_OWNER); + + address callTo = address(0xdeadbeef); + bytes memory callData = abi.encodeWithSignature("someFunction()"); + address assetAddress = ADDRESS_USDT; + address to = USER_RECEIVER; + uint256 amount = 100 * 10 ** usdt.decimals(); + + vm.expectRevert(ContractCallNotAllowed.selector); + + standAlone.triggerRefund( + payable(callTo), + callData, + assetAddress, + to, + amount + ); + + vm.stopPrank(); + } + + function testRevert_TriggerRefundFailsWhenCallToCBridgeRouterFails() + public + { + vm.startPrank(USER_DIAMOND_OWNER); + + address callTo = CBRIDGE_ROUTER; // must match the expected `CBRIDGE_ROUTER` address + bytes memory callData = abi.encodeWithSignature("someFunction()"); + address assetAddress = ADDRESS_USDT; + address to = USER_RECEIVER; + uint256 amount = 100 * 10 ** usdt.decimals(); + + vm.expectRevert(ExternalCallFailed.selector); + + standAlone.triggerRefund( + payable(callTo), + callData, + assetAddress, + to, + amount + ); + + vm.stopPrank(); + } } diff --git a/test/solidity/Facets/DeBridgeDlnFacet.t.sol b/test/solidity/Facets/DeBridgeDlnFacet.t.sol index f8e4c9510..2f4fc7c02 100644 --- a/test/solidity/Facets/DeBridgeDlnFacet.t.sol +++ b/test/solidity/Facets/DeBridgeDlnFacet.t.sol @@ -5,6 +5,7 @@ import { LibAllowList, TestBaseFacet, console, ERC20, LibSwap } from "../utils/T import { DeBridgeDlnFacet } from "lifi/Facets/DeBridgeDlnFacet.sol"; import { IDlnSource } from "lifi/Interfaces/IDlnSource.sol"; import { stdJson } from "forge-std/StdJson.sol"; +import { NotInitialized, OnlyContractOwner } from "src/Errors/GenericErrors.sol"; // Stub DeBridgeDlnFacet Contract contract TestDeBridgeDlnFacet is DeBridgeDlnFacet { @@ -30,13 +31,17 @@ contract DeBridgeDlnFacetTest is TestBaseFacet { // Errors error EmptyNonEVMAddress(); + error UnknownDeBridgeChain(); + + // Events + event DeBridgeChainIdSet(uint256 indexed chainId, uint256 deBridgeChainId); function setUp() public { customBlockNumberForForking = 19279222; initTestBase(); deBridgeDlnFacet = new TestDeBridgeDlnFacet(DLN_SOURCE); - bytes4[] memory functionSelectors = new bytes4[](5); + bytes4[] memory functionSelectors = new bytes4[](7); functionSelectors[0] = deBridgeDlnFacet .startBridgeTokensViaDeBridgeDln .selector; @@ -47,7 +52,9 @@ contract DeBridgeDlnFacetTest is TestBaseFacet { functionSelectors[3] = deBridgeDlnFacet .setFunctionApprovalBySignature .selector; - functionSelectors[4] = DeBridgeDlnFacet.initDeBridgeDln.selector; + functionSelectors[4] = deBridgeDlnFacet.setDeBridgeChainId.selector; + functionSelectors[5] = deBridgeDlnFacet.getDeBridgeChainId.selector; + functionSelectors[6] = DeBridgeDlnFacet.initDeBridgeDln.selector; addFacet(diamond, address(deBridgeDlnFacet), functionSelectors); deBridgeDlnFacet = TestDeBridgeDlnFacet(address(diamond)); @@ -338,4 +345,116 @@ contract DeBridgeDlnFacetTest is TestBaseFacet { vm.expectRevert(EmptyNonEVMAddress.selector); initiateSwapAndBridgeTxWithFacet(true); } + + function test_CanSetDeBridgeChainIdFromOwner() public { + vm.startPrank(USER_DIAMOND_OWNER); + + uint256 chainId = 137; // example chain ID + uint256 deBridgeChainId = 1234; // mapped chain ID + + vm.expectEmit(true, true, true, true); + + emit DeBridgeChainIdSet(chainId, deBridgeChainId); + + deBridgeDlnFacet.setDeBridgeChainId(chainId, deBridgeChainId); + + assertEq( + deBridgeDlnFacet.getDeBridgeChainId(chainId), + deBridgeChainId + ); + + vm.stopPrank(); + } + + function testRevert_FailToSetDeBridgeChainIdFromNotOwner() public { + vm.startPrank(USER_SENDER); + + uint256 chainId = 137; + uint256 deBridgeChainId = 1234; + + vm.expectRevert(OnlyContractOwner.selector); + + deBridgeDlnFacet.setDeBridgeChainId(chainId, deBridgeChainId); + + vm.stopPrank(); + } + + function testRevert_FailsToSetDeBridgeChainIdIfNotInitialized() public { + vm.startPrank(address(0)); // address zero because facet is not set in the diamond so current contract owner is address zero + + TestDeBridgeDlnFacet uninitializedFacet = new TestDeBridgeDlnFacet( + DLN_SOURCE + ); + + vm.expectRevert(NotInitialized.selector); + + uninitializedFacet.setDeBridgeChainId(137, 1001); + + vm.stopPrank(); + } + + function test_CanGetDeBridgeChainId() public { + vm.startPrank(USER_DIAMOND_OWNER); + + uint256 chainId = 137; + uint256 deBridgeChainId = 1234; + + deBridgeDlnFacet.setDeBridgeChainId(chainId, deBridgeChainId); + + vm.stopPrank(); + + uint256 returnedChainId = deBridgeDlnFacet.getDeBridgeChainId(chainId); + + assertEq(returnedChainId, deBridgeChainId); + } + + function testRevert_FailsIfAttemptingToGetUnknownDeBridgeChainId() public { + vm.startPrank(USER_DIAMOND_OWNER); + + uint256 unknownChainId = 999999; + + vm.expectRevert(UnknownDeBridgeChain.selector); + + deBridgeDlnFacet.getDeBridgeChainId(unknownChainId); + + vm.stopPrank(); + } + + function testRevert_FailsIfAttemptingToStartBridgingWithEmptyReceiverAddress() + public + { + vm.startPrank(USER_SENDER); + + validDeBridgeDlnData.receiver = ""; // empty receiver + + vm.expectRevert(EmptyNonEVMAddress.selector); + + deBridgeDlnFacet.startBridgeTokensViaDeBridgeDln{ value: FIXED_FEE }( + bridgeData, + validDeBridgeDlnData + ); + + vm.stopPrank(); + } + + function test_CanGetStorage() public { + bytes32 namespace = keccak256("com.lifi.facets.debridgedln"); + + uint256 chainIdKey = 1; + uint256 expectedValue = 42; + + // compute the correct storage slot for `deBridgeChainId[chainIdKey]` + bytes32 storageSlot = keccak256(abi.encode(chainIdKey, namespace)); + + // store the test value in the computed slot + vm.store( + address(deBridgeDlnFacet), + storageSlot, + bytes32(expectedValue) + ); + + uint256 storedValue = deBridgeDlnFacet.getDeBridgeChainId(chainIdKey); + + assertEq(storedValue, expectedValue); + } } diff --git a/test/solidity/Facets/DexManagerFacet.t.sol b/test/solidity/Facets/DexManagerFacet.t.sol index ea4e1fd3d..e927c5201 100644 --- a/test/solidity/Facets/DexManagerFacet.t.sol +++ b/test/solidity/Facets/DexManagerFacet.t.sol @@ -7,13 +7,14 @@ import { DiamondTest, LiFiDiamond } from "../utils/DiamondTest.sol"; import { Vm } from "forge-std/Vm.sol"; import { DexManagerFacet } from "lifi/Facets/DexManagerFacet.sol"; import { AccessManagerFacet } from "lifi/Facets/AccessManagerFacet.sol"; -import { InvalidContract, OnlyContractOwner, CannotAuthoriseSelf, UnAuthorized } from "lifi/Errors/GenericErrors.sol"; +import { InvalidContract, OnlyContractOwner, CannotAuthoriseSelf, UnAuthorized, OnlyContractOwner } from "lifi/Errors/GenericErrors.sol"; contract Foo {} contract DexManagerFacetTest is DSTest, DiamondTest { address internal constant USER_PAUSER = address(0xdeadbeef); address internal constant USER_DIAMOND_OWNER = address(0x123456); + address internal constant NOT_DIAMOND_OWNER = address(0xabc123456); LiFiDiamond internal diamond; DexManagerFacet internal dexMgr; @@ -55,23 +56,33 @@ contract DexManagerFacetTest is DSTest, DiamondTest { accessMgr = AccessManagerFacet(address(diamond)); dexMgr = DexManagerFacet(address(diamond)); - vm.startPrank(USER_DIAMOND_OWNER); } - function testCanAddDEX() public { + function test_SucceedsIfOwnerAddsDex() public { + vm.startPrank(USER_DIAMOND_OWNER); + dexMgr.addDex(address(c1)); address[] memory approved = dexMgr.approvedDexs(); assertEq(approved[0], address(c1)); + + vm.stopPrank(); } - function testCanRemoveDEX() public { + function test_SucceedsIfOwnerRemovesDex() public { + vm.startPrank(USER_DIAMOND_OWNER); + dexMgr.addDex(address(c1)); dexMgr.removeDex(address(c1)); + + vm.stopPrank(); + address[] memory approved = dexMgr.approvedDexs(); assertEq(approved.length, 0); } - function testCanBatchAddDEXs() public { + function test_SucceedsIfOwnerBatchAddsDexes() public { + vm.startPrank(USER_DIAMOND_OWNER); + address[] memory dexs = new address[](3); dexs[0] = address(c1); dexs[1] = address(c2); @@ -82,9 +93,13 @@ contract DexManagerFacetTest is DSTest, DiamondTest { assertEq(approved[1], dexs[1]); assertEq(approved[2], dexs[2]); assertEq(approved.length, 3); + + vm.stopPrank(); } - function testCanBatchRemoveDEXs() public { + function test_SucceedsIfOwnerBatchRemovesDexes() public { + vm.startPrank(USER_DIAMOND_OWNER); + address[] memory dexs = new address[](3); dexs[0] = address(c1); dexs[1] = address(c2); @@ -99,15 +114,23 @@ contract DexManagerFacetTest is DSTest, DiamondTest { address[] memory approved = dexMgr.approvedDexs(); assertEq(approved.length, 1); assertEq(approved[0], dexs[2]); + + vm.stopPrank(); } - function testCanApproveFunctionSignature() public { + function test_SucceedsIfOwnerApprovesFunctionSignature() public { + vm.startPrank(USER_DIAMOND_OWNER); + bytes4 signature = hex"faceface"; dexMgr.setFunctionApprovalBySignature(signature, true); assertTrue(dexMgr.isFunctionApproved(signature)); + + vm.stopPrank(); } - function testCanApproveBatchFunctionSignature() public { + function test_SucceedsIfOwnerBatchApprovesFunctionSignatures() public { + vm.startPrank(USER_DIAMOND_OWNER); + bytes4[] memory signatures = new bytes4[](5); signatures[0] = bytes4(hex"faceface"); signatures[1] = bytes4(hex"deadbeef"); @@ -121,32 +144,33 @@ contract DexManagerFacetTest is DSTest, DiamondTest { ++i; } } - } - function testRevert_CannotAddZeroAddress() public { - vm.expectRevert(InvalidContract.selector); - - dexMgr.addDex(address(0)); + vm.stopPrank(); } - function testRevert_NonOwnerCannotAddAddress() public { - vm.stopPrank(); - vm.startPrank(USER_PAUSER); // not the owner + function testRevert_FailsIfAddingDexWithZeroAddress() public { + vm.startPrank(USER_DIAMOND_OWNER); - vm.expectRevert(UnAuthorized.selector); + vm.expectRevert(InvalidContract.selector); dexMgr.addDex(address(0)); vm.stopPrank(); } - function testRevert_CannotAddNonContract() public { + function testRevert_FailsIfAddingDexThatIsNotAContract() public { + vm.startPrank(USER_DIAMOND_OWNER); + vm.expectRevert(InvalidContract.selector); dexMgr.addDex(address(1337)); + + vm.stopPrank(); } - function testRevert_CannotBatchAddZeroAddress() public { + function testRevert_FailsIfBatchAddingDexsWithZeroAddress() public { + vm.startPrank(USER_DIAMOND_OWNER); + address[] memory dexs = new address[](3); dexs[0] = address(c1); dexs[1] = address(c2); @@ -155,28 +179,149 @@ contract DexManagerFacetTest is DSTest, DiamondTest { vm.expectRevert(InvalidContract.selector); dexMgr.batchAddDex(dexs); + + vm.stopPrank(); } - function testRevert_CannotBatchAddSelf() public { + function testRevert_FailsIfBatchAddingDexsThatAreNotAContracts() public { + vm.startPrank(USER_DIAMOND_OWNER); + address[] memory dexs = new address[](3); dexs[0] = address(c1); dexs[1] = address(c2); - dexs[2] = address(dexMgr); + dexs[2] = address(1337); - vm.expectRevert(CannotAuthoriseSelf.selector); + vm.expectRevert(InvalidContract.selector); dexMgr.batchAddDex(dexs); + + vm.stopPrank(); } - function testRevert_CannotBatchAddNonContract() public { - address[] memory dexs = new address[](3); + function testRevert_FailsIfNonOwnerTriesToAddDex() public { + vm.startPrank(NOT_DIAMOND_OWNER); // prank a non-owner to attempt adding a DEX + + vm.expectRevert(UnAuthorized.selector); + + dexMgr.addDex(address(c1)); + + vm.stopPrank(); + } + + function testRevert_FailsIfNonOwnerTriesToBatchAddDex() public { + vm.startPrank(NOT_DIAMOND_OWNER); + address[] memory dexs = new address[](2); dexs[0] = address(c1); dexs[1] = address(c2); - dexs[2] = address(1337); - vm.expectRevert(InvalidContract.selector); + vm.expectRevert(UnAuthorized.selector); dexMgr.batchAddDex(dexs); + + vm.stopPrank(); + } + + function testRevert_FailsIfAddingDexThatIsDexManager() public { + vm.startPrank(USER_DIAMOND_OWNER); + + address[] memory dexs = new address[](2); + dexs[0] = address(c1); + dexs[1] = address(dexMgr); // contract itself + + vm.expectRevert(CannotAuthoriseSelf.selector); + + dexMgr.batchAddDex(dexs); + + vm.stopPrank(); + } + + function testRevert_FailsIfNonOwnerTriesToRemoveDex() public { + vm.prank(USER_DIAMOND_OWNER); + + dexMgr.addDex(address(c1)); + + vm.stopPrank(); + + vm.expectRevert(UnAuthorized.selector); + + vm.prank(NOT_DIAMOND_OWNER); + dexMgr.removeDex(address(c1)); + } + + function testRevert_FailsIfNonOwnerTriesToBatchRemoveDex() public { + address[] memory dexs = new address[](2); + dexs[0] = address(c1); + dexs[1] = address(c2); + + vm.prank(USER_DIAMOND_OWNER); + dexMgr.batchAddDex(dexs); + + vm.expectRevert(UnAuthorized.selector); + + vm.prank(NOT_DIAMOND_OWNER); + dexMgr.batchRemoveDex(dexs); + } + + function testRevert_FailsIfNonOwnerTriesToSetFunctionApprovalBySignature() + public + { + bytes4 signature = hex"faceface"; + + vm.expectRevert(UnAuthorized.selector); + + vm.prank(NOT_DIAMOND_OWNER); + dexMgr.setFunctionApprovalBySignature(signature, true); + } + + function testRevert_FailsIfNonOwnerTriesToBatchSetFunctionApprovalBySignature() + public + { + bytes4[] memory signatures = new bytes4[](3); + signatures[0] = bytes4(hex"faceface"); + signatures[1] = bytes4(hex"deadbeef"); + signatures[2] = bytes4(hex"beefbeef"); + + vm.expectRevert(UnAuthorized.selector); + + vm.prank(NOT_DIAMOND_OWNER); + dexMgr.batchSetFunctionApprovalBySignature(signatures, true); + } + + function test_SucceedsIfOwnerSetsFunctionApprovalBySignature() public { + vm.startPrank(USER_DIAMOND_OWNER); + + bytes4 signature = hex"faceface"; + + dexMgr.setFunctionApprovalBySignature(signature, true); + assertTrue(dexMgr.isFunctionApproved(signature)); + + dexMgr.setFunctionApprovalBySignature(signature, false); + assertFalse(dexMgr.isFunctionApproved(signature)); + + vm.stopPrank(); + } + + function test_SucceedsIfOwnerBatchSetsFunctionApprovalBySignature() + public + { + vm.startPrank(USER_DIAMOND_OWNER); + + bytes4[] memory signatures = new bytes4[](3); + signatures[0] = bytes4(hex"faceface"); + signatures[1] = bytes4(hex"deadbeef"); + signatures[2] = bytes4(hex"beefbeef"); + + dexMgr.batchSetFunctionApprovalBySignature(signatures, true); + for (uint256 i = 0; i < 3; i++) { + assertTrue(dexMgr.isFunctionApproved(signatures[i])); + } + + dexMgr.batchSetFunctionApprovalBySignature(signatures, false); + for (uint256 i = 0; i < 3; i++) { + assertFalse(dexMgr.isFunctionApproved(signatures[i])); + } + + vm.stopPrank(); } function test_AllowsWhitelistedAddressToAddContract() public { diff --git a/test/solidity/utils/MockFailingContract.sol b/test/solidity/utils/MockFailingContract.sol new file mode 100644 index 000000000..0d2adf896 --- /dev/null +++ b/test/solidity/utils/MockFailingContract.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +contract MockFailingContract { + fallback() external payable { + revert("Always fails"); + } +}