diff --git a/src/debridge/DebridgeHelper.sol b/src/debridge/DebridgeHelper.sol new file mode 100644 index 0000000..3b8411b --- /dev/null +++ b/src/debridge/DebridgeHelper.sol @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +/// library imports +import "forge-std/Test.sol"; +import {IDebridgeGate} from "./interfaces/IDebridgeGate.sol"; +import {DeBridgeSignatureVerifierMock} from "./mocks/DeBridgeSignatureVerifierMock.sol"; +import {console} from "forge-std/console.sol"; + +/// @title Debridge Helper +/// @notice helps simulate Debridge message relaying +contract DebridgeHelper is Test { + bytes32 constant DebridgeSend = keccak256( + "Sent(bytes32,bytes32,uint256,bytes,uint256,uint256,uint32,(uint256,uint256,uint256,bool,bool),bytes,address)" + ); + + struct HelpArgs { + address gate; + uint256 forkId; + uint256 destinationChainId; + bytes32 eventSelector; + Vm.Log[] logs; + } + + struct LocalVars { + uint256 prevForkId; + uint256 originChainId; + uint256 destinationChainId; + DebridgeLogData logData; + } + + struct DebridgeLogData { + bytes32 submissionId; + bytes32 debridgeId; + uint256 amount; + bytes receiver; + uint256 nonce; + uint256 chainIdTo; + uint32 referralCode; + IDebridgeGate.FeeParams feeParams; + bytes autoParams; + address nativeSender; + } + + ////////////////////////////////////////////////////////////// + // EXTERNAL FUNCTIONS // + ////////////////////////////////////////////////////////////// + /// @notice helps process multiple destination messages to relay + /// @param deBridgeGate represents the deBridge gate on the source chain + /// @param forkIds represents the destination chain fork ids + /// @param destinationChainIds represents the destination chain ids + /// @param debridgeGateAdmins represents the admin of the debridge gate + /// @param logs represents the recorded message logs + function help( + address deBridgeGate, + uint256[] memory forkIds, + uint256[] memory destinationChainIds, + address[] memory debridgeGateAdmins, + Vm.Log[] calldata logs + ) external { + uint256 chains = destinationChainIds.length; + for (uint256 i; i < chains;) { + _help( + HelpArgs({ + gate: deBridgeGate, + forkId: forkIds[i], + destinationChainId: destinationChainIds[i], + eventSelector: DebridgeSend, + logs: logs + }), + debridgeGateAdmins[i] + ); + unchecked { + ++i; + } + } + } + /// @notice helps process multiple destination messages to relay + /// @param deBridgeGate represents the deBridge gate on the source chain + /// @param forkIds represents the destination chain fork ids + /// @param destinationChainIds represents the destination chain ids + /// @param debridgeGateAdmins represents the admin of the debridge gate + /// @param eventSelector represents a custom event selector + /// @param logs represents the recorded message logs + + function help( + address deBridgeGate, + uint256[] memory forkIds, + uint256[] memory destinationChainIds, + address[] memory debridgeGateAdmins, + bytes32 eventSelector, + Vm.Log[] calldata logs + ) external { + uint256 chains = destinationChainIds.length; + for (uint256 i; i < chains;) { + _help( + HelpArgs({ + gate: deBridgeGate, + forkId: forkIds[i], + destinationChainId: destinationChainIds[i], + eventSelector: eventSelector, + logs: logs + }), + debridgeGateAdmins[i] + ); + unchecked { + ++i; + } + } + } + + /// @notice helps process single destination message to relay + /// @param debridgeGateAdmin represents the admin of the debridge gate + /// @param deBridgeGate represents the deBridge gate on the source chain + /// @param forkId represents the destination chain fork id + /// @param destinationChainId represents the destination chain id + /// @param logs represents the recorded message logs + function help( + address debridgeGateAdmin, + address deBridgeGate, + uint256 forkId, + uint256 destinationChainId, + Vm.Log[] calldata logs + ) external { + _help( + HelpArgs({ + gate: deBridgeGate, + forkId: forkId, + destinationChainId: destinationChainId, + eventSelector: DebridgeSend, + logs: logs + }), + debridgeGateAdmin + ); + } + + /// @notice helps process single destination message to relay + /// @param debridgeGateAdmin represents the admin of the debridge gate + /// @param deBridgeGate represents the deBridge gate on the source chain + /// @param forkId represents the destination chain fork id + /// @param destinationChainId represents the destination chain id + /// @param eventSelector represents a custom event selector + /// @param logs represents the recorded message logs + function help( + address debridgeGateAdmin, + address deBridgeGate, + uint256 forkId, + uint256 destinationChainId, + bytes32 eventSelector, + Vm.Log[] calldata logs + ) external { + _help( + HelpArgs({ + gate: deBridgeGate, + forkId: forkId, + destinationChainId: destinationChainId, + eventSelector: eventSelector, + logs: logs + }), + debridgeGateAdmin + ); + } + + /// @notice internal function to process a single destination message to relay + /// @param args represents the help arguments + function _help(HelpArgs memory args, address debridgeGateAdmin) internal { + LocalVars memory vars; + vars.originChainId = uint256(block.chainid); + vars.prevForkId = vm.activeFork(); + vm.selectFork(args.forkId); + + uint256 count = args.logs.length; + for (uint256 i; i < count;) { + // https://docs.debridge.finance/the-debridge-messaging-protocol/development-guides/building-an-evm-based-dapp/evm-smart-contract-interfaces + // DeBridgeSend is the event selector for the Sent event emitted by the DeBridge gate contract + // Requests must be filled using the `.claim()` function of the DeBridge gate contract. + if (args.logs[i].topics[0] == args.eventSelector && args.logs[i].emitter == args.gate) { + vars.destinationChainId = uint256(args.logs[i].topics[2]); + + if (vars.destinationChainId == args.destinationChainId) { + DebridgeLogData memory logData = + _decodeLogData(args.logs[i], args.logs[i].topics[1], vars.destinationChainId); + vars.logData = logData; + + // simulate signature verification + // -- create verification contract + DeBridgeSignatureVerifierMock _verifier = new DeBridgeSignatureVerifierMock(); + // -- need to overwrite the signature verifier address + vm.startPrank(debridgeGateAdmin); + IDebridgeGate(args.gate).setSignatureVerifier(address(_verifier)); + vm.stopPrank(); + + IDebridgeGate.DebridgeInfo memory debridgeInfo = + IDebridgeGate(args.gate).getDebridge(logData.debridgeId); + deal(debridgeInfo.tokenAddress, args.gate, logData.amount); + + address receiver = address(bytes20(logData.receiver)); + IDebridgeGate(args.gate).claim( + logData.debridgeId, + logData.amount, + vars.originChainId, + receiver, + logData.nonce, + "", + logData.autoParams + ); + } + } + + unchecked { + ++i; + } + } + + vm.selectFork(vars.prevForkId); + } + + function _decodeLogData(Vm.Log memory log, bytes32 debridgeId, uint256 chainIdTo) + internal + pure + returns (DebridgeLogData memory data) + { + ( + bytes32 submissionId, + uint256 amount, + bytes memory receiver, + uint256 nonce, + uint32 referralCode, + IDebridgeGate.FeeParams memory feeParams, + bytes memory autoParams, + address nativeSender + ) = abi.decode(log.data, (bytes32, uint256, bytes, uint256, uint32, IDebridgeGate.FeeParams, bytes, address)); + + return DebridgeLogData({ + submissionId: submissionId, + debridgeId: debridgeId, + amount: amount, + receiver: receiver, + nonce: nonce, + chainIdTo: chainIdTo, + referralCode: referralCode, + feeParams: feeParams, + autoParams: autoParams, + nativeSender: nativeSender + }); + } +} diff --git a/src/debridge/interfaces/IDebridgeGate.sol b/src/debridge/interfaces/IDebridgeGate.sol new file mode 100644 index 0000000..f32a105 --- /dev/null +++ b/src/debridge/interfaces/IDebridgeGate.sol @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +interface IDebridgeGate { + error FeeProxyBadRole(); + error FeeContractUpdaterBadRole(); + error AdminBadRole(); + error GovMonitoringBadRole(); + error DebridgeNotFound(); + + error WrongChainTo(); + error WrongChainFrom(); + error WrongArgument(); + error WrongAutoArgument(); + + error TransferAmountTooHigh(); + + error NotSupportedFixedFee(); + error TransferAmountNotCoverFees(); + error InvalidTokenToSend(); + + error SubmissionUsed(); + error SubmissionBlocked(); + + error AssetAlreadyExist(); + error ZeroAddress(); + + error ProposedFeeTooHigh(); + + error NotEnoughReserves(); + error EthTransferFailed(); + + /* ========== STRUCTS ========== */ + struct TokenInfo { + uint256 nativeChainId; + bytes nativeAddress; + } + + struct DebridgeInfo { + uint256 chainId; // native chain id + uint256 maxAmount; // maximum amount to transfer + uint256 balance; // total locked assets + uint256 lockedInStrategies; // total locked assets in strategy (AAVE, Compound, etc) + address tokenAddress; // asset address on the current chain + uint16 minReservesBps; // minimal hot reserves in basis points (1/10000) + bool exist; + } + + struct DebridgeFeeInfo { + uint256 collectedFees; // total collected fees + uint256 withdrawnFees; // fees that already withdrawn + mapping(uint256 => uint256) getChainFee; // whether the chain for the asset is supported + } + + struct ChainSupportInfo { + uint256 fixedNativeFee; // transfer fixed fee + bool isSupported; // whether the chain for the asset is supported + uint16 transferFeeBps; // transfer fee rate nominated in basis points (1/10000) of transferred amount + } + + struct DiscountInfo { + uint16 discountFixBps; // fix discount in BPS + uint16 discountTransferBps; // transfer % discount in BPS + } + + /// @param executionFee Fee paid to the transaction executor. + /// @param fallbackAddress Receiver of the tokens if the call fails. + struct SubmissionAutoParamsTo { + uint256 executionFee; + uint256 flags; + bytes fallbackAddress; + bytes data; + } + + /// @param executionFee Fee paid to the transaction executor. + /// @param fallbackAddress Receiver of the tokens if the call fails. + struct SubmissionAutoParamsFrom { + uint256 executionFee; + uint256 flags; + address fallbackAddress; + bytes data; + bytes nativeSender; + } + + struct FeeParams { + uint256 receivedAmount; + uint256 fixFee; + uint256 transferFee; + bool useAssetFee; + bool isNativeToken; + } + + /* ========== PUBLIC VARS GETTERS ========== */ + + /// @dev Returns whether the transfer with the submissionId was claimed. + /// submissionId is generated in getSubmissionIdFrom + function isSubmissionUsed(bytes32 submissionId) external view returns (bool); + + /// @dev Returns native token info by wrapped token address + function getNativeInfo(address token) external view returns (uint256 nativeChainId, bytes memory nativeAddress); + + /// @dev Returns address of the proxy to execute user's calls. + function callProxy() external view returns (address); + + /// @dev Fallback fixed fee in native asset, used if a chain fixed fee is set to 0 + function globalFixedNativeFee() external view returns (uint256); + + /// @dev Fallback transfer fee in BPS, used if a chain transfer fee is set to 0 + function globalTransferFeeBps() external view returns (uint16); + + function getDebridge(bytes32 debridgeId) external view returns (DebridgeInfo memory); + + /* ========== FUNCTIONS ========== */ + function setSignatureVerifier(address _verifier) external; + + /// @dev Submits the message to the deBridge infrastructure to be broadcasted to another supported blockchain (identified by _dstChainId) + /// with the instructions to call the _targetContractAddress contract using the given _targetContractCalldata + /// @notice NO ASSETS ARE BROADCASTED ALONG WITH THIS MESSAGE + /// @notice DeBridgeGate only accepts submissions with msg.value (native ether) covering a small protocol fee + /// (defined in the globalFixedNativeFee property). Any excess amount of ether passed to this function is + /// included in the message as the execution fee - the amount deBridgeGate would give as an incentive to + /// a third party in return for successful claim transaction execution on the destination chain. + /// @notice DeBridgeGate accepts a set of flags that control the behaviour of the execution. This simple method + /// sets the default set of flags: REVERT_IF_EXTERNAL_FAIL, PROXY_WITH_SENDER + /// @param _dstChainId ID of the destination chain. + /// @param _targetContractAddress A contract address to be called on the destination chain + /// @param _targetContractCalldata Calldata to execute against the target contract on the destination chain + function sendMessage(uint256 _dstChainId, bytes memory _targetContractAddress, bytes memory _targetContractCalldata) + external + payable + returns (bytes32 submissionId); + + /// @dev Submits the message to the deBridge infrastructure to be broadcasted to another supported blockchain (identified by _dstChainId) + /// with the instructions to call the _targetContractAddress contract using the given _targetContractCalldata + /// @notice NO ASSETS ARE BROADCASTED ALONG WITH THIS MESSAGE + /// @notice DeBridgeGate only accepts submissions with msg.value (native ether) covering a small protocol fee + /// (defined in the globalFixedNativeFee property). Any excess amount of ether passed to this function is + /// included in the message as the execution fee - the amount deBridgeGate would give as an incentive to + /// a third party in return for successful claim transaction execution on the destination chain. + /// @notice DeBridgeGate accepts a set of flags that control the behaviour of the execution. This simple method + /// sets the default set of flags: REVERT_IF_EXTERNAL_FAIL, PROXY_WITH_SENDER + /// @param _dstChainId ID of the destination chain. + /// @param _targetContractAddress A contract address to be called on the destination chain + /// @param _targetContractCalldata Calldata to execute against the target contract on the destination chain + /// @param _flags A bitmask of toggles listed in the Flags library + /// @param _referralCode Referral code to identify this submission + function sendMessage( + uint256 _dstChainId, + bytes memory _targetContractAddress, + bytes memory _targetContractCalldata, + uint256 _flags, + uint32 _referralCode + ) external payable returns (bytes32 submissionId); + + /// @dev This method is used for the transfer of assets [from the native chain](https://docs.debridge.finance/the-core-protocol/transfers#transfer-from-native-chain). + /// It locks an asset in the smart contract in the native chain and enables minting of deAsset on the secondary chain. + /// @param _tokenAddress Asset identifier. + /// @param _amount Amount to be transferred (note: the fee can be applied). + /// @param _chainIdTo Chain id of the target chain. + /// @param _receiver Receiver address. + /// @param _permitEnvelope Permit for approving the spender by signature. bytes (amount + deadline + signature) + /// @param _useAssetFee use assets fee for pay protocol fix (work only for specials token) + /// @param _referralCode Referral code + /// @param _autoParams Auto params for external call in target network + function send( + address _tokenAddress, + uint256 _amount, + uint256 _chainIdTo, + bytes memory _receiver, + bytes memory _permitEnvelope, + bool _useAssetFee, + uint32 _referralCode, + bytes calldata _autoParams + ) external payable returns (bytes32 submissionId); + + /// @dev Is used for transfers [into the native chain](https://docs.debridge.finance/the-core-protocol/transfers#transfer-from-secondary-chain-to-native-chain) + /// to unlock the designated amount of asset from collateral and transfer it to the receiver. + /// @param _debridgeId Asset identifier. + /// @param _amount Amount of the transferred asset (note: the fee can be applied). + /// @param _chainIdFrom Chain where submission was sent + /// @param _receiver Receiver address. + /// @param _nonce Submission id. + /// @param _signatures Validators signatures to confirm + /// @param _autoParams Auto params for external call + function claim( + bytes32 _debridgeId, + uint256 _amount, + uint256 _chainIdFrom, + address _receiver, + uint256 _nonce, + bytes calldata _signatures, + bytes calldata _autoParams + ) external; + + /// @dev Withdraw collected fees to feeProxy + /// @param _debridgeId Asset identifier. + function withdrawFee(bytes32 _debridgeId) external; + + /// @dev Returns asset fixed fee value for specified debridge and chainId. + /// @param _debridgeId Asset identifier. + /// @param _chainId Chain id. + function getDebridgeChainAssetFixedFee(bytes32 _debridgeId, uint256 _chainId) external view returns (uint256); + + /* ========== EVENTS ========== */ + + /// @dev Emitted once the tokens are sent from the original(native) chain to the other chain; the transfer tokens + /// are expected to be claimed by the users. + event Sent( + bytes32 submissionId, + bytes32 indexed debridgeId, + uint256 amount, + bytes receiver, + uint256 nonce, + uint256 indexed chainIdTo, + uint32 referralCode, + FeeParams feeParams, + bytes autoParams, + address nativeSender + ); + // bool isNativeToken //added to feeParams + + /// @dev Emitted once the tokens are transferred and withdrawn on a target chain + event Claimed( + bytes32 submissionId, + bytes32 indexed debridgeId, + uint256 amount, + address indexed receiver, + uint256 nonce, + uint256 indexed chainIdFrom, + bytes autoParams, + bool isNativeToken + ); + + /// @dev Emitted when new asset support is added. + event PairAdded( + bytes32 debridgeId, + address tokenAddress, + bytes nativeAddress, + uint256 indexed nativeChainId, + uint256 maxAmount, + uint16 minReservesBps + ); + + event MonitoringSendEvent(bytes32 submissionId, uint256 nonce, uint256 lockedOrMintedAmount, uint256 totalSupply); + + event MonitoringClaimEvent(bytes32 submissionId, uint256 lockedOrMintedAmount, uint256 totalSupply); + + /// @dev Emitted when the asset is allowed/disallowed to be transferred to the chain. + event ChainSupportUpdated(uint256 chainId, bool isSupported, bool isChainFrom); + /// @dev Emitted when the supported chains are updated. + event ChainsSupportUpdated(uint256 chainIds, ChainSupportInfo chainSupportInfo, bool isChainFrom); + + /// @dev Emitted when the new call proxy is set. + event CallProxyUpdated(address callProxy); + /// @dev Emitted when the transfer request is executed. + event AutoRequestExecuted(bytes32 submissionId, bool indexed success, address callProxy); + + /// @dev Emitted when a submission is blocked. + event Blocked(bytes32 submissionId); + /// @dev Emitted when a submission is unblocked. + event Unblocked(bytes32 submissionId); + + /// @dev Emitted when fee is withdrawn. + event WithdrawnFee(bytes32 debridgeId, uint256 fee); + + /// @dev Emitted when globalFixedNativeFee and globalTransferFeeBps are updated. + event FixedNativeFeeUpdated(uint256 globalFixedNativeFee, uint256 globalTransferFeeBps); + + /// @dev Emitted when globalFixedNativeFee is updated by feeContractUpdater + event FixedNativeFeeAutoUpdated(uint256 globalFixedNativeFee); +} diff --git a/src/debridge/mocks/DeBridgeSignatureVerifierMock.sol b/src/debridge/mocks/DeBridgeSignatureVerifierMock.sol new file mode 100644 index 0000000..57a2f8a --- /dev/null +++ b/src/debridge/mocks/DeBridgeSignatureVerifierMock.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +contract DeBridgeSignatureVerifierMock { + function submit(bytes32, bytes memory, uint8) external { + // simulate signature verification + } +} diff --git a/test/Debridge.t.sol b/test/Debridge.t.sol new file mode 100644 index 0000000..b98383b --- /dev/null +++ b/test/Debridge.t.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import {IERC20} from "src/across/interfaces/IERC20.sol"; +import "forge-std/Test.sol"; + +import {DebridgeHelper} from "src/debridge/DebridgeHelper.sol"; +import {IDebridgeGate} from "src/debridge/interfaces/IDebridgeGate.sol"; + +contract DebridgeHelperTest is Test { + DebridgeHelper debridgeHelper; + + address public target = address(this); + + uint256 L1_FORK_ID; + uint256 POLYGON_FORK_ID; + uint256 ARBITRUM_FORK_ID; + + address constant L1_USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address constant ARBITRUM_USDC = 0xaf88d065e77c8cC2239327C5EDb3A432268e5831; + address constant POLYGON_USDC = 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174; + + uint256 constant L1_ID = 1; + uint256 constant ARBITRUM_ID = 42161; + uint256 constant POLYGON_ID = 137; + + address constant L1_debridge = 0x43dE2d77BF8027e25dBD179B491e8d64f38398aA; + address constant ARBITRUM_debridge = 0x43dE2d77BF8027e25dBD179B491e8d64f38398aA; + address constant POLYGON_debridge = 0x43dE2d77BF8027e25dBD179B491e8d64f38398aA; + + address[] public allDstTargets; + uint256[] public allDstChainIds; + uint256[] public allDstForks; + + address constant L1_ADMIN = 0x6bec1faF33183e1Bc316984202eCc09d46AC92D5; + address constant ARBITRUM_ADMIN = 0xA52842cD43fA8c4B6660E443194769531d45b265; + address constant POLYGON_ADMIN = 0xA52842cD43fA8c4B6660E443194769531d45b265; + + address constant ARBITRUM_DEBRIDGE_TOKEN = 0x1dDcaa4Ed761428ae348BEfC6718BCb12e63bFaa; + address constant POLYGON_DEBRIDGE_TOKEN = 0x1dDcaa4Ed761428ae348BEfC6718BCb12e63bFaa; + + string RPC_ETH_MAINNET = vm.envString("ETH_MAINNET_RPC_URL"); + string RPC_ARBITRUM_MAINNET = vm.envString("ARBITRUM_MAINNET_RPC_URL"); + string RPC_POLYGON_MAINNET = vm.envString("POLYGON_MAINNET_RPC_URL"); + + // eth refund + receive() external payable {} + + function setUp() external { + L1_FORK_ID = vm.createSelectFork(RPC_ETH_MAINNET, 21580621); + ARBITRUM_FORK_ID = vm.createSelectFork(RPC_ARBITRUM_MAINNET, 293298223); + POLYGON_FORK_ID = vm.createSelectFork(RPC_POLYGON_MAINNET); + + vm.selectFork(L1_FORK_ID); + debridgeHelper = new DebridgeHelper(); + + allDstTargets.push(target); + allDstTargets.push(target); + allDstChainIds.push(ARBITRUM_ID); + allDstChainIds.push(POLYGON_ID); + allDstForks.push(ARBITRUM_FORK_ID); + allDstForks.push(POLYGON_FORK_ID); + } + + function testSimpleDebridge() external { + vm.selectFork(L1_FORK_ID); + uint256 amount = 1e10; + + // || + // || + // \/ This is the part of the code you could copy to use the DebridgeHelper + // in your own tests. + vm.recordLogs(); + _someCrossChainFunctionInYourContract(L1_debridge, ARBITRUM_ID, amount); + Vm.Log[] memory logs = vm.getRecordedLogs(); + + debridgeHelper.help(ARBITRUM_ADMIN, L1_debridge, ARBITRUM_FORK_ID, ARBITRUM_ID, logs); + // /\ + // || + // || + + vm.selectFork(ARBITRUM_FORK_ID); + assertApproxEqAbs(IERC20(ARBITRUM_DEBRIDGE_TOKEN).balanceOf(target), amount, amount * 1e4 / 1e5); + } + + function _someCrossChainFunctionInYourContract( + address sourceDebridgeGate, + uint256 destinationChainId, + uint256 amount + ) internal { + deal(L1_USDC, address(this), amount); + IERC20(L1_USDC).approve(sourceDebridgeGate, amount); + + IDebridgeGate(sourceDebridgeGate).send{value: 1 ether}( + L1_USDC, + amount, + destinationChainId, + abi.encodePacked(target), + "", //permit envelope + false, // use asset fee + 0, // referral code + "" // auto params + ); + } + + function testMultiDstDebridge() external { + vm.selectFork(L1_FORK_ID); + uint256 amount = 1e10; + + vm.recordLogs(); + _manyCrossChainFunctionInYourContract(amount); + Vm.Log[] memory logs = vm.getRecordedLogs(); + + address[] memory debridgeGateAdmins = new address[](2); + debridgeGateAdmins[0] = ARBITRUM_ADMIN; + debridgeGateAdmins[1] = POLYGON_ADMIN; + + debridgeHelper.help(L1_debridge, allDstForks, allDstChainIds, debridgeGateAdmins, logs); + + vm.selectFork(ARBITRUM_FORK_ID); + assertApproxEqAbs(IERC20(ARBITRUM_DEBRIDGE_TOKEN).balanceOf(target), amount, amount * 1e4 / 1e5); + } + + function _manyCrossChainFunctionInYourContract(uint256 amount) internal { + uint256 count = allDstForks.length; + + deal(L1_USDC, address(this), amount * count); + IERC20(L1_USDC).approve(L1_debridge, amount * count); + + IDebridgeGate(L1_debridge).send{value: 1 ether}( + L1_USDC, + amount, + ARBITRUM_ID, + abi.encodePacked(target), + "", //permit envelope + false, // use asset fee + 0, // referral code + "" // auto params + ); + + IDebridgeGate(L1_debridge).send{value: 1 ether}( + L1_USDC, + amount, + POLYGON_ID, + abi.encodePacked(target), + "", //permit envelope + false, // use asset fee + 0, // referral code + "" // auto params + ); + } +}