diff --git a/.solhint.json b/.solhint.json index 40cc53d..b0e650a 100644 --- a/.solhint.json +++ b/.solhint.json @@ -9,7 +9,7 @@ } ], "code-complexity": ["error", 7], - "compiler-version": ["error", "^0.8.7"], + "compiler-version": ["error", "^0.8.0"], "const-name-snakecase": "off", "func-name-mixedcase": "off", "constructor-syntax": "error", diff --git a/config/allbridge.json b/config/allbridge.json new file mode 100644 index 0000000..c014ed4 --- /dev/null +++ b/config/allbridge.json @@ -0,0 +1,5 @@ +{ + "addresses": { + "bsc": "0x3C4FA639c8D7E65c603145adaD8bD12F2358312f" + } +} diff --git a/deployments/bsc.diamond.json b/deployments/bsc.diamond.json index 40ce714..586bddf 100644 --- a/deployments/bsc.diamond.json +++ b/deployments/bsc.diamond.json @@ -16,6 +16,7 @@ "0x050b1A40fBcAC0beccD9520D7363A33827283030", "0x167D54C29A448fC0b9596EA530223330D608e22f", "0x4dd8D62229BaF0B1ACcC3dE5c0637291323EF6b6", - "0x9556eC186526C028c3260737EA26A53EB51ce47B" + "0x9556eC186526C028c3260737EA26A53EB51ce47B", + "0x3dd2f184953A72EA00011D854C371639b95169d0" ] } \ No newline at end of file diff --git a/deployments/bsc.json b/deployments/bsc.json index 2afcf38..0c83c68 100644 --- a/deployments/bsc.json +++ b/deployments/bsc.json @@ -18,5 +18,6 @@ "Receiver": "0xb15A66101ac2A6de589dAd1dCbbd7566DCCaB61D", "TransferFacet": "0x9f50F4122B3d66397859C146e3489B4c0f66E17d", "TransferWithBytesFacet": "0x9556eC186526C028c3260737EA26A53EB51ce47B", - "StargateV2Receiver": "0xDAd6723eb88bf4D20404687B1828e26d5D6eA498" -} + "StargateV2Receiver": "0xDAd6723eb88bf4D20404687B1828e26d5D6eA498", + "StellarFacet": "0x3dd2f184953A72EA00011D854C371639b95169d0" +} \ No newline at end of file diff --git a/script/DeployStellarFacet.s.sol b/script/DeployStellarFacet.s.sol new file mode 100644 index 0000000..ef20874 --- /dev/null +++ b/script/DeployStellarFacet.s.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "./utils/DeployScriptBase.sol"; +import { stdJson } from "forge-std/Script.sol"; +import { StellarFacet } from "rubic/Facets/StellarFacet.sol"; + +contract DeployScript is DeployScriptBase { + using stdJson for string; + + constructor() DeployScriptBase("StellarFacet") {} + + function run() + public + returns (StellarFacet deployed, bytes memory constructorArgs) + { + string memory path = string.concat( + vm.projectRoot(), + "/config/allbridge.json" + ); + string memory json = vm.readFile(path); + address allBridgeCore = json.readAddress( + string.concat(".addresses.", network) + ); + + constructorArgs = abi.encode(allBridgeCore); + + vm.startBroadcast(deployerPrivateKey); + + if (isDeployed()) { + return (StellarFacet(payable(predicted)), constructorArgs); + } + + if (networkSupportsCreate3(network)) { + deployed = StellarFacet( + payable( + factory.deploy( + salt, + bytes.concat( + type(StellarFacet).creationCode, + constructorArgs + ) + ) + ) + ); + } else { + deployed = new StellarFacet(allBridgeCore); + } + + vm.stopBroadcast(); + } +} diff --git a/script/UpdateStellarFacet.s.sol b/script/UpdateStellarFacet.s.sol new file mode 100644 index 0000000..71ef1df --- /dev/null +++ b/script/UpdateStellarFacet.s.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { UpdateScriptBase } from "./utils/UpdateScriptBase.sol"; +import { stdJson } from "forge-std/StdJson.sol"; +import { DiamondCutFacet, IDiamondCut } from "rubic/Facets/DiamondCutFacet.sol"; +import { StellarFacet } from "rubic/Facets/StellarFacet.sol"; + +contract DeployScript is UpdateScriptBase { + using stdJson for string; + + function run() public returns (address[] memory facets) { + string memory path = string.concat( + root, + "/deployments/", + network, + ".", + fileSuffix, + "json" + ); + string memory json = vm.readFile(path); + address facet = json.readAddress(".StellarFacet"); + + vm.startBroadcast(deployerPrivateKey); + + // StellarFacet + if (loupe.facetFunctionSelectors(facet).length == 0) { + bytes4[] memory exclude; + cut.push( + IDiamondCut.FacetCut({ + facetAddress: address(facet), + action: IDiamondCut.FacetCutAction.Add, + functionSelectors: getSelectors( + "StellarFacet", + exclude + ) + }) + ); + cutter.diamondCut(cut, address(0), ""); + } + + facets = loupe.facetAddresses(); + + vm.stopBroadcast(); + } +} diff --git a/src/Facets/StellarFacet.sol b/src/Facets/StellarFacet.sol new file mode 100644 index 0000000..69fa96a --- /dev/null +++ b/src/Facets/StellarFacet.sol @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { IRubic } from "../Interfaces/IRubic.sol"; +import { LibAsset, IERC20 } from "../Libraries/LibAsset.sol"; +import { SwapperV2, LibSwap } from "../Helpers/SwapperV2.sol"; +import { ReentrancyGuard } from "../Helpers/ReentrancyGuard.sol"; +import { Validatable } from "../Helpers/Validatable.sol"; +import { LibAccess } from "../Libraries/LibAccess.sol"; +import { IAllBridgeCore } from "../Interfaces/IAllBridgeCore.sol"; +import { AlreadyInitialized, NotInitialized, UnsupportedChainId, NotEnoughBalance, InformationMismatch } from "../Errors/GenericErrors.sol"; + +contract StellarFacet is IRubic, ReentrancyGuard, SwapperV2, Validatable { + bytes32 internal constant STELLAR_NAMESPACE = + keccak256("com.rubic.facets.stellar"); + uint256 internal constant DEST_CHAIN_ID = 7; + uint256 internal constant DENOMINATOR = 10 ** 18; + + IAllBridgeCore private immutable allBridgeCore; + uint256 private immutable allBridgeChainId; + + /// Storage /// + + struct Storage { + mapping(uint256 => bool) nonceSent; + bool initialized; + address relayerFeeReceiver; + uint256 scalingFactor; + } + + /// Types /// + + /// @param tokensOut output token address after bridge & final output tokens address after swap (format: hex in destination chain) + /// @param amountOutMin output token minimum amount in destination chain + /// @param finalReceiver final receiver address (hex in destination chain) + /// @param allBridgeReceiver receiver address in allBridge call (hex in destination chain) + /// @param nonce operation nonce (to prevent message double spending) + /// @param destinationSwapDeadline for executing the swap on the destination chain + /// @param allBridgeFee fee for AllBridge protocol in stablecoin + struct StellarData { + bytes32[2] tokensOut; // do not use _bridgeData.receivingAssetId cause it has `address` type (not suitable for Stellar addresses & AllBridge protocol) + uint256 amountOutMin; + bytes32 finalReceiver; // do not use _bridgeData.receiver cause it has `address` type (not suitable for Stellar addresses & AllBridge protocol) + bytes32 allBridgeReceiver; // do not use _bridgeData.receiver cause it has `address` type (not suitable for Stellar addresses & AllBridge protocol) + uint256 nonce; + uint256 destinationSwapDeadline; + uint256 allBridgeFee; + } + + /// Events /// + /** @notice Params for `Deposit` event + * @param rubicId txn id in Rubic Ecosystem + * @param integrator address + * @param tokenIn input token address + * @param amountIn input token amount + * @param tokenIntermediateSrc token address after swap (if no source swap, tokenIntermediate should be equal to tokenIn) + * @param amountIntermediate `tokenIntermediate` amount after source swap + * @param tokenIntermediateDest output token address after bridge (hex in destination chain) + * @param tokenOut final output token address after swap (hex in destination chain) + * @param amountOutMin output token minimum amount + * @param finalReceiver final receiver address (hex in destination chain) + * @param allBridgeReceiver receiver address in allBridge call (hex in destination chain) + * @param allBridgeFee bridge fee amount in USDC (for allBridge) + * @param destinationChainId destination chain id (in allBridge protocol) + * @param nonce txn nonce + * @param deadline for executing the swap on the destination chain + */ + struct DepositEventParams { + bytes32 rubicId; + address integrator; + address tokenIn; + uint256 amountIn; + address tokenIntermediateSrc; + uint256 amountIntermediate; + bytes32 tokenIntermediateDest; + bytes32 tokenOut; + uint256 amountOutMin; + bytes32 finalReceiver; + bytes32 allBridgeReceiver; + uint256 allBridgeFee; + uint256 destinationChainId; + uint256 nonce; + uint256 deadline; + } + /** @notice Event emitted in `deposit` + * @param params see below in `DepositEventParams` structure + */ + event Deposit(DepositEventParams params); + + /// Errors /// + error UsedNonce(uint256 nonce); + + constructor(address _allBridgeCore) { + allBridgeCore = IAllBridgeCore(_allBridgeCore); + allBridgeChainId = IAllBridgeCore(_allBridgeCore).chainId(); + } + + /// Init /// + + /// @notice Initialize local variables for the Stellar Facet + /// @param _feeReceiver Relayer fee receiver address + /// @param _scalingFactor Multiplier to calculate relayer fee based on the AllBridge protocol fee + function initStellar( + address _feeReceiver, + uint256 _scalingFactor + ) external { + LibAccess.enforceAccessControl(); + Storage storage s = getStorage(); + + if (s.initialized) revert AlreadyInitialized(); + + s.initialized = true; + s.relayerFeeReceiver = _feeReceiver; + s.scalingFactor = _scalingFactor; + } + + /// External Methods /// + + /// @notice Bridges tokens via AllBridge + /// @param _bridgeData Data used purely for tracking and analytics + /// @param _stellarData Data specific to bridge assets to the Stellar network + function startBridgeTokensViaAllBridge( + IRubic.BridgeData memory _bridgeData, + StellarData calldata _stellarData + ) + external + payable + nonReentrant + refundExcessNative(payable(_bridgeData.refundee)) + doesNotContainSourceSwaps(_bridgeData) + validateBridgeData(_bridgeData) + noNativeAsset(_bridgeData) + { + _bridgeData.minAmount = LibAsset.depositAssetAndAccrueFees( + _bridgeData.sendingAssetId, + _bridgeData.minAmount, + 0, + _bridgeData.integrator + ); + + _startBridge(_bridgeData, _stellarData); + + emit Deposit( + DepositEventParams( + _bridgeData.transactionId, + _bridgeData.integrator, + _bridgeData.sendingAssetId, + _bridgeData.minAmount, + _bridgeData.sendingAssetId, + _bridgeData.minAmount, + _stellarData.tokensOut[0], + _stellarData.tokensOut[1], + _stellarData.amountOutMin, + _stellarData.finalReceiver, + _stellarData.allBridgeReceiver, + _stellarData.allBridgeFee, + _bridgeData.destinationChainId, + _stellarData.nonce, + _stellarData.destinationSwapDeadline + ) + ); + } + + /// @notice Performs a swap before bridging via AllBridge + /// @param _bridgeData Data used purely for tracking and analytics + /// @param _swapData An array of swap related data for performing swaps before bridging + /// @param _stellarData Data specific to bridge assets to the Stellar network + function swapAndStartBridgeTokensViaAllBridge( + IRubic.BridgeData memory _bridgeData, + LibSwap.SwapData[] calldata _swapData, + StellarData calldata _stellarData + ) + external + payable + nonReentrant + refundExcessNative(payable(_bridgeData.refundee)) + containsSourceSwaps(_bridgeData) + validateBridgeData(_bridgeData) + noNativeAsset(_bridgeData) + { + _bridgeData.minAmount = _depositAndSwap( + _bridgeData.transactionId, + _bridgeData.minAmount, + _swapData, + _bridgeData.integrator, + payable(_bridgeData.refundee), + 0 + ); + + _bridgeData.minAmount = _startBridge(_bridgeData, _stellarData); + + emit Deposit( + DepositEventParams( + _bridgeData.transactionId, + _bridgeData.integrator, + _swapData[0].sendingAssetId, + _swapData[0].fromAmount, + _bridgeData.sendingAssetId, + _bridgeData.minAmount, + _stellarData.tokensOut[0], + _stellarData.tokensOut[1], + _stellarData.amountOutMin, + _stellarData.finalReceiver, + _stellarData.allBridgeReceiver, + _stellarData.allBridgeFee, + _bridgeData.destinationChainId, + _stellarData.nonce, + _stellarData.destinationSwapDeadline + ) + ); + } + + function setScalingFactor(uint256 _scalingFactor) external { + LibAccess.enforceAccessControl(); + Storage storage s = getStorage(); + if (!s.initialized) revert NotInitialized(); + s.scalingFactor = _scalingFactor; + } + + function setRelayerFeeReceiver(address _feeReceiver) external { + LibAccess.enforceAccessControl(); + Storage storage s = getStorage(); + if (!s.initialized) revert NotInitialized(); + s.relayerFeeReceiver = _feeReceiver; + } + + function getNonces(uint256 nonce) external view returns (bool) { + return getStorage().nonceSent[nonce]; + } + + function getRelayerFeeReceiver() external view returns (address) { + return getStorage().relayerFeeReceiver; + } + + function getScalingFactor() external view returns (uint256) { + return getStorage().scalingFactor; + } + + /// @dev fetch local storage + function getStorage() private pure returns (Storage storage s) { + bytes32 namespace = STELLAR_NAMESPACE; + // solhint-disable-next-line no-inline-assembly + assembly { + s.slot := namespace + } + } + + /// @notice Calculate relayer fee amount based on the AllBridge fee + /// @param token address to bridge + function calculateRelayerFee(address token) public view returns (uint256) { + return + (allBridgeCore.getBridgingCostInTokens( + DEST_CHAIN_ID, + IAllBridgeCore.MessengerProtocol.Allbridge, + token + ) * getStorage().scalingFactor) / DENOMINATOR; + } + + /// Private Methods /// + + /// @dev Contains the business logic for the bridge via AllBridge into Stellar network + /// @param _bridgeData Data used purely for tracking and analytics + /// @param _stellarData Data specific to bridge assets to the Stellar network + // solhint-disable-next-line code-complexity + function _startBridge( + IRubic.BridgeData memory _bridgeData, + StellarData calldata _stellarData + ) private noNativeAsset(_bridgeData) returns (uint256) { + if (_bridgeData.destinationChainId != DEST_CHAIN_ID) + revert UnsupportedChainId(_bridgeData.destinationChainId); + + if ( + _bridgeData.hasDestinationCall && + _stellarData.allBridgeReceiver == _stellarData.finalReceiver + ) revert InformationMismatch(); + if ( + !_bridgeData.hasDestinationCall && + _stellarData.allBridgeReceiver != _stellarData.finalReceiver + ) revert InformationMismatch(); + + Storage storage s = getStorage(); + + if (s.nonceSent[_stellarData.nonce]) + revert UsedNonce(_stellarData.nonce); + s.nonceSent[_stellarData.nonce] = true; + + if (_bridgeData.hasDestinationCall) { + uint256 relayerFee = calculateRelayerFee( + _bridgeData.sendingAssetId + ); + if ( + relayerFee + _stellarData.allBridgeFee >= _bridgeData.minAmount + ) + revert NotEnoughBalance( + relayerFee + _stellarData.allBridgeFee, + _bridgeData.minAmount + ); + + if (relayerFee > 0) { + _bridgeData.minAmount -= relayerFee; + LibAsset.transferERC20( + _bridgeData.sendingAssetId, + s.relayerFeeReceiver, + relayerFee + ); + } + } + + LibAsset.maxApproveERC20( + IERC20(_bridgeData.sendingAssetId), + address(allBridgeCore), + _bridgeData.minAmount + ); + + allBridgeCore.swapAndBridge( + _addressToBytes32(_bridgeData.sendingAssetId), + _bridgeData.minAmount, + _stellarData.allBridgeReceiver, + _bridgeData.destinationChainId, + _stellarData.tokensOut[0], + _stellarData.nonce, + IAllBridgeCore.MessengerProtocol.Allbridge, + _stellarData.allBridgeFee + ); + + emit RubicTransferStarted(_bridgeData); + + return _bridgeData.minAmount; + } + + function _addressToBytes32(address a) internal pure returns (bytes32) { + return bytes32(uint256(uint160(a))); + } +} diff --git a/src/Interfaces/IAllBridgeCore.sol b/src/Interfaces/IAllBridgeCore.sol new file mode 100644 index 0000000..44d9dee --- /dev/null +++ b/src/Interfaces/IAllBridgeCore.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IAllBridgeCore { + enum MessengerProtocol { + None, + Allbridge, + Wormhole, + LayerZero + } + + function chainId() external view returns (uint256); + + function swapAndBridge( + bytes32 token, + uint amount, + bytes32 recipient, + uint destinationChainId, + bytes32 receiveToken, + uint nonce, + MessengerProtocol messenger, + uint feeTokenAmount + ) external payable; + + function getBridgingCostInTokens( + uint destinationChainId, + MessengerProtocol messenger, + address tokenAddress + ) external view returns (uint); +} diff --git a/test/solidity/Facets/StellarFacet.t.sol b/test/solidity/Facets/StellarFacet.t.sol new file mode 100644 index 0000000..c238a55 --- /dev/null +++ b/test/solidity/Facets/StellarFacet.t.sol @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity 0.8.17; + +import { LibAllowList, TestBaseFacet, console, RubicMultiProxy, IRubic } from "../utils/TestBaseFacet.sol"; +import { OnlyContractOwner, InvalidConfig, AlreadyInitialized } from "rubic/Errors/GenericErrors.sol"; +import { StellarFacet } from "rubic/Facets/StellarFacet.sol"; +import { IAllBridgeCore } from "rubic/Interfaces/IAllBridgeCore.sol"; +import { IAccessManagerFacet } from "rubic/Interfaces/IAccessManagerFacet.sol"; +import { AlreadyInitialized, UnAuthorized } from "rubic/Errors/GenericErrors.sol"; + +// Stub CBridgeFacet Contract +contract TestStellarFacet is StellarFacet { + /// @notice Initialize the contract. + /// @param _allBridgeCore The contract address of the allbridge core contract on the source chain. + constructor(address _allBridgeCore) StellarFacet(_allBridgeCore) {} + + function addDex(address _dex) external { + LibAllowList.addAllowedContract(_dex); + } + + function setFunctionApprovalBySignature(bytes4 _signature) external { + LibAllowList.addAllowedSelector(_signature); + } +} + +contract StellarFacetTest is TestBaseFacet { + // copy of Deposit event + event Deposit(StellarFacet.DepositEventParams params); + + address internal constant ALLBRIDGE_CORE = + 0x609c690e8F7D68a59885c9132e812eEbDaAf0c9e; + + TestStellarFacet internal stellarFacet; + StellarFacet.StellarData internal stellarData; + + function setUp() public { + // set custom block number for forking + customBlockNumberForForking = 24040000; + + initTestBase(); + + stellarFacet = new TestStellarFacet(ALLBRIDGE_CORE); + + bytes4[] memory functionSelectors = new bytes4[](11); + functionSelectors[0] = stellarFacet.initStellar.selector; + functionSelectors[1] = stellarFacet + .startBridgeTokensViaAllBridge + .selector; + functionSelectors[2] = stellarFacet + .swapAndStartBridgeTokensViaAllBridge + .selector; + functionSelectors[3] = stellarFacet.setScalingFactor.selector; + functionSelectors[4] = stellarFacet.setRelayerFeeReceiver.selector; + functionSelectors[5] = stellarFacet.getNonces.selector; + functionSelectors[6] = stellarFacet.getRelayerFeeReceiver.selector; + functionSelectors[7] = stellarFacet.getScalingFactor.selector; + functionSelectors[8] = stellarFacet.calculateRelayerFee.selector; + functionSelectors[9] = stellarFacet.addDex.selector; + functionSelectors[10] = stellarFacet + .setFunctionApprovalBySignature + .selector; + + addFacet(diamond, address(stellarFacet), functionSelectors); + + IAccessManagerFacet(address(diamond)).setCanExecute( + stellarFacet.initStellar.selector, + address(this), + true + ); + + IAccessManagerFacet(address(diamond)).setCanExecute( + stellarFacet.setScalingFactor.selector, + address(this), + true + ); + + IAccessManagerFacet(address(diamond)).setCanExecute( + stellarFacet.setRelayerFeeReceiver.selector, + address(this), + true + ); + + stellarFacet = TestStellarFacet(address(diamond)); + stellarFacet.initStellar(USER_RECEIVER, 5 * 10 ** 18); + + stellarFacet.addDex(address(uniswap)); + stellarFacet.setFunctionApprovalBySignature( + uniswap.swapExactTokensForTokens.selector + ); + stellarFacet.setFunctionApprovalBySignature( + uniswap.swapETHForExactTokens.selector + ); + stellarFacet.setFunctionApprovalBySignature( + uniswap.swapExactTokensForETH.selector + ); + + setFacetAddressInTestBase(address(stellarFacet), "StellarFacet"); + + bridgeData.bridge = "stellar"; + bridgeData.minAmount = defaultUSDCAmount; + bridgeData.destinationChainId = 7; + + stellarData = StellarFacet.StellarData({ + tokensOut: [ + bytes32( + 0xadefce59aee52968f76061d494c2525b75659fa4296a65f499ef29e56477e496 + ), + bytes32(0) + ], + amountOutMin: 0, + finalReceiver: bytes32(uint256(0xabc)), + allBridgeReceiver: bytes32(uint256(0xabc)), + nonce: 0, + destinationSwapDeadline: 0, + allBridgeFee: IAllBridgeCore(ALLBRIDGE_CORE) + .getBridgingCostInTokens( + 7, + IAllBridgeCore.MessengerProtocol.Allbridge, + address(usdc) + ) + }); + } + + function initiateBridgeTxWithFacet(bool isNative) internal override { + bytes memory facetCallData = abi.encodeWithSelector( + stellarFacet.startBridgeTokensViaAllBridge.selector, + bridgeData, + stellarData + ); + + address[] memory tokens; + uint256[] memory amounts; + + if (isNative) { + erc20proxy.startViaRubic{ + value: bridgeData.minAmount + addToMessageValue + }(tokens, amounts, facetCallData); + } else { + tokens = new address[](1); + amounts = new uint256[](1); + + tokens[0] = bridgeData.sendingAssetId; + amounts[0] = bridgeData.minAmount; + + erc20proxy.startViaRubic{ value: addToMessageValue }( + tokens, + amounts, + facetCallData + ); + } + } + + function initiateSwapAndBridgeTxWithFacet( + bool isNative + ) internal override { + bytes memory facetCallData = abi.encodeWithSelector( + stellarFacet.swapAndStartBridgeTokensViaAllBridge.selector, + bridgeData, + swapData, + stellarData + ); + + address[] memory tokens; + uint256[] memory amounts; + + if (isNative) { + erc20proxy.startViaRubic{ + value: swapData[0].fromAmount + addToMessageValue + }(tokens, amounts, facetCallData); + } else { + if (swapData.length > 0) { + tokens = new address[](1); + amounts = new uint256[](1); + tokens[0] = swapData[0].sendingAssetId; + amounts[0] = swapData[0].fromAmount; + } + + erc20proxy.startViaRubic{ value: addToMessageValue }( + tokens, + amounts, + facetCallData + ); + } + } + + function testBase_CanBridgeNativeTokens() public override { + // facet does not support native bridging + } + + function testBase_CanBridgeNativeTokensWithFees() public override { + // facet does not support native bridging + } + + function testBase_CanSwapAndBridgeNativeTokens() public override { + // facet does not support native bridging + } + + function testBase_CanBridgeTokens_fuzzed(uint256 amount) public override { + vm.assume(amount > 2); // to cover allbridge fees + super.testBase_CanBridgeTokens_fuzzed(amount); + } + + function test_DepositEventNoSwapNoDestSwap() public { + vm.startPrank(USER_SENDER); + usdc.approve(address(erc20proxy), bridgeData.minAmount); + + vm.expectEmit(true, true, true, true, _facetTestContractAddress); + emit Deposit( + StellarFacet.DepositEventParams( + bridgeData.transactionId, + bridgeData.integrator, + bridgeData.sendingAssetId, + bridgeData.minAmount, + bridgeData.sendingAssetId, + bridgeData.minAmount, + stellarData.tokensOut[0], + stellarData.tokensOut[1], + stellarData.amountOutMin, + stellarData.finalReceiver, + stellarData.allBridgeReceiver, + stellarData.allBridgeFee, + bridgeData.destinationChainId, + stellarData.nonce, + stellarData.destinationSwapDeadline + ) + ); + + initiateBridgeTxWithFacet(false); + } + + function test_DepositEventNoSwapDestSwap() + public + assertBalanceChange( + address(usdc), + stellarFacet.getRelayerFeeReceiver(), + int256(stellarData.allBridgeFee * 5) + ) + { + stellarData.finalReceiver = bytes32(0); + bridgeData.hasDestinationCall = true; + + vm.startPrank(USER_SENDER); + usdc.approve(address(erc20proxy), bridgeData.minAmount); + + vm.expectEmit(true, true, true, true, _facetTestContractAddress); + emit Deposit( + StellarFacet.DepositEventParams( + bridgeData.transactionId, + bridgeData.integrator, + bridgeData.sendingAssetId, + bridgeData.minAmount - stellarData.allBridgeFee * 5, + bridgeData.sendingAssetId, + bridgeData.minAmount - stellarData.allBridgeFee * 5, + stellarData.tokensOut[0], + stellarData.tokensOut[1], + stellarData.amountOutMin, + stellarData.finalReceiver, + stellarData.allBridgeReceiver, + stellarData.allBridgeFee, + bridgeData.destinationChainId, + stellarData.nonce, + stellarData.destinationSwapDeadline + ) + ); + + initiateBridgeTxWithFacet(false); + } + + function test_DepositEventSwapNoDestSwap() public { + bridgeData.hasSourceSwaps = true; + _setDefaultSwapDataSingleDAItoUSDC(false); + + vm.startPrank(USER_SENDER); + dai.approve(address(erc20proxy), swapData[0].fromAmount); + + vm.expectEmit(true, true, true, true, _facetTestContractAddress); + emit Deposit( + StellarFacet.DepositEventParams( + bridgeData.transactionId, + bridgeData.integrator, + address(dai), + swapData[0].fromAmount, + bridgeData.sendingAssetId, + bridgeData.minAmount, + stellarData.tokensOut[0], + stellarData.tokensOut[1], + stellarData.amountOutMin, + stellarData.finalReceiver, + stellarData.allBridgeReceiver, + stellarData.allBridgeFee, + bridgeData.destinationChainId, + stellarData.nonce, + stellarData.destinationSwapDeadline + ) + ); + + initiateSwapAndBridgeTxWithFacet(false); + } + + function test_DepositEventSwapDestSwap() + public + assertBalanceChange( + address(usdc), + stellarFacet.getRelayerFeeReceiver(), + int256(stellarData.allBridgeFee * 5) + ) + { + bridgeData.hasSourceSwaps = true; + stellarData.finalReceiver = bytes32(0); + bridgeData.hasDestinationCall = true; + _setDefaultSwapDataSingleDAItoUSDC(false); + + vm.startPrank(USER_SENDER); + dai.approve(address(erc20proxy), swapData[0].fromAmount); + + vm.expectEmit(true, true, true, true, _facetTestContractAddress); + emit Deposit( + StellarFacet.DepositEventParams( + bridgeData.transactionId, + bridgeData.integrator, + address(dai), + swapData[0].fromAmount, + bridgeData.sendingAssetId, + bridgeData.minAmount - stellarData.allBridgeFee * 5, + stellarData.tokensOut[0], + stellarData.tokensOut[1], + stellarData.amountOutMin, + stellarData.finalReceiver, + stellarData.allBridgeReceiver, + stellarData.allBridgeFee, + bridgeData.destinationChainId, + stellarData.nonce, + stellarData.destinationSwapDeadline + ) + ); + + initiateSwapAndBridgeTxWithFacet(false); + } + + function test_Revert_InitOwnable() public { + vm.startPrank(USER_SENDER); + vm.expectRevert(UnAuthorized.selector); + stellarFacet.initStellar(USER_RECEIVER, 5 * 10 ** 18); + } + + function test_Revert_SetScalingFactorOwnable() public { + vm.startPrank(USER_SENDER); + vm.expectRevert(UnAuthorized.selector); + stellarFacet.setScalingFactor(5 * 10 ** 18); + } + + function test_Revert_SetRelayerFeeReceiverOwnable() public { + vm.startPrank(USER_SENDER); + vm.expectRevert(UnAuthorized.selector); + stellarFacet.setRelayerFeeReceiver(USER_RECEIVER); + } + + function test_Revert_InitTwice() public { + vm.expectRevert(AlreadyInitialized.selector); + stellarFacet.initStellar(USER_RECEIVER, 5 * 10 ** 18); + } + + function test_Revert_DepositTwice() public { + assertEq(stellarFacet.getNonces(0), false); + + vm.startPrank(USER_SENDER); + usdc.approve(address(erc20proxy), bridgeData.minAmount); + initiateBridgeTxWithFacet(false); + + assertEq(stellarFacet.getNonces(0), true); + + usdc.approve(address(erc20proxy), bridgeData.minAmount); + vm.expectRevert( + abi.encodeWithSelector(StellarFacet.UsedNonce.selector, 0) + ); + initiateBridgeTxWithFacet(false); + } + + function test_CalculateRelayerFee() public { + assertEq(stellarFacet.getScalingFactor(), 5 * 10 ** 18); + assertEq( + stellarFacet.calculateRelayerFee(address(usdc)), + stellarData.allBridgeFee * 5 + ); + stellarFacet.setScalingFactor(10 ** 17); + assertEq(stellarFacet.getScalingFactor(), 10 ** 17); + assertEq( + stellarFacet.calculateRelayerFee(address(usdc)), + stellarData.allBridgeFee / 10 + ); + } + + function test_SetRelayerFeeReceiver() public { + assertEq(stellarFacet.getRelayerFeeReceiver(), USER_RECEIVER); + stellarFacet.setRelayerFeeReceiver(USER_SENDER); + assertEq(stellarFacet.getRelayerFeeReceiver(), USER_SENDER); + } +}