diff --git a/audit/auditLog.json b/audit/auditLog.json index 27131b81a..c8462dcb1 100644 --- a/audit/auditLog.json +++ b/audit/auditLog.json @@ -167,6 +167,13 @@ "auditorGitHandle": "sujithsomraaj", "auditReportPath": "./audit/reports/2025.02.20_CalldataVerificationFacet(v1.3.0).pdf", "auditCommitHash": "48427d21160585f276d206f0e103ce6bd42c4c03" + }, + "audit20250228": { + "auditCompletedOn": "28.02.2025", + "auditedBy": "Sujith Somraaj (individual security researcher)", + "auditorGitHandle": "sujithsomraaj", + "auditReportPath": "./audit/reports/2025.02.28_MayanFacet(v1.1.0).pdf", + "auditCommitHash": "be250763f57596d53d19a1af0988ed3ea351148e" } }, "auditedContracts": { @@ -232,6 +239,9 @@ "LiFuelFeeCollector": { "1.0.2": ["audit20250109_3"] }, + "MayanFacet": { + "1.1.0": ["audit20250228"] + }, "Permit2Proxy": { "1.0.0": ["audit20241122"], "1.0.1": ["audit20250110_1"], diff --git a/audit/reports/2025.02.28_MayanFacet(v1.1.0).pdf b/audit/reports/2025.02.28_MayanFacet(v1.1.0).pdf new file mode 100644 index 000000000..7f524ee4e Binary files /dev/null and b/audit/reports/2025.02.28_MayanFacet(v1.1.0).pdf differ diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 5e5d74c26..18e905ae2 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -24264,6 +24264,16 @@ "SALT": "", "VERIFIED": "false" } + ], + "1.1.0": [ + { + "ADDRESS": "0x6C96d5C36d9aDBD3F4e0337D2d1E133A59288D1A", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-02-28 09:07:01", + "CONSTRUCTOR_ARGS": "0x0000000000000000000000000654874eb7f59c6f5b39931fc45dc45337c967c3", + "SALT": "", + "VERIFIED": "true" + } ] } }, diff --git a/deployments/arbitrum.diamond.staging.json b/deployments/arbitrum.diamond.staging.json index ee64127be..282d2e88c 100644 --- a/deployments/arbitrum.diamond.staging.json +++ b/deployments/arbitrum.diamond.staging.json @@ -138,12 +138,24 @@ "Version": "1.0.0" }, "0xE15C7585636e62b88bA47A40621287086E0c2E33": { - "Name": "", - "Version": "" + "Name": "DeBridgeDlnFacet", + "Version": "1.0.0" }, "0x08BfAc22A3B41637edB8A7920754fDb30B18f740": { "Name": "AcrossFacetV3", "Version": "1.1.0" + }, + "0xF82830B952Bc60b93206FA22f1cD4770cedb2840": { + "Name": "", + "Version": "" + }, + "0xDd661337B48BEA5194F6d26F2C59fF0855E15289": { + "Name": "", + "Version": "" + }, + "0x6C96d5C36d9aDBD3F4e0337D2d1E133A59288D1A": { + "Name": "MayanFacet", + "Version": "1.1.0" } }, "Periphery": { @@ -153,9 +165,8 @@ "GasZipPeriphery": "", "LiFiDEXAggregator": "", "LiFuelFeeCollector": "0x94EA56D8049e93E0308B9c7d1418Baf6A7C68280", - "Permit2Proxy": "0x6FC01BC9Ff6Cdab694Ec8Ca41B21a2F04C8c37E5", + "Permit2Proxy": "0xb33Fe241BEd9bf5F694101D7498F63a0d060F999", "ReceiverAcrossV3": "0xe4F3DEF14D61e47c696374453CD64d438FD277F8", - "Receiver": "0x36E9B2E8A627474683eF3b1E9Df26D2bF04396f3", "ReceiverStargateV2": "", "RelayerCelerIM": "0xa1Ed8783AC96385482092b82eb952153998e9b70", "TokenWrapper": "0xF63b27AE2Dc887b88f82E2Cc597d07fBB2E78E70" diff --git a/deployments/arbitrum.staging.json b/deployments/arbitrum.staging.json index 96b7f9fe6..537811337 100644 --- a/deployments/arbitrum.staging.json +++ b/deployments/arbitrum.staging.json @@ -33,9 +33,8 @@ "WormholeFacet": "0x7260Fd3F8D0bEb06fF5935C6eadE9f406107c270", "SymbiosisFacet": "0x21571D628B0bCBeb954D5933A604eCac35bAF2c7", "DeBridgeDlnFacet": "0xE15C7585636e62b88bA47A40621287086E0c2E33", - "MayanFacet": "0xd596C903d78870786c5DB0E448ce7F87A65A0daD", + "MayanFacet": "0x6C96d5C36d9aDBD3F4e0337D2d1E133A59288D1A", "StandardizedCallFacet": "0xA7ffe57ee70Ac4998e9E9fC6f17341173E081A8f", - "MayanFacet": "0xd596C903d78870786c5DB0E448ce7F87A65A0daD", "GenericSwapFacetV3": "0xFf6Fa203573Baaaa4AE375EB7ac2819d539e16FF", "CalldataVerificationFacet": "0x90B5b319cA20D9E466cB5b843952363C34d1b54E", "AcrossFacetPacked": "0x7A3770a9504924d99D38BBba4F0116B756393Eb3", diff --git a/src/Facets/MayanFacet.sol b/src/Facets/MayanFacet.sol index ab312bda9..d21dac2a5 100644 --- a/src/Facets/MayanFacet.sol +++ b/src/Facets/MayanFacet.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.17; import { ILiFi } from "../Interfaces/ILiFi.sol"; -import { LibDiamond } from "../Libraries/LibDiamond.sol"; import { LibAsset, IERC20 } from "../Libraries/LibAsset.sol"; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { LibSwap } from "../Libraries/LibSwap.sol"; @@ -10,12 +9,11 @@ import { ReentrancyGuard } from "../Helpers/ReentrancyGuard.sol"; import { SwapperV2 } from "../Helpers/SwapperV2.sol"; import { Validatable } from "../Helpers/Validatable.sol"; import { IMayan } from "../Interfaces/IMayan.sol"; -import { UnsupportedChainId } from "../Errors/GenericErrors.sol"; /// @title Mayan Facet /// @author LI.FI (https://li.fi) /// @notice Provides functionality for bridging through Mayan Bridge -/// @custom:version 1.0.0 +/// @custom:version 1.1.0 contract MayanFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { /// Storage /// @@ -244,6 +242,18 @@ contract MayanFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { // 0x8e8d142b createOrderWithToken(address,uint256,(bytes32,bytes32,uint64,uint64,uint64,uint64,uint64,[*bytes32*],uint16,bytes32,uint8,uint8,bytes32)) receiver := mload(add(protocolData, 0x144)) // MayanSwift::createOrderWithToken() } + case 0x1c59b7fc { + // 0x1c59b7fc MayanCircle::createOrder((address,uint256,uint64,bytes32,uint16,bytes32,uint64,uint64,uint64,bytes32,uint8)) + receiver := mload(add(protocolData, 0x84)) + } + case 0x9be95bb4 { + // 0x9be95bb4 MayanCircle::bridgeWithLockedFee(address,uint256,uint64,uint256,uint32,bytes32) + receiver := mload(add(protocolData, 0xc4)) + } + case 0x2072197f { + // 0x2072197f MayanCircle::bridgeWithFee(address,uint256,uint64,uint64,bytes32,uint32,uint8,bytes) + receiver := mload(add(protocolData, 0xa4)) + } default { receiver := 0x0 } diff --git a/test/solidity/Facets/MayanFacet.t.sol b/test/solidity/Facets/MayanFacet.t.sol index e30d67897..0e99012a1 100644 --- a/test/solidity/Facets/MayanFacet.t.sol +++ b/test/solidity/Facets/MayanFacet.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.17; -import { LibAllowList, TestBaseFacet, console, ERC20, LibSwap, LibAsset } from "../utils/TestBaseFacet.sol"; +import { LibAllowList, TestBaseFacet, LibSwap, LibAsset } from "../utils/TestBaseFacet.sol"; import { MayanFacet } from "lifi/Facets/MayanFacet.sol"; import { IMayan } from "lifi/Interfaces/IMayan.sol"; import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; @@ -19,19 +19,44 @@ contract TestMayanFacet is MayanFacet { } } +/// @notice This contract exposes _parseReceiver for testing purposes. +contract TestMayanFacetExposed is MayanFacet { + constructor(IMayan _mayan) MayanFacet(_mayan) {} + + /// @dev Exposes the internal _parseReceiver function. + function testParseReceiver( + bytes memory protocolData + ) public pure returns (bytes32) { + return _parseReceiver(protocolData); + } +} + contract MayanFacetTest is TestBaseFacet { MayanFacet.MayanData internal validMayanData; MayanFacet.MayanData internal validMayanDataNative; MayanFacet.MayanData internal invalidMayanDataEVM2Solana; TestMayanFacet internal mayanBridgeFacet; - IMayan internal MAYAN_FORWARDER = + IMayan internal constant MAYAN_FORWARDER = IMayan(0x0654874eb7F59C6f5b39931FC45dC45337c967c3); - address internal POLYGON_USDT = 0xc2132D05D31c914a87C6611C10748AEb04B58e8F; - address DEV_WALLET = 0x29DaCdF7cCaDf4eE67c923b4C22255A4B2494eD7; - - bytes32 ACTUAL_SOL_ADDR = + address internal constant POLYGON_USDT = + 0xc2132D05D31c914a87C6611C10748AEb04B58e8F; + address internal constant DEV_WALLET = + 0x29DaCdF7cCaDf4eE67c923b4C22255A4B2494eD7; + address internal constant NON_EVM_ADDRESS = + 0x11f111f111f111F111f111f111F111f111f111F1; + + bytes32 internal constant ACTUAL_SOL_ADDR = hex"4cb7c5f1632114c376c0e7a9a1fd1fbd562699fbd9a0c9f4f26ba8cf6e23df0d"; // [pre-commit-checker: not a secret] - bytes32 EXPECTED_SOL_ADDR = bytes32("EXPECTED ADDRESS"); + bytes32 internal constant EXPECTED_SOL_ADDR = bytes32("EXPECTED ADDRESS"); + + error InvalidReceiver(address expected, address actual); + error InvalidNonEVMReceiver(bytes32 expected, bytes32 actual); + + event BridgeToNonEVMChain( + bytes32 indexed transactionId, + uint256 indexed destinationChainId, + bytes32 receiver + ); function setUp() public { customBlockNumberForForking = 19968172; @@ -286,8 +311,8 @@ contract MayanFacetTest is TestBaseFacet { super.testBase_CanBridgeTokens_fuzzed(amount); } - function test_RevertsIfNonEVMReceiverIsIncorrect() public { - bridgeData.receiver = 0x11f111f111f111F111f111f111F111f111f111F1; + function testRevert_FailsIfNonEVMReceiverIsIncorrect() public { + bridgeData.receiver = NON_EVM_ADDRESS; validMayanData = invalidMayanDataEVM2Solana; vm.startPrank(USER_SENDER); @@ -383,4 +408,141 @@ contract MayanFacetTest is TestBaseFacet { defaultNativeAmount += 0.123456789 ether; testBase_CanSwapAndBridgeNativeTokens(); } + + function test_CanBridgeNativeTokens() public { + vm.startPrank(USER_SENDER); + // store initial balances + uint256 initialBalance = USER_SENDER.balance; + + // prepare bridgeData + bridgeData.receiver = NON_EVM_ADDRESS; + bridgeData.sendingAssetId = address(0); + bridgeData.minAmount = 1 ether; + + validMayanDataNative = MayanFacet.MayanData( + bytes32( + 0x0000000000000000000000000000000000000000000000000000000abc654321 + ), + 0xBF5f3f65102aE745A48BD521d10BaB5BF02A9eF4, // mayanProtocol address + // Calldata generated from Mayan SDK 1 ETH -> USDT on Polygon + hex"1eb1cff00000000000000000000000000000000000000000000000000000000000013e0b0000000000000000000000000000000000000000000000000000000000004df200000000000000000000000000000000000000000000000000000000000a42dfcb617b639c537bd08846f61be4481c34f9391f1b8f53d082de024e232508113e00000000000000000000000000000000000000000000000000000000000000016dfa43f824c3b8b61e715fe8bf447f2aba63e59ab537f186cf665152c2114c390000000000000000000000000000000000000000000000000000000abC654321000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000abC654321000000000000000000000000c2132d05d31c914a87c6611c10748aeb04b58e8f000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000006655d880000000000000000000000000000000000000000000000000000000006655d88000000000000000000000000000000000000000000000000000000000e16ffab40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000" + ); + + vm.expectEmit(true, true, true, true, _facetTestContractAddress); + emit BridgeToNonEVMChain( + bridgeData.transactionId, + bridgeData.destinationChainId, + validMayanDataNative.nonEVMReceiver + ); + + // execute call in child contract + initiateBridgeTxWithFacet(true); + + // check balances after call + assertEq(USER_SENDER.balance, initialBalance - 1 ether); + } + + function testRevert_FailsWhenNonEVMChainIntentionAndNonEVMReceiverIsEmpty() + public + { + vm.startPrank(USER_SENDER); + + usdc.approve(_facetTestContractAddress, type(uint256).max); + + bridgeData.receiver = NON_EVM_ADDRESS; // nonEVMAddress + + vm.expectRevert( + abi.encodeWithSelector( + InvalidNonEVMReceiver.selector, + bytes32(0), + bytes32(0) + ) + ); + + initiateBridgeTxWithFacet(false); + + vm.stopPrank(); + } + + function testRevert_FailsWhenBridgeDataReceiverDoesNotMatchMayanProtocolReceiver() + public + { + vm.startPrank(USER_SENDER); + + usdc.approve(_facetTestContractAddress, type(uint256).max); + + bridgeData.receiver = DEV_WALLET; + + validMayanData = MayanFacet.MayanData( + "", + 0xBF5f3f65102aE745A48BD521d10BaB5BF02A9eF4, // mayanProtocol address + // Calldata generated from Mayan SDK 4.12312312 USDC on Mainnet -> Arbitrum + hex"6222ad25000000000000000000000000000000000000000000000000000000000f52ae0e000000000000000000000000000000000000000000000000000000000000f2d000000000000000000000000000000000000000000000000000000000018eb30afc7fcf68097cd0584877939477347b5b8fa10efee2e29805370a35fd2a22ee9500000000000000000000000000000000000000000000000000000000000000016dfa43f824c3b8b61e715fe8bf447f2aba63e59ab537f186cf665152c2114c3900000000000000000000000029dacdf7ccadf4ee67c923b4c22255a4b2494ed700000000000000000000000000000000000000000000000000000000000000171e8c4fab8994494c8f1e5c1287445b2917d60c43c79aa959162f5d6000598d3200000000000000000000000029dacdf7ccadf4ee67c923b4c22255a4b2494ed7000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000001e00000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d000000000000000000000000000000000000000000000000393846a1e4cce00000000000000000000000000000000000000000000000000000000000667d7a7a00000000000000000000000000000000000000000000000000000000667d7a7a0000000000000000000000000000000000000000000000000000000000177f850000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000" + ); + // invalid protocolData that produces wrong receiver for payload + + vm.expectRevert( + abi.encodeWithSelector( + InvalidReceiver.selector, + DEV_WALLET, + address(0) + ) + ); + + initiateBridgeTxWithFacet(false); + + vm.stopPrank(); + } + + function test_ParseReceiver() public { + TestMayanFacetExposed testFacet = new TestMayanFacetExposed( + IMayan(address(0)) + ); + + address expectedReceiver = 0x1eB6638dE8c571c787D7bC24F98bFA735425731C; + + // test for 0x94454a5d bridgeWithFee(address,uint256,uint64,uint64,[*bytes32*],(uint32,bytes32,bytes32)) + // test for 0x32ad465f bridgeWithLockedFee(address,uint256,uint64,uint256,(uint32,[*bytes32*],bytes32)) + // test for 0xafd9b706 createOrder((address,uint256,uint64,[*bytes32*],uint16,bytes32,uint64,uint64,uint64,bytes32,uint8),(uint32,bytes32,bytes32)) + // test for 0x6111ad25 swap((uint64,uint64,uint64),(bytes32,uint16,bytes32,[*bytes32*],uint16,bytes32,bytes32),bytes32,uint16,(uint256,uint64,uint64,bool,uint64,bytes),address,uint256) + // test for 0x1eb1cff0 wrapAndSwapETH((uint64,uint64,uint64),(bytes32,uint16,bytes32,[*bytes32*],uint16,bytes32,bytes32),bytes32,uint16,(uint256,uint64,uint64,bool,uint64,bytes)) + // test for 0xb866e173 createOrderWithEth((bytes32,bytes32,uint64,uint64,uint64,uint64,uint64,[*bytes32*],uint16,bytes32,uint8,uint8,bytes32)) + // test for 0x8e8d142b createOrderWithToken(address,uint256,(bytes32,bytes32,uint64,uint64,uint64,uint64,uint64,[*bytes32*],uint16,bytes32,uint8,uint8,bytes32)) + + // test for 0x1c59b7fc MayanCircle::createOrder((address,uint256,uint64,bytes32,uint16,bytes32,uint64,uint64,uint64,bytes32,uint8)) + // example tenderly: https://dashboard.tenderly.co/tx/arbitrum/0x3bffa9aa20062cd21e0f4d40333214ce23e382d308180fc20ddd6c405bff2649/debugger?trace=0.3.0 + bytes memory protocolData = vm.parseBytes( + "0x1c59b7fc000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e583100000000000000000000000000000000000000000000000000000000004c4b4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001eb6638de8c571c787d7bc24f98bfa735425731c000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023e290000000000000000000000000000000000000000000000000000000067c1b1f500000000000000000000000000000000000000000000000000000000001c497b000000000000000000000000a5aa6e2171b416e1d27ec53ca8c13db3f91a89cd0000000000000000000000000000000000000000000000000000000000000000" + ); + bytes32 receiver = testFacet.testParseReceiver(protocolData); + assertEq( + address(uint160(uint256(receiver))), + expectedReceiver, + "parse receiver test failure for 0x1c59b7fc MayanCircle::createOrder((address,uint256,uint64,bytes32,uint16,bytes32,uint64,uint64,uint64,bytes32,uint8))" + ); + + // test for 0x9be95bb4 MayanCircle::bridgeWithLockedFee(address,uint256,uint64,uint256,uint32,bytes32) + // example tenderly: https://dashboard.tenderly.co/tx/arbitrum/0x8ad553f8059efcb7fd84130e5625e4b2fdc3ea34461227e1e4a983053e12790c/debugger?trace=0.3 + protocolData = vm.parseBytes( + "0x9be95bb4000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e583100000000000000000000000000000000000000000000000000000000004c4b40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000df8e600000000000000000000000000000000000000000000000000000000000000000000000000000000000000001eb6638de8c571c787d7bc24f98bfa735425731c" + ); + receiver = testFacet.testParseReceiver(protocolData); + assertEq( + address(uint160(uint256(receiver))), + expectedReceiver, + "parse receiver test failure for 0x9be95bb4 MayanCircle::bridgeWithLockedFee(address,uint256,uint64,uint256,uint32,bytes32)" + ); + + // test for 0x2072197f MayanCircle::bridgeWithFee(address,uint256,uint64,uint64,bytes32,uint32,uint8,bytes) + // example tenderly: https://dashboard.tenderly.co/tx/arbitrum/0xa12ac33dcc79c4185a484095764772f8169fee8228c614892843e2f8df685a98/debugger?trace=0 + protocolData = vm.parseBytes( + "0x2072197f000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e583100000000000000000000000000000000000000000000000000000000004c4b400000000000000000000000000000000000000000000000000000000000000b4400000000000000000000000000000000000000000000000000000000000000000000000000000000000000001eb6638de8c571c787d7bc24f98bfa735425731c0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000" + ); + receiver = testFacet.testParseReceiver(protocolData); + assertEq( + address(uint160(uint256(receiver))), + expectedReceiver, + "parse receiver test failure for 0x2072197f MayanCircle::bridgeWithFee(address,uint256,uint64,uint64,bytes32,uint32,uint8,bytes)" + ); + } }