From 965f0403ac56fde666daaca8df97c7e8cfdea186 Mon Sep 17 00:00:00 2001 From: 0xTimepunk Date: Tue, 26 Nov 2024 10:53:19 +0000 Subject: [PATCH 1/2] feat: deploys router plus to staging and restores paymentHelper --- .../10/Optimism-latest.json | 4 +- .../250/Fantom-latest.json | 4 +- .../42161/Arbitrum-latest.json | 4 +- .../56/Binance-latest.json | 4 +- .../59144/Linea-latest.json | 4 +- .../81457/Blast-latest.json | 4 +- .../8453/Base-latest.json | 4 +- .../misc/Abstract.Deploy.RouterPlus.s.sol | 12 - .../run_script_mainnet_staging_routerplus.sh | 36 +- script/utils/verify_contracts_staging.sh | 8 +- src/crosschain-data/utils/PayloadHelper.sol | 114 ++--- src/crosschain-data/utils/PayloadHelperV2.sol | 392 ++++++++++++++++++ src/crosschain-liquidity/DstSwapper.sol | 5 - src/interfaces/IPayloadHelper.sol | 15 +- src/interfaces/IPayloadHelperV2.sol | 115 +++++ src/interfaces/ITimelockStateRegistry.sol | 43 ++ .../utils/PayloadHelper.multiVault.t.sol | 2 +- .../utils/PayloadHelper.singleVault.t.sol | 2 +- test/utils/BaseSetup.sol | 2 +- 19 files changed, 643 insertions(+), 131 deletions(-) create mode 100644 src/crosschain-data/utils/PayloadHelperV2.sol create mode 100644 src/interfaces/IPayloadHelperV2.sol create mode 100644 src/interfaces/ITimelockStateRegistry.sol diff --git a/script/deployments/v1_staging_deployment/10/Optimism-latest.json b/script/deployments/v1_staging_deployment/10/Optimism-latest.json index 602ee77f7..22dd191ee 100644 --- a/script/deployments/v1_staging_deployment/10/Optimism-latest.json +++ b/script/deployments/v1_staging_deployment/10/Optimism-latest.json @@ -30,6 +30,6 @@ "ERC5115Form": "0x93f5fD75460aC5F0686eBfE22e556F1129F504B0", "OneInchValidator": "0x480140a26c3eb10F0F17e56495CE588320f45Cfe", "ERC5115To4626WrapperFactory": "0x14Bc2728DaE89FE7c828833a186DdC5E9AE439C3", - "SuperformRouterPlus": "0x82D3EF5e1005559fc6D577a730Bb201De1F7Cc6d", - "SuperformRouterPlusAsync": "0x47aa92F7bb7c018046079BDaA94E5618cbCe151f" + "SuperformRouterPlus": "0xA4bf3ab533Bd9dd0cC8C71D273B748E968065249", + "SuperformRouterPlusAsync": "0xbc1300e95393cF481b7c2d29d85B54689ef04fc8" } \ No newline at end of file diff --git a/script/deployments/v1_staging_deployment/250/Fantom-latest.json b/script/deployments/v1_staging_deployment/250/Fantom-latest.json index 473f9be93..428d83247 100644 --- a/script/deployments/v1_staging_deployment/250/Fantom-latest.json +++ b/script/deployments/v1_staging_deployment/250/Fantom-latest.json @@ -30,6 +30,6 @@ "ERC5115Form": "0x6aA92De361938B0A062E74e068a2028778F17852", "OneInchValidator": "0x16e9f8549c2b6a026dc2706d746beA76CeFF4098", "ERC5115To4626WrapperFactory": "0x0df3d7D6daE058667e49C6b85F7b92458Ab06836", - "SuperformRouterPlus": "0x5223a22BB4EecE74ac4f258cf0a4977eaEB0166A", - "SuperformRouterPlusAsync": "0x4A4a0042e485CBE61ac4BC495Fab5Ca516040e18" + "SuperformRouterPlus": "0x19425A9c151D4F5F28244ae07eB6cF7b14718baF", + "SuperformRouterPlusAsync": "0x45b482E122502298f42c438110732941A3955f06" } \ No newline at end of file diff --git a/script/deployments/v1_staging_deployment/42161/Arbitrum-latest.json b/script/deployments/v1_staging_deployment/42161/Arbitrum-latest.json index 602ee77f7..22dd191ee 100644 --- a/script/deployments/v1_staging_deployment/42161/Arbitrum-latest.json +++ b/script/deployments/v1_staging_deployment/42161/Arbitrum-latest.json @@ -30,6 +30,6 @@ "ERC5115Form": "0x93f5fD75460aC5F0686eBfE22e556F1129F504B0", "OneInchValidator": "0x480140a26c3eb10F0F17e56495CE588320f45Cfe", "ERC5115To4626WrapperFactory": "0x14Bc2728DaE89FE7c828833a186DdC5E9AE439C3", - "SuperformRouterPlus": "0x82D3EF5e1005559fc6D577a730Bb201De1F7Cc6d", - "SuperformRouterPlusAsync": "0x47aa92F7bb7c018046079BDaA94E5618cbCe151f" + "SuperformRouterPlus": "0xA4bf3ab533Bd9dd0cC8C71D273B748E968065249", + "SuperformRouterPlusAsync": "0xbc1300e95393cF481b7c2d29d85B54689ef04fc8" } \ No newline at end of file diff --git a/script/deployments/v1_staging_deployment/56/Binance-latest.json b/script/deployments/v1_staging_deployment/56/Binance-latest.json index 602ee77f7..22dd191ee 100644 --- a/script/deployments/v1_staging_deployment/56/Binance-latest.json +++ b/script/deployments/v1_staging_deployment/56/Binance-latest.json @@ -30,6 +30,6 @@ "ERC5115Form": "0x93f5fD75460aC5F0686eBfE22e556F1129F504B0", "OneInchValidator": "0x480140a26c3eb10F0F17e56495CE588320f45Cfe", "ERC5115To4626WrapperFactory": "0x14Bc2728DaE89FE7c828833a186DdC5E9AE439C3", - "SuperformRouterPlus": "0x82D3EF5e1005559fc6D577a730Bb201De1F7Cc6d", - "SuperformRouterPlusAsync": "0x47aa92F7bb7c018046079BDaA94E5618cbCe151f" + "SuperformRouterPlus": "0xA4bf3ab533Bd9dd0cC8C71D273B748E968065249", + "SuperformRouterPlusAsync": "0xbc1300e95393cF481b7c2d29d85B54689ef04fc8" } \ No newline at end of file diff --git a/script/deployments/v1_staging_deployment/59144/Linea-latest.json b/script/deployments/v1_staging_deployment/59144/Linea-latest.json index 3c397eda9..21f85d21b 100644 --- a/script/deployments/v1_staging_deployment/59144/Linea-latest.json +++ b/script/deployments/v1_staging_deployment/59144/Linea-latest.json @@ -30,6 +30,6 @@ "VaultClaimer": "0xf1930eD240cF9c4F1840aDB689E5d231687922C5", "WormholeARImplementation": "0x0000000000000000000000000000000000000000", "WormholeSRImplementation": "0x0000000000000000000000000000000000000000", - "SuperformRouterPlus": "0x82D3EF5e1005559fc6D577a730Bb201De1F7Cc6d", - "SuperformRouterPlusAsync": "0x47aa92F7bb7c018046079BDaA94E5618cbCe151f" + "SuperformRouterPlus": "0xA4bf3ab533Bd9dd0cC8C71D273B748E968065249", + "SuperformRouterPlusAsync": "0xbc1300e95393cF481b7c2d29d85B54689ef04fc8" } \ No newline at end of file diff --git a/script/deployments/v1_staging_deployment/81457/Blast-latest.json b/script/deployments/v1_staging_deployment/81457/Blast-latest.json index 07467f048..2de5eafc8 100644 --- a/script/deployments/v1_staging_deployment/81457/Blast-latest.json +++ b/script/deployments/v1_staging_deployment/81457/Blast-latest.json @@ -30,6 +30,6 @@ "VaultClaimer": "0xf1930eD240cF9c4F1840aDB689E5d231687922C5", "WormholeARImplementation": "0x71ec658F19AcF74D258c55A025ADC534c34EcaDA", "WormholeSRImplementation": "0x44b451Ca87267a62A0C853ECFbaaC1C3E528a82C", - "SuperformRouterPlus": "0x82D3EF5e1005559fc6D577a730Bb201De1F7Cc6d", - "SuperformRouterPlusAsync": "0x47aa92F7bb7c018046079BDaA94E5618cbCe151f" + "SuperformRouterPlus": "0xA4bf3ab533Bd9dd0cC8C71D273B748E968065249", + "SuperformRouterPlusAsync": "0xbc1300e95393cF481b7c2d29d85B54689ef04fc8" } \ No newline at end of file diff --git a/script/deployments/v1_staging_deployment/8453/Base-latest.json b/script/deployments/v1_staging_deployment/8453/Base-latest.json index dafdea323..0e0fc5dab 100644 --- a/script/deployments/v1_staging_deployment/8453/Base-latest.json +++ b/script/deployments/v1_staging_deployment/8453/Base-latest.json @@ -30,6 +30,6 @@ "ERC5115Form": "0x93f5fD75460aC5F0686eBfE22e556F1129F504B0", "OneInchValidator": "0x480140a26c3eb10F0F17e56495CE588320f45Cfe", "ERC5115To4626WrapperFactory": "0x14Bc2728DaE89FE7c828833a186DdC5E9AE439C3", - "SuperformRouterPlus": "0x82D3EF5e1005559fc6D577a730Bb201De1F7Cc6d", - "SuperformRouterPlusAsync": "0x47aa92F7bb7c018046079BDaA94E5618cbCe151f" + "SuperformRouterPlus": "0xA4bf3ab533Bd9dd0cC8C71D273B748E968065249", + "SuperformRouterPlusAsync": "0xbc1300e95393cF481b7c2d29d85B54689ef04fc8" } \ No newline at end of file diff --git a/script/forge-scripts/misc/Abstract.Deploy.RouterPlus.s.sol b/script/forge-scripts/misc/Abstract.Deploy.RouterPlus.s.sol index c4970ad2f..03b7b69bf 100644 --- a/script/forge-scripts/misc/Abstract.Deploy.RouterPlus.s.sol +++ b/script/forge-scripts/misc/Abstract.Deploy.RouterPlus.s.sol @@ -56,18 +56,6 @@ abstract contract AbstractDeployRouterPlus is EnvironmentUtils { keccak256("SUPERFORM_ROUTER_PLUS_ASYNC"), superformRouterPlusAsync, vars.chainId ); - /// @dev below part is already done - /* - SuperRegistry(superRegistry).setAddress( - keccak256("ROUTER_PLUS_PROCESSOR_ROLE"), ROUTER_PLUS_PROCESSOR, vars.chainId - ); - - vars.superRBACC = SuperRBAC(payable(_readContractsV1(env, chainNames[trueIndex], vars.chainId, "SuperRBAC"))); - - vars.superRBACC.setRoleAdmin(keccak256("ROUTER_PLUS_PROCESSOR_ROLE"), vars.superRBACC.PROTOCOL_ADMIN_ROLE()); - vars.superRBACC.grantRole(keccak256("ROUTER_PLUS_PROCESSOR_ROLE"), ROUTER_PLUS_PROCESSOR); - */ - vm.stopBroadcast(); /// @dev we use normal export contract to not override v1 contracts diff --git a/script/utils/misc/run_script_mainnet_staging_routerplus.sh b/script/utils/misc/run_script_mainnet_staging_routerplus.sh index 8ea66e36b..15db00aa2 100644 --- a/script/utils/misc/run_script_mainnet_staging_routerplus.sh +++ b/script/utils/misc/run_script_mainnet_staging_routerplus.sh @@ -14,46 +14,24 @@ export BLAST_RPC_URL=$(op read op://5ylebqljbh3x6zomdxi3qd7tsa/BLAST_RPC_URL/cre # Run the script echo Deploying Router Plus and Router Plus Async: ... -< asyncStateRegistry.syncWithdrawTxDataPayloadCounter()) { + if (timelockPayloadId_ > timelockStateRegistry.timelockPayloadCounter()) { revert Error.INVALID_PAYLOAD_ID(); } - SyncWithdrawTxDataPayload memory payload = - asyncStateRegistry.getSyncWithdrawTxDataPayload(syncWithdrawPayloadId_); + TimelockPayload memory payload = timelockStateRegistry.getTimelockPayload(timelockPayloadId_); return ( payload.data.receiverAddress, @@ -173,24 +175,24 @@ contract PayloadHelper is IPayloadHelper { } /// @inheritdoc IPayloadHelper - function decodeAsyncAckPayload(uint256 payloadId_) + function decodeTimeLockFailedPayload(uint256 payloadId_) external view override returns (address srcSender, uint64 srcChainId, uint256 srcPayloadId, uint256 superformId, uint256 amount) { - IBaseStateRegistry asyncStateRegistry = - IBaseStateRegistry(superRegistry.getAddress(keccak256("ASYNC_STATE_REGISTRY"))); + IBaseStateRegistry timelockPayloadRegistry = + IBaseStateRegistry(superRegistry.getAddress(keccak256("TIMELOCK_STATE_REGISTRY"))); - _isValidPayloadId(payloadId_, asyncStateRegistry); + _isValidPayloadId(payloadId_, timelockPayloadRegistry); - bytes memory payloadBody = asyncStateRegistry.payloadBody(payloadId_); - uint256 payloadHeader = asyncStateRegistry.payloadHeader(payloadId_); + bytes memory payloadBody = timelockPayloadRegistry.payloadBody(payloadId_); + uint256 payloadHeader = timelockPayloadRegistry.payloadHeader(payloadId_); (, uint8 callbackType_,,, address srcSender_, uint64 srcChainId_) = payloadHeader.decodeTxInfo(); - /// @dev callback type can never be INIT - if (callbackType_ == uint256(CallbackType.RETURN) || callbackType_ == uint256(CallbackType.FAIL)) { + /// @dev callback type can never be INIT / RETURN + if (callbackType_ == uint256(CallbackType.FAIL)) { ReturnSingleData memory rsd = abi.decode(payloadBody, (ReturnSingleData)); amount = rsd.amount; superformId = rsd.superformId; @@ -218,84 +220,84 @@ contract PayloadHelper is IPayloadHelper { } function _decodePayloadHeader( - DecodedDstPayload memory v_, uint256 dstPayloadId_, IBaseStateRegistry coreStateRegistry_ ) internal view + returns (uint8 txType, uint8 callbackType, uint8 multi, address srcSender, uint64 srcChainId) { - (v_.txType, v_.callbackType, v_.multi,, v_.srcSender, v_.srcChainId) = + (txType, callbackType, multi,, srcSender, srcChainId) = coreStateRegistry_.payloadHeader(dstPayloadId_).decodeTxInfo(); } function _decodeReturnData( - DecodedDstPayload memory v_, uint256 dstPayloadId_, + uint8 multi_, IBaseStateRegistry coreStateRegistry_ ) internal view + returns (uint256[] memory amounts, uint256 srcPayloadId) { - if (v_.multi == 1) { + if (multi_ == 1) { ReturnMultiData memory rd = abi.decode(coreStateRegistry_.payloadBody(dstPayloadId_), (ReturnMultiData)); - v_.amounts = rd.amounts; - v_.srcPayloadId = rd.payloadId; + return (rd.amounts, rd.payloadId); } else { ReturnSingleData memory rsd = abi.decode(coreStateRegistry_.payloadBody(dstPayloadId_), (ReturnSingleData)); - v_.amounts = new uint256[](1); - v_.amounts[0] = rsd.amount; - - v_.srcPayloadId = rsd.payloadId; + amounts = new uint256[](1); + amounts[0] = rsd.amount; + return (amounts, rsd.payloadId); } } function _decodeInitData( - DecodedDstPayload memory v_, uint256 dstPayloadId_, - IBaseStateRegistry coreStateRegistry_ + uint8 multi_, + IBaseStateRegistry coreStateRegistry_, + DecodedDstPayload memory v ) internal view { - if (v_.multi == 1) { + if (multi_ == 1) { InitMultiVaultData memory imvd = abi.decode(coreStateRegistry_.payloadBody(dstPayloadId_), (InitMultiVaultData)); - v_.amounts = imvd.amounts; - v_.outputAmounts = imvd.outputAmounts; - v_.slippages = imvd.maxSlippages; - v_.superformIds = imvd.superformIds; - v_.hasDstSwaps = imvd.hasDstSwaps; - v_.extraFormData = imvd.extraFormData; - v_.receiverAddress = imvd.receiverAddress; - v_.srcPayloadId = imvd.payloadId; - v_.retain4626 = imvd.retain4626s; + v.amounts = imvd.amounts; + v.outputAmounts = imvd.outputAmounts; + v.slippages = imvd.maxSlippages; + v.superformIds = imvd.superformIds; + v.hasDstSwaps = imvd.hasDstSwaps; + v.extraFormData = imvd.extraFormData; + v.receiverAddress = imvd.receiverAddress; + v.srcPayloadId = imvd.payloadId; + v.retain4626 = imvd.retain4626s; } else { InitSingleVaultData memory isvd = abi.decode(coreStateRegistry_.payloadBody(dstPayloadId_), (InitSingleVaultData)); - v_.amounts = new uint256[](1); - v_.amounts[0] = isvd.amount; + v.amounts = new uint256[](1); + v.amounts[0] = isvd.amount; - v_.outputAmounts = new uint256[](1); - v_.outputAmounts[0] = isvd.outputAmount; + v.outputAmounts = new uint256[](1); + v.outputAmounts[0] = isvd.outputAmount; - v_.slippages = new uint256[](1); - v_.slippages[0] = isvd.maxSlippage; + v.slippages = new uint256[](1); + v.slippages[0] = isvd.maxSlippage; - v_.superformIds = new uint256[](1); - v_.superformIds[0] = isvd.superformId; + v.superformIds = new uint256[](1); + v.superformIds[0] = isvd.superformId; - v_.hasDstSwaps = new bool[](1); - v_.hasDstSwaps[0] = isvd.hasDstSwap; + v.hasDstSwaps = new bool[](1); + v.hasDstSwaps[0] = isvd.hasDstSwap; - v_.retain4626 = new bool[](1); - v_.retain4626[0] = isvd.retain4626; + v.retain4626 = new bool[](1); + v.retain4626[0] = isvd.retain4626; - v_.extraFormData = isvd.extraFormData; - v_.receiverAddress = isvd.receiverAddress; - v_.srcPayloadId = isvd.payloadId; + v.extraFormData = isvd.extraFormData; + v.receiverAddress = isvd.receiverAddress; + v.srcPayloadId = isvd.payloadId; } } diff --git a/src/crosschain-data/utils/PayloadHelperV2.sol b/src/crosschain-data/utils/PayloadHelperV2.sol new file mode 100644 index 000000000..d83b18ffb --- /dev/null +++ b/src/crosschain-data/utils/PayloadHelperV2.sol @@ -0,0 +1,392 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.23; + +import { IBaseStateRegistry } from "src/interfaces/IBaseStateRegistry.sol"; +import { IAsyncStateRegistry, SyncWithdrawTxDataPayload } from "src/interfaces/IAsyncStateRegistry.sol"; +import { IPayloadHelper } from "src/interfaces/IPayloadHelperV2.sol"; +import { IBridgeValidator } from "src/interfaces/IBridgeValidator.sol"; +import { ISuperRegistry } from "src/interfaces/ISuperRegistry.sol"; +import { ISuperPositions } from "src/interfaces/ISuperPositions.sol"; +import { DataLib } from "src/libraries/DataLib.sol"; +import { ProofLib } from "src/libraries/ProofLib.sol"; +import { Error } from "src/libraries/Error.sol"; +import { + CallbackType, + ReturnMultiData, + ReturnSingleData, + InitMultiVaultData, + InitSingleVaultData, + AMBMessage +} from "src/types/DataTypes.sol"; + +/// @title PayloadHelper +/// @dev Helps decode payload data for off-chain purposes +/// @author ZeroPoint Labs +contract PayloadHelper is IPayloadHelper { + using DataLib for uint256; + + ////////////////////////////////////////////////////////////// + // CONSTANTS // + ////////////////////////////////////////////////////////////// + + ISuperRegistry public immutable superRegistry; + + ////////////////////////////////////////////////////////////// + // STRUCTS // + ////////////////////////////////////////////////////////////// + + struct DecodeDstPayloadLiqDataInternalVars { + uint8 callbackType; + uint8 multi; + uint8[] bridgeIds; + bytes[] txDatas; + address[] liqDataTokens; + uint64[] liqDataChainIds; + uint256[] liqDataAmountsIn; + uint256[] liqDataNativeAmounts; + InitMultiVaultData imvd; + InitSingleVaultData isvd; + uint256 i; + } + + ////////////////////////////////////////////////////////////// + // CONSTRUCTOR // + ////////////////////////////////////////////////////////////// + + constructor(address superRegistry_) { + if (superRegistry_ == address(0)) { + revert Error.ZERO_ADDRESS(); + } + superRegistry = ISuperRegistry(superRegistry_); + } + + ////////////////////////////////////////////////////////////// + // EXTERNAL VIEW FUNCTIONS // + ////////////////////////////////////////////////////////////// + + /// @inheritdoc IPayloadHelper + function decodeCoreStateRegistryPayload(uint256 dstPayloadId_) + external + view + override + returns (DecodedDstPayload memory v) + { + _isValidPayloadId(dstPayloadId_, _getCoreStateRegistry()); + _decodePayloadHeader(v, dstPayloadId_, _getCoreStateRegistry()); + + if (v.callbackType == uint256(CallbackType.RETURN) || v.callbackType == uint256(CallbackType.FAIL)) { + _decodeReturnData(v, dstPayloadId_, _getCoreStateRegistry()); + } else if (v.callbackType == uint256(CallbackType.INIT)) { + _decodeInitData(v, dstPayloadId_, _getCoreStateRegistry()); + } else { + revert Error.INVALID_PAYLOAD(); + } + } + + /// @inheritdoc IPayloadHelper + function decodeCoreStateRegistryPayloadLiqData(uint256 dstPayloadId_) + external + view + override + returns ( + bytes[] memory txDatas, + address[] memory tokens, + address[] memory interimTokens, + uint8[] memory bridgeIds, + uint64[] memory liqDstChainIds, + uint256[] memory amountsIn, + uint256[] memory nativeAmounts + ) + { + IBaseStateRegistry coreStateRegistry = _getCoreStateRegistry(); + _isValidPayloadId(dstPayloadId_, coreStateRegistry); + + DecodeDstPayloadLiqDataInternalVars memory v; + (, v.callbackType, v.multi,,,) = coreStateRegistry.payloadHeader(dstPayloadId_).decodeTxInfo(); + + if (v.multi == 1) { + return _decodeMultiLiqData(dstPayloadId_, coreStateRegistry); + } else { + return _decodeSingleLiqData(dstPayloadId_, coreStateRegistry); + } + } + + /// @inheritdoc IPayloadHelper + function decodePayloadHistory(uint256 srcPayloadId_) + external + view + override + returns ( + uint8 txType, + uint8 callbackType, + uint8 multi, + address srcSender, + address receiverAddressSP, + uint64 srcChainId + ) + { + uint256 txInfo; + (txInfo, receiverAddressSP) = + ISuperPositions(superRegistry.getAddress(keccak256("SUPER_POSITIONS"))).txHistory(srcPayloadId_); + + if (txInfo == 0) { + revert Error.INVALID_PAYLOAD_ID(); + } + + (txType, callbackType, multi,, srcSender, srcChainId) = txInfo.decodeTxInfo(); + } + + /// @inheritdoc IPayloadHelper + function decodeSyncWithdrawPayload(uint256 syncWithdrawPayloadId_) + external + view + override + returns (address receiverAddress, uint64 srcChainId, uint256 srcPayloadId, uint256 superformId, uint256 amount) + { + IAsyncStateRegistry asyncStateRegistry = + IAsyncStateRegistry(superRegistry.getAddress(keccak256("ASYNC_STATE_REGISTRY"))); + + if (syncWithdrawPayloadId_ > asyncStateRegistry.syncWithdrawTxDataPayloadCounter()) { + revert Error.INVALID_PAYLOAD_ID(); + } + + SyncWithdrawTxDataPayload memory payload = + asyncStateRegistry.getSyncWithdrawTxDataPayload(syncWithdrawPayloadId_); + + return ( + payload.data.receiverAddress, + payload.srcChainId, + payload.data.payloadId, + payload.data.superformId, + payload.data.amount + ); + } + + /// @inheritdoc IPayloadHelper + function getDstPayloadProof(uint256 dstPayloadId_) external view override returns (bytes32) { + IBaseStateRegistry coreStateRegistry = + IBaseStateRegistry(superRegistry.getAddress(keccak256("CORE_STATE_REGISTRY"))); + + return ProofLib.computeProof( + AMBMessage(coreStateRegistry.payloadHeader(dstPayloadId_), coreStateRegistry.payloadBody(dstPayloadId_)) + ); + } + + /// @inheritdoc IPayloadHelper + function decodeAsyncAckPayload(uint256 payloadId_) + external + view + override + returns (address srcSender, uint64 srcChainId, uint256 srcPayloadId, uint256 superformId, uint256 amount) + { + IBaseStateRegistry asyncStateRegistry = + IBaseStateRegistry(superRegistry.getAddress(keccak256("ASYNC_STATE_REGISTRY"))); + + _isValidPayloadId(payloadId_, asyncStateRegistry); + + bytes memory payloadBody = asyncStateRegistry.payloadBody(payloadId_); + uint256 payloadHeader = asyncStateRegistry.payloadHeader(payloadId_); + + (, uint8 callbackType_,,, address srcSender_, uint64 srcChainId_) = payloadHeader.decodeTxInfo(); + + /// @dev callback type can never be INIT + if (callbackType_ == uint256(CallbackType.RETURN) || callbackType_ == uint256(CallbackType.FAIL)) { + ReturnSingleData memory rsd = abi.decode(payloadBody, (ReturnSingleData)); + amount = rsd.amount; + superformId = rsd.superformId; + srcPayloadId = rsd.payloadId; + } else { + revert Error.INVALID_PAYLOAD(); + } + + srcSender = srcSender_; + srcChainId = srcChainId_; + } + + ////////////////////////////////////////////////////////////// + // INTERNAL FUNCTIONS // + ////////////////////////////////////////////////////////////// + + function _isValidPayloadId(uint256 payloadId_, IBaseStateRegistry stateRegistry) internal view { + if (payloadId_ > stateRegistry.payloadsCount()) { + revert Error.INVALID_PAYLOAD_ID(); + } + } + + function _getCoreStateRegistry() internal view returns (IBaseStateRegistry) { + return IBaseStateRegistry(superRegistry.getAddress(keccak256("CORE_STATE_REGISTRY"))); + } + + function _decodePayloadHeader( + DecodedDstPayload memory v_, + uint256 dstPayloadId_, + IBaseStateRegistry coreStateRegistry_ + ) + internal + view + { + (v_.txType, v_.callbackType, v_.multi,, v_.srcSender, v_.srcChainId) = + coreStateRegistry_.payloadHeader(dstPayloadId_).decodeTxInfo(); + } + + function _decodeReturnData( + DecodedDstPayload memory v_, + uint256 dstPayloadId_, + IBaseStateRegistry coreStateRegistry_ + ) + internal + view + { + if (v_.multi == 1) { + ReturnMultiData memory rd = abi.decode(coreStateRegistry_.payloadBody(dstPayloadId_), (ReturnMultiData)); + v_.amounts = rd.amounts; + v_.srcPayloadId = rd.payloadId; + } else { + ReturnSingleData memory rsd = abi.decode(coreStateRegistry_.payloadBody(dstPayloadId_), (ReturnSingleData)); + v_.amounts = new uint256[](1); + v_.amounts[0] = rsd.amount; + + v_.srcPayloadId = rsd.payloadId; + } + } + + function _decodeInitData( + DecodedDstPayload memory v_, + uint256 dstPayloadId_, + IBaseStateRegistry coreStateRegistry_ + ) + internal + view + { + if (v_.multi == 1) { + InitMultiVaultData memory imvd = + abi.decode(coreStateRegistry_.payloadBody(dstPayloadId_), (InitMultiVaultData)); + + v_.amounts = imvd.amounts; + v_.outputAmounts = imvd.outputAmounts; + v_.slippages = imvd.maxSlippages; + v_.superformIds = imvd.superformIds; + v_.hasDstSwaps = imvd.hasDstSwaps; + v_.extraFormData = imvd.extraFormData; + v_.receiverAddress = imvd.receiverAddress; + v_.srcPayloadId = imvd.payloadId; + v_.retain4626 = imvd.retain4626s; + } else { + InitSingleVaultData memory isvd = + abi.decode(coreStateRegistry_.payloadBody(dstPayloadId_), (InitSingleVaultData)); + + v_.amounts = new uint256[](1); + v_.amounts[0] = isvd.amount; + + v_.outputAmounts = new uint256[](1); + v_.outputAmounts[0] = isvd.outputAmount; + + v_.slippages = new uint256[](1); + v_.slippages[0] = isvd.maxSlippage; + + v_.superformIds = new uint256[](1); + v_.superformIds[0] = isvd.superformId; + + v_.hasDstSwaps = new bool[](1); + v_.hasDstSwaps[0] = isvd.hasDstSwap; + + v_.retain4626 = new bool[](1); + v_.retain4626[0] = isvd.retain4626; + + v_.extraFormData = isvd.extraFormData; + v_.receiverAddress = isvd.receiverAddress; + v_.srcPayloadId = isvd.payloadId; + } + } + + function _decodeMultiLiqData( + uint256 dstPayloadId_, + IBaseStateRegistry coreStateRegistry_ + ) + internal + view + returns ( + bytes[] memory txDatas, + address[] memory tokens, + address[] memory interimTokens, + uint8[] memory bridgeIds, + uint64[] memory liqDstChainIds, + uint256[] memory amountsIn, + uint256[] memory nativeAmounts + ) + { + InitMultiVaultData memory imvd = abi.decode(coreStateRegistry_.payloadBody(dstPayloadId_), (InitMultiVaultData)); + + bridgeIds = new uint8[](imvd.liqData.length); + txDatas = new bytes[](imvd.liqData.length); + tokens = new address[](imvd.liqData.length); + interimTokens = new address[](imvd.liqData.length); + liqDstChainIds = new uint64[](imvd.liqData.length); + amountsIn = new uint256[](imvd.liqData.length); + nativeAmounts = new uint256[](imvd.liqData.length); + + uint256 len = imvd.liqData.length; + + for (uint256 i; i < len; ++i) { + bridgeIds[i] = imvd.liqData[i].bridgeId; + txDatas[i] = imvd.liqData[i].txData; + tokens[i] = imvd.liqData[i].token; + interimTokens[i] = imvd.liqData[i].interimToken; + liqDstChainIds[i] = imvd.liqData[i].liqDstChainId; + + /// @dev decodes amount from txdata only if its present + if (imvd.liqData[i].txData.length != 0) { + amountsIn[i] = + IBridgeValidator(superRegistry.getBridgeValidator(bridgeIds[i])).decodeAmountIn(txDatas[i], false); + } + + nativeAmounts[i] = imvd.liqData[i].nativeAmount; + } + } + + function _decodeSingleLiqData( + uint256 dstPayloadId_, + IBaseStateRegistry coreStateRegistry_ + ) + internal + view + returns ( + bytes[] memory txDatas, + address[] memory tokens, + address[] memory interimTokens, + uint8[] memory bridgeIds, + uint64[] memory liqDstChainIds, + uint256[] memory amountsIn, + uint256[] memory nativeAmounts + ) + { + InitSingleVaultData memory isvd = + abi.decode(coreStateRegistry_.payloadBody(dstPayloadId_), (InitSingleVaultData)); + + bridgeIds = new uint8[](1); + bridgeIds[0] = isvd.liqData.bridgeId; + + txDatas = new bytes[](1); + txDatas[0] = isvd.liqData.txData; + + tokens = new address[](1); + tokens[0] = isvd.liqData.token; + + interimTokens = new address[](1); + interimTokens[0] = isvd.liqData.interimToken; + + liqDstChainIds = new uint64[](1); + liqDstChainIds[0] = isvd.liqData.liqDstChainId; + + amountsIn = new uint256[](1); + + /// @dev decodes amount from txdata only if its present + if (isvd.liqData.txData.length != 0) { + amountsIn[0] = + IBridgeValidator(superRegistry.getBridgeValidator(bridgeIds[0])).decodeAmountIn(txDatas[0], false); + } + + nativeAmounts = new uint256[](1); + nativeAmounts[0] = isvd.liqData.nativeAmount; + } +} diff --git a/src/crosschain-liquidity/DstSwapper.sol b/src/crosschain-liquidity/DstSwapper.sol index e56226cda..49b150809 100644 --- a/src/crosschain-liquidity/DstSwapper.sol +++ b/src/crosschain-liquidity/DstSwapper.sol @@ -15,8 +15,6 @@ import { IERC20 } from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol" import { SafeERC20 } from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; import { ReentrancyGuard } from "openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol"; -import "forge-std/console.sol"; - /// @title DstSwapper /// @dev Handles all destination chain swaps /// @author Zeropoint Labs @@ -376,9 +374,6 @@ contract DstSwapper is IDstSwapper, ReentrancyGuard, LiquidityHandler { v.balanceDiff = v.balanceAfter - v.balanceBefore; - console.log("v.balanceDiff", v.balanceDiff); - console.log("v.expAmount", v.expAmount); - /// @dev if actual underlying is less than expAmount adjusted with maxSlippage, invariant breaks if (v.balanceDiff * ENTIRE_SLIPPAGE < v.expAmount * (ENTIRE_SLIPPAGE - v.maxSlippage)) { revert Error.SLIPPAGE_OUT_OF_BOUNDS(); diff --git a/src/interfaces/IPayloadHelper.sol b/src/interfaces/IPayloadHelper.sol index ef8e6f969..9239960c8 100644 --- a/src/interfaces/IPayloadHelper.sol +++ b/src/interfaces/IPayloadHelper.sol @@ -31,7 +31,6 @@ interface IPayloadHelper { uint256[] slippages; uint256[] superformIds; bool[] hasDstSwaps; - bool[] retain4626s; address receiverAddress; uint256 srcPayloadId; bytes extraFormData; @@ -94,16 +93,16 @@ interface IPayloadHelper { uint64 srcChainId ); - /// @dev returns decoded sync withdraw form payloads - /// @param syncWithdrawPayloadId_ is the unique identifier of payload in async state registry - function decodeSyncWithdrawPayload(uint256 syncWithdrawPayloadId_) + /// @dev returns decoded timelock form payloads + /// @param timelockPayloadId_ is the unique identifier of payload in timelock state registry + function decodeTimeLockPayload(uint256 timelockPayloadId_) external view returns (address receiverAddress, uint64 srcChainId, uint256 srcPayloadId, uint256 superformId, uint256 amount); - /// @dev returns decoded successful async payloads - /// @param payloadId_ is the unique identifier of payload in async state registry - function decodeAsyncAckPayload(uint256 payloadId_) + /// @dev returns decoded failed timelock form payloads + /// @param timelockPayloadId_ is the unique identifier of payload in timelock state registry + function decodeTimeLockFailedPayload(uint256 timelockPayloadId_) external view returns (address srcSender, uint64 srcChainId, uint256 srcPayloadId, uint256 superformId, uint256 amount); @@ -112,4 +111,4 @@ interface IPayloadHelper { /// @param dstPayloadId_ is the unique identifier of payload in dst core state registry /// @return proof is the proof for the payload function getDstPayloadProof(uint256 dstPayloadId_) external view returns (bytes32); -} +} \ No newline at end of file diff --git a/src/interfaces/IPayloadHelperV2.sol b/src/interfaces/IPayloadHelperV2.sol new file mode 100644 index 000000000..ef8e6f969 --- /dev/null +++ b/src/interfaces/IPayloadHelperV2.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.23; + +/// @title IPayloadHelper +/// @dev Interface for PayloadHelper +/// @author ZeroPoint Labs +interface IPayloadHelper { + ////////////////////////////////////////////////////////////// + // STRUCTS // + ////////////////////////////////////////////////////////////// + + /// @notice txType is the type of transaction. check {TransactionType} enum in DataTypes.sol + /// @notice callbackType is the type of payload. check {CallbackType} enum in DataTypes.sol + /// @notice srcSender is the user who initiated the transaction on the srcChain + /// @notice srcChainId is the unique identifier of the srcChain + /// @notice amounts are the amounts to deposit/withdraw + /// @notice outputAmounts are the expected outputAmounts specified by user + /// @notice slippages are the max slippages configured by the user (only for deposits) + /// @notice superformIds are the unique identifiers of the superforms + /// @notice hasDstSwaps are the array of flags indicating if the original liqData has a dstSwaps + /// @notice extraFormData is the extra form data (optional: passed for forms with special needs) + /// @notice receiverAddress is the address to be used for refunds + /// @notice srcPayloadId is the identifier of the corresponding payload on srcChain + struct DecodedDstPayload { + uint8 txType; + uint8 callbackType; + address srcSender; + uint64 srcChainId; + uint256[] amounts; + uint256[] outputAmounts; + uint256[] slippages; + uint256[] superformIds; + bool[] hasDstSwaps; + bool[] retain4626s; + address receiverAddress; + uint256 srcPayloadId; + bytes extraFormData; + uint8 multi; + bool[] retain4626; + } + + ////////////////////////////////////////////////////////////// + // EXTERNAL VIEW FUNCTIONS // + ////////////////////////////////////////////////////////////// + + /// @dev reads the payload from the core state registry and decodes it in a more detailed manner. + /// @param dstPayloadId_ is the unique identifier of the payload received in dst core state registry + /// @return decodedDstPayload is the details of the payload, refer DecodedDstPayload struct for info + function decodeCoreStateRegistryPayload(uint256 dstPayloadId_) + external + view + returns (DecodedDstPayload memory decodedDstPayload); + + /// @dev reads the payload from the core state registry and decodes liqData for it (to be used in withdraw cases) + /// @param dstPayloadId_ is the unique identifier of the payload received in dst core state registry + /// @return txDatas are the array of txData to be sent to the bridges + /// @return tokens are the tokens to be used in the liqData + /// @return interimTokens are the interim tokens to be used in the liqData + /// @return bridgeIds are the ids of the bridges to be used + /// @return liqDstChainIds are the final destination chain id for the underlying token (can be arbitrary on + /// withdraws) + /// @return amountsIn are the from amounts to the liquidity bridge + /// @return nativeAmounts are the native amounts to be used in the liqData + function decodeCoreStateRegistryPayloadLiqData(uint256 dstPayloadId_) + external + view + returns ( + bytes[] memory txDatas, + address[] memory tokens, + address[] memory interimTokens, + uint8[] memory bridgeIds, + uint64[] memory liqDstChainIds, + uint256[] memory amountsIn, + uint256[] memory nativeAmounts + ); + + /// @dev reads the payload header from superPositions and decodes it. + /// @param srcPayloadId_ is the unique identifier of the payload allocated by superform router + /// @return txType is the type of transaction. check {TransactionType} enum in DataTypes.sol + /// @return callbackType is the type of payload. check {CallbackType} enum in DataTypes.sol + /// @return isMulti indicates if the transaction involves operations to multiple vaults + /// @return srcSender is the user who initiated the transaction on the srcChain + /// @return receiverAddressSP is the address to be used for receiving Super Positions + /// @return srcChainId is the unique identifier of the srcChain + function decodePayloadHistory(uint256 srcPayloadId_) + external + view + returns ( + uint8 txType, + uint8 callbackType, + uint8 isMulti, + address srcSender, + address receiverAddressSP, + uint64 srcChainId + ); + + /// @dev returns decoded sync withdraw form payloads + /// @param syncWithdrawPayloadId_ is the unique identifier of payload in async state registry + function decodeSyncWithdrawPayload(uint256 syncWithdrawPayloadId_) + external + view + returns (address receiverAddress, uint64 srcChainId, uint256 srcPayloadId, uint256 superformId, uint256 amount); + + /// @dev returns decoded successful async payloads + /// @param payloadId_ is the unique identifier of payload in async state registry + function decodeAsyncAckPayload(uint256 payloadId_) + external + view + returns (address srcSender, uint64 srcChainId, uint256 srcPayloadId, uint256 superformId, uint256 amount); + + /// @dev returns proof for payloads + /// @param dstPayloadId_ is the unique identifier of payload in dst core state registry + /// @return proof is the proof for the payload + function getDstPayloadProof(uint256 dstPayloadId_) external view returns (bytes32); +} diff --git a/src/interfaces/ITimelockStateRegistry.sol b/src/interfaces/ITimelockStateRegistry.sol new file mode 100644 index 000000000..cb557dbb9 --- /dev/null +++ b/src/interfaces/ITimelockStateRegistry.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.23; + +import { InitSingleVaultData, TimelockPayload } from "src/types/DataTypes.sol"; + +/// @title ITimelockStateRegistry +/// @dev Interface for TimelockStateRegistry +/// @author ZeroPoint Labs +interface ITimelockStateRegistry { + ////////////////////////////////////////////////////////////// + // EXTERNAL VIEW FUNCTIONS // + ////////////////////////////////////////////////////////////// + + /// @dev allows users to read the timeLockPayload_ stored per payloadId_ + /// @param payloadId_ is the unique payload identifier allocated on the destination chain + /// @return timeLockPayload_ the timelock payload stored + function getTimelockPayload(uint256 payloadId_) external view returns (TimelockPayload memory timeLockPayload_); + + /// @dev allows users to read the timelockPayloadCounter + function timelockPayloadCounter() external view returns (uint256); + + ////////////////////////////////////////////////////////////// + // EXTERNAL WRITE FUNCTIONS // + ////////////////////////////////////////////////////////////// + + /// @notice Receives request (payload) from timelock form to process later + /// @param type_ is the nature of transaction (xChain: 1 or same chain: 0) + /// @param srcChainId_ is the chainId of the source chain + /// @param lockedTill_ is the deadline for timelock (after which we can call `finalizePayload`) + /// @param data_ is the basic information of superformId, amount to withdraw of type InitSingleVaultData + function receivePayload( + uint8 type_, + uint64 srcChainId_, + uint256 lockedTill_, + InitSingleVaultData memory data_ + ) + external; + + /// @notice Form Keeper finalizes payload to process timelock withdraw fully + /// @param payloadId_ is the id of the payload to finalize + /// @param txData_ is the off-chain generated transaction data + function finalizePayload(uint256 payloadId_, bytes memory txData_) external payable; +} diff --git a/test/unit/crosschain-data/utils/PayloadHelper.multiVault.t.sol b/test/unit/crosschain-data/utils/PayloadHelper.multiVault.t.sol index 1b3d26ec4..bed2734c6 100644 --- a/test/unit/crosschain-data/utils/PayloadHelper.multiVault.t.sol +++ b/test/unit/crosschain-data/utils/PayloadHelper.multiVault.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.23; /// Interfaces -import { IPayloadHelper } from "src/interfaces/IPayloadHelper.sol"; +import { IPayloadHelper } from "src/interfaces/IPayloadHelperV2.sol"; import { IPaymentHelperV2 as IPaymentHelper } from "src/interfaces/IPaymentHelperV2.sol"; import { DataLib } from "src/libraries/DataLib.sol"; diff --git a/test/unit/crosschain-data/utils/PayloadHelper.singleVault.t.sol b/test/unit/crosschain-data/utils/PayloadHelper.singleVault.t.sol index 2ed5e8cb7..b9bade51b 100644 --- a/test/unit/crosschain-data/utils/PayloadHelper.singleVault.t.sol +++ b/test/unit/crosschain-data/utils/PayloadHelper.singleVault.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.23; /// Interfaces -import { IPayloadHelper } from "src/interfaces/IPayloadHelper.sol"; +import { IPayloadHelper } from "src/interfaces/IPayloadHelperV2.sol"; import { IPaymentHelperV2 as IPaymentHelper } from "src/interfaces/IPaymentHelperV2.sol"; import { IBaseRouter } from "src/interfaces/IBaseRouter.sol"; diff --git a/test/utils/BaseSetup.sol b/test/utils/BaseSetup.sol index 9e48e236f..437a756a1 100644 --- a/test/utils/BaseSetup.sol +++ b/test/utils/BaseSetup.sol @@ -92,7 +92,7 @@ import { IERC7540Vault as IERC7540 } from "src/vendor/centrifuge/IERC7540.sol"; import { AsyncStateRegistry } from "src/crosschain-data/extensions/AsyncStateRegistry.sol"; import { RequestConfig } from "src/interfaces/IAsyncStateRegistry.sol"; -import { PayloadHelper } from "src/crosschain-data/utils/PayloadHelper.sol"; +import { PayloadHelper } from "src/crosschain-data/utils/PayloadHelperV2.sol"; import { PaymentHelper } from "src/payments/PaymentHelper.sol"; import { IPaymentHelperV2 as IPaymentHelper } from "src/interfaces/IPaymentHelperV2.sol"; import { ISuperRBAC } from "src/interfaces/ISuperRBAC.sol"; From 45d0b8da8c86ed20c9d2df9e4e68ca6fda38a1e9 Mon Sep 17 00:00:00 2001 From: 0xTimepunk Date: Mon, 2 Dec 2024 14:14:15 +0000 Subject: [PATCH 2/2] test: fix coverage --- Makefile | 2 +- src/interfaces/IPayloadHelperV2.sol | 1 - .../utils/PayloadHelper.V2.multiVault.t.sol | 303 ++++++++++++++++++ .../utils/PayloadHelper.V2.singleVault.t.sol | 283 ++++++++++++++++ .../utils/PayloadHelper.multiVault.t.sol | 3 +- .../utils/PayloadHelper.singleVault.t.sol | 2 +- 6 files changed, 589 insertions(+), 5 deletions(-) create mode 100644 test/unit/crosschain-data/utils/PayloadHelper.V2.multiVault.t.sol create mode 100644 test/unit/crosschain-data/utils/PayloadHelper.V2.singleVault.t.sol diff --git a/Makefile b/Makefile index 6f34a00ee..ab9a85f80 100644 --- a/Makefile +++ b/Makefile @@ -121,7 +121,7 @@ build-sizes: ## Builds the project and shows sizes .PHONY: test-vvv test-vvv: ## Runs tests with verbose output - forge test --match-test test_lzConfig --evm-version cancun -vvv + forge test --match-contract PayloadHelperSingleTest --evm-version cancun .PHONY: ftest ftest: ## Runs tests with cancun evm version diff --git a/src/interfaces/IPayloadHelperV2.sol b/src/interfaces/IPayloadHelperV2.sol index ef8e6f969..6ee064270 100644 --- a/src/interfaces/IPayloadHelperV2.sol +++ b/src/interfaces/IPayloadHelperV2.sol @@ -31,7 +31,6 @@ interface IPayloadHelper { uint256[] slippages; uint256[] superformIds; bool[] hasDstSwaps; - bool[] retain4626s; address receiverAddress; uint256 srcPayloadId; bytes extraFormData; diff --git a/test/unit/crosschain-data/utils/PayloadHelper.V2.multiVault.t.sol b/test/unit/crosschain-data/utils/PayloadHelper.V2.multiVault.t.sol new file mode 100644 index 000000000..bed2734c6 --- /dev/null +++ b/test/unit/crosschain-data/utils/PayloadHelper.V2.multiVault.t.sol @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.23; + +/// Interfaces +import { IPayloadHelper } from "src/interfaces/IPayloadHelperV2.sol"; +import { IPaymentHelperV2 as IPaymentHelper } from "src/interfaces/IPaymentHelperV2.sol"; +import { DataLib } from "src/libraries/DataLib.sol"; + +// Test Utils +import "test/utils/ProtocolActions.sol"; + +contract PayloadHelperMultiTest is ProtocolActions { + /// @dev Access SuperformRouter interface + using DataLib for uint256; + + function setUp() public override { + super.setUp(); + /*////////////////////////////////////////////////////////////// + !! WARNING !! DEFINE TEST SETTINGS HERE + //////////////////////////////////////////////////////////////*/ + /// @dev singleDestinationSingleVault Deposit test case + AMBs = [1, 2]; + + CHAIN_0 = OP; + DST_CHAINS = [ETH]; + + TARGET_UNDERLYINGS[ETH][0] = [0, 0]; + TARGET_UNDERLYINGS[ETH][1] = [0, 0]; + + TARGET_VAULTS[ETH][0] = [0, 0]; + TARGET_VAULTS[ETH][1] = [0, 0]; + + TARGET_FORM_KINDS[ETH][0] = [0, 0]; + TARGET_FORM_KINDS[ETH][1] = [0, 0]; + + AMOUNTS[ETH][0] = [20_001, 214]; + + MAX_SLIPPAGE = 1000; + + LIQ_BRIDGES[ETH][0] = [1, 1]; + LIQ_BRIDGES[ETH][1] = [1, 1]; + RECEIVE_4626[ETH][0] = [false, false]; + RECEIVE_4626[ETH][1] = [false, false]; + + FINAL_LIQ_DST_WITHDRAW[ETH] = [OP, OP]; + + actions.push( + TestAction({ + action: Actions.Deposit, + multiVaults: true, + user: 0, + testType: TestType.Pass, + revertError: "", + revertRole: "", + slippage: 0, // 0% <- if we are testing a pass this must be below each maxSlippage, + dstSwap: false, + externalToken: 69_420 // 0 = DAI, 1 = USDT, 2 = WETH + }) + ); + + actions.push( + TestAction({ + action: Actions.Withdraw, + multiVaults: true, + user: 0, + testType: TestType.Pass, + revertError: "", + revertRole: "", + slippage: 0, // 0% <- if we are testing a pass this must be below each maxSlippage, + dstSwap: false, + externalToken: 2 // 0 = DAI, 1 = USDT, 2 = WETH + }) + ); + } + + /*/////////////////////////////////////////////////////////////// + SCENARIO TESTS + //////////////////////////////////////////////////////////////*/ + + function test_payloadHelperMulti() public { + for (uint256 act = 0; act < actions.length; ++act) { + TestAction memory action = actions[act]; + MultiVaultSFData[] memory multiSuperformsData; + SingleVaultSFData[] memory singleSuperformsData; + MessagingAssertVars[] memory aV; + StagesLocalVars memory vars; + bool success; + + if (act == 1) { + for (uint256 i = 0; i < DST_CHAINS.length; ++i) { + uint256[] memory superPositions = _getSuperpositionsForDstChain( + actions[1].user, + TARGET_UNDERLYINGS[DST_CHAINS[i]][1], + TARGET_VAULTS[DST_CHAINS[i]][1], + TARGET_FORM_KINDS[DST_CHAINS[i]][1], + DST_CHAINS[i] + ); + + AMOUNTS[DST_CHAINS[i]][1] = [superPositions[0] / 2, superPositions[0] / 2]; + + if (superPositions[0] != AMOUNTS[DST_CHAINS[i]][1][0] + AMOUNTS[DST_CHAINS[i]][1][1]) { + AMOUNTS[DST_CHAINS[i]][1][0] += 1; + } + } + } + + _runMainStages(action, act, multiSuperformsData, singleSuperformsData, aV, vars, success); + } + + _checkSrcPayload(); + + vm.selectFork(FORKS[ETH]); + (, int256 USDPerDAIonETH,,,) = + AggregatorV3Interface(tokenPriceFeeds[ETH][getContract(ETH, "DAI")]).latestRoundData(); + + vm.selectFork(FORKS[OP]); + (, int256 USDPerETHonOP,,,) = AggregatorV3Interface(tokenPriceFeeds[OP][NATIVE_TOKEN]).latestRoundData(); + (, int256 USDPerDAIonOP,,,) = + AggregatorV3Interface(tokenPriceFeeds[OP][getContract(OP, "DAI")]).latestRoundData(); + _checkDstPayloadInit( + CheckDstPayloadInitArgs(uint256(USDPerDAIonETH), uint256(USDPerETHonOP), uint256(USDPerDAIonOP)) + ); + + _checkDstPayloadReturn(); + } + + function test_payloadHelperLiqMulti() public { + for (uint256 act = 0; act < actions.length; ++act) { + TestAction memory action = actions[act]; + MultiVaultSFData[] memory multiSuperformsData; + SingleVaultSFData[] memory singleSuperformsData; + MessagingAssertVars[] memory aV; + StagesLocalVars memory vars; + bool success; + + if (act == 1) { + for (uint256 i = 0; i < DST_CHAINS.length; ++i) { + uint256[] memory superPositions = _getSuperpositionsForDstChain( + actions[1].user, + TARGET_UNDERLYINGS[DST_CHAINS[i]][1], + TARGET_VAULTS[DST_CHAINS[i]][1], + TARGET_FORM_KINDS[DST_CHAINS[i]][1], + DST_CHAINS[i] + ); + + AMOUNTS[DST_CHAINS[i]][1] = [superPositions[0] / 2, superPositions[0] / 2]; + + if (superPositions[0] != AMOUNTS[DST_CHAINS[i]][1][0] + AMOUNTS[DST_CHAINS[i]][1][1]) { + AMOUNTS[DST_CHAINS[i]][1][0] += 1; + } + } + } + + _runMainStages(action, act, multiSuperformsData, singleSuperformsData, aV, vars, success); + } + _checkDstPayloadLiqData( + getContract(FINAL_LIQ_DST_WITHDRAW[ETH][0], UNDERLYING_TOKENS[actions[1].externalToken]) + ); + } + + function _checkSrcPayload() internal { + vm.selectFork(FORKS[CHAIN_0]); + + address _PayloadHelper = contracts[CHAIN_0][bytes32(bytes("PayloadHelper"))]; + IPayloadHelper helper = IPayloadHelper(_PayloadHelper); + + (uint8 txType, uint8 callbackType, uint8 multi, address srcSender, address receiverAddress, uint64 srcChainId) = + helper.decodePayloadHistory(1); + + assertEq(txType, 0); + + /// 0 for deposit + assertEq(callbackType, 0); + /// 0 for init + assertEq(srcChainId, 10); + /// chain id of optimism is 10 + assertEq(multi, 1); + /// 0 for not multi vault + assertEq(srcSender, users[0]); + + assertEq(receiverAddress, users[0]); + } + + struct CheckDstPayloadInternalVars { + bytes[] extraDataGenerated; + uint256 ambFees; + uint8 txType; + uint8 callbackType; + address srcSender; + uint64 srcChainId; + uint256[] amounts; + uint256[] slippage; + uint256[] superformIds; + bool[] hasDstSwaps; + bytes extraFormData; + uint256 srcPayloadId; + address receiverAddress; + uint256 daiAfterFirstSwap; + uint256 daiAfterSecondSwap; + } + + struct CheckDstPayloadInitArgs { + uint256 USDPerDAIonETH_; + uint256 USDPerETHonOP_; + uint256 USDPerDAIonOP_; + } + + function _checkDstPayloadInit(CheckDstPayloadInitArgs memory args) internal { + vm.selectFork(FORKS[DST_CHAINS[0]]); + IPayloadHelper.DecodedDstPayload memory v = + IPayloadHelper(contracts[DST_CHAINS[0]][bytes32(bytes("PayloadHelper"))]).decodeCoreStateRegistryPayload(1); + + bytes[] memory extraDataGenerated = new bytes[](2); + extraDataGenerated[0] = abi.encode("500000"); + extraDataGenerated[1] = abi.encode("0"); + + assertEq(v.txType, 0); + + /// 0 for deposit + assertEq(v.callbackType, 0); + /// 0 for init + assertEq(v.srcChainId, 10); + /// chain id of optimism is 10 + assertEq(v.srcPayloadId, 1); + + assertEq(v.receiverAddress, users[0]); + + for (uint256 i; i < v.amounts.length; ++i) { + /// @dev ETH<>DAI swap on OP + uint256 daiAfterFirstSwap = (AMOUNTS[ETH][0][i] * args.USDPerETHonOP_) / args.USDPerDAIonOP_; + /// @dev DAI on OP <> DAI on ETH + uint256 daiAfterSecondSwap = (daiAfterFirstSwap * args.USDPerDAIonOP_) / args.USDPerDAIonETH_; + /// @dev daiAfterSecondSwap doesn't include bridge slippage hence should be greater + assertLe(v.amounts[i], daiAfterSecondSwap); + } + + for (uint256 i = 0; i < v.slippages.length; ++i) { + assertEq(v.slippages[i], MAX_SLIPPAGE); + } + + for (uint256 i; i < v.retain4626.length; ++i) { + assertEq(v.retain4626[i], false); + } + + /// @notice: just asserting if fees are greater than 0 + /// no way to write serious tests on forked testnet at this point. should come back to this later on. + (uint256 ambFees,) = IPaymentHelper(contracts[DST_CHAINS[0]][bytes32(bytes("PaymentHelper"))]).estimateAMBFees( + AMBs, DST_CHAINS[0], abi.encode(1), extraDataGenerated + ); + assertGe(ambFees, 0); + } + + struct CheckDstPayloadLiqDataInternalVars { + uint8[] bridgeIds; + bytes[] txData; + address[] tokens; + uint64[] liqDstChainIds; + uint256[] amounts; + uint256[] nativeAmounts; + } + + function _checkDstPayloadLiqData(address externalToken_) internal { + vm.selectFork(FORKS[DST_CHAINS[0]]); + CheckDstPayloadLiqDataInternalVars memory v; + + (v.txData, v.tokens,, v.bridgeIds, v.liqDstChainIds, v.amounts, v.nativeAmounts) = IPayloadHelper( + contracts[DST_CHAINS[0]][bytes32(bytes("PayloadHelper"))] + ).decodeCoreStateRegistryPayloadLiqData(2); + + assertEq(v.bridgeIds[0], 1); + + assertGt(v.txData[0].length, 0); + + assertEq(v.tokens[0], externalToken_); + + assertEq(v.liqDstChainIds[0], FINAL_LIQ_DST_WITHDRAW[ETH][0]); + + /// @dev number of superpositions to burn in withdraws are not meant to be same as deposit amounts + + assertEq(v.amounts, actualAmountWithdrawnPerDst[0]); + } + + function _checkDstPayloadReturn() internal { + vm.selectFork(FORKS[CHAIN_0]); + + IPayloadHelper.DecodedDstPayload memory v = + IPayloadHelper(contracts[CHAIN_0][bytes32(bytes("PayloadHelper"))]).decodeCoreStateRegistryPayload(1); + + assertEq(v.txType, 0); + + /// 0 for deposit + assertEq(v.callbackType, 1); + /// 1 for return + assertEq(v.srcChainId, 1); + /// chain id of polygon is 42161 + assertEq(v.srcPayloadId, 1); + + for (uint256 i = 0; i < v.slippages.length; ++i) { + assertLe(v.amounts[i], AMOUNTS[ETH][0][i]); + assertEq(v.slippages[i], MAX_SLIPPAGE); + } + } +} diff --git a/test/unit/crosschain-data/utils/PayloadHelper.V2.singleVault.t.sol b/test/unit/crosschain-data/utils/PayloadHelper.V2.singleVault.t.sol new file mode 100644 index 000000000..b9bade51b --- /dev/null +++ b/test/unit/crosschain-data/utils/PayloadHelper.V2.singleVault.t.sol @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.23; + +/// Interfaces +import { IPayloadHelper } from "src/interfaces/IPayloadHelperV2.sol"; +import { IPaymentHelperV2 as IPaymentHelper } from "src/interfaces/IPaymentHelperV2.sol"; +import { IBaseRouter } from "src/interfaces/IBaseRouter.sol"; + +// Test Utils +import "test/utils/ProtocolActions.sol"; + +contract PayloadHelperSingleTest is ProtocolActions { + /// @dev Access SuperformRouter interface + IBaseRouter superformRouter; + + function setUp() public override { + super.setUp(); + /*////////////////////////////////////////////////////////////// + !! WARNING !! DEFINE TEST SETTINGS HERE + //////////////////////////////////////////////////////////////*/ + /// @dev singleDestinationSingleVault Deposit test case + AMBs = [2, 3]; + + CHAIN_0 = OP; + DST_CHAINS = [POLY]; + + /// @dev define vaults amounts and slippage for every destination chain and for every action + + TARGET_UNDERLYINGS[POLY][0] = [0]; + TARGET_UNDERLYINGS[POLY][1] = [0]; + + TARGET_VAULTS[POLY][0] = [0]; + TARGET_VAULTS[POLY][1] = [0]; + + TARGET_FORM_KINDS[POLY][0] = [0]; + TARGET_FORM_KINDS[POLY][1] = [0]; + + AMOUNTS[POLY][0] = [23_183]; + + MAX_SLIPPAGE = 1000; + + LIQ_BRIDGES[POLY][0] = [1]; + LIQ_BRIDGES[POLY][1] = [1]; + + RECEIVE_4626[POLY][0] = [false]; + RECEIVE_4626[POLY][1] = [false]; + + FINAL_LIQ_DST_WITHDRAW[POLY] = [OP]; + + actions.push( + TestAction({ + action: Actions.Deposit, + multiVaults: false, + user: 0, + testType: TestType.Pass, + revertError: "", + revertRole: "", + slippage: 0, // 0% <- if we are testing a pass this must be below each maxSlippage, + dstSwap: false, + externalToken: 69_420 // 0 = DAI, 1 = USDT, 2 = WETH + }) + ); + + actions.push( + TestAction({ + action: Actions.Withdraw, + multiVaults: false, + user: 0, + testType: TestType.Pass, + revertError: "", + revertRole: "", + slippage: 0, // 0% <- if we are testing a pass this must be below each maxSlippage, + dstSwap: false, + externalToken: 2 // 0 = DAI, 1 = USDT, 2 = WETH + }) + ); + } + + /*/////////////////////////////////////////////////////////////// + SCENARIO TESTS + //////////////////////////////////////////////////////////////*/ + + function test_payloadHelperSingle() public { + for (uint256 act = 0; act < actions.length; ++act) { + TestAction memory action = actions[act]; + MultiVaultSFData[] memory multiSuperformsData; + SingleVaultSFData[] memory singleSuperformsData; + MessagingAssertVars[] memory aV; + StagesLocalVars memory vars; + bool success; + if (act == 1) { + for (uint256 i = 0; i < DST_CHAINS.length; ++i) { + uint256[] memory superPositions = _getSuperpositionsForDstChain( + actions[1].user, + TARGET_UNDERLYINGS[DST_CHAINS[i]][1], + TARGET_VAULTS[DST_CHAINS[i]][1], + TARGET_FORM_KINDS[DST_CHAINS[i]][1], + DST_CHAINS[i] + ); + + AMOUNTS[DST_CHAINS[i]][1] = [superPositions[0]]; + } + } + _runMainStages(action, act, multiSuperformsData, singleSuperformsData, aV, vars, success); + } + + _checkSrcPayload(); + + _checkDstPayloadInit(); + _checkDstPayloadReturn(); + } + + function test_payloadHelperLiqSingle() public { + for (uint256 act = 0; act < actions.length; ++act) { + TestAction memory action = actions[act]; + MultiVaultSFData[] memory multiSuperformsData; + SingleVaultSFData[] memory singleSuperformsData; + MessagingAssertVars[] memory aV; + StagesLocalVars memory vars; + bool success; + if (act == 1) { + for (uint256 i = 0; i < DST_CHAINS.length; ++i) { + uint256[] memory superPositions = _getSuperpositionsForDstChain( + actions[1].user, + TARGET_UNDERLYINGS[DST_CHAINS[i]][1], + TARGET_VAULTS[DST_CHAINS[i]][1], + TARGET_FORM_KINDS[DST_CHAINS[i]][1], + DST_CHAINS[i] + ); + + AMOUNTS[DST_CHAINS[i]][1] = [superPositions[0]]; + } + } + _runMainStages(action, act, multiSuperformsData, singleSuperformsData, aV, vars, success); + } + + _checkDstPayloadLiqData( + getContract(FINAL_LIQ_DST_WITHDRAW[POLY][0], UNDERLYING_TOKENS[actions[1].externalToken]) + ); + } + + function test_decodePayloadHistory_InvalidPayloadId() public { + vm.selectFork(FORKS[ETH]); + + vm.expectRevert(Error.INVALID_PAYLOAD_ID.selector); + IPayloadHelper(contracts[ETH][bytes32(bytes("PayloadHelper"))]).decodePayloadHistory(2); + } + + function test_decodeCoreStateRegistryPayload_invalidPayload() public { + uint8[] memory ambIds_ = new uint8[](2); + ambIds_[0] = 1; + ambIds_[1] = 2; + vm.selectFork(FORKS[ETH]); + vm.prank(getContract(ETH, "LayerzeroImplementation")); + CoreStateRegistry(getContract(ETH, "CoreStateRegistry")).receivePayload( + POLY, + abi.encode(AMBMessage(DataLib.packTxInfo(1, 5, 1, 1, address(420), uint64(137)), abi.encode(ambIds_, ""))) + ); + + vm.expectRevert(Error.INVALID_PAYLOAD.selector); + PayloadHelper(getContract(ETH, "PayloadHelper")).decodeCoreStateRegistryPayload(1); + } + + function test_constructorZeroAddress() public { + vm.expectRevert(Error.ZERO_ADDRESS.selector); + new PayloadHelper(address(0)); + } + + function _checkSrcPayload() internal { + vm.selectFork(FORKS[CHAIN_0]); + + (uint8 txType, uint8 callbackType, uint8 multi, address srcSender, address receiverAddress, uint64 srcChainId) = + IPayloadHelper(contracts[CHAIN_0][bytes32(bytes("PayloadHelper"))]).decodePayloadHistory(1); + + /// @dev 0 for deposit + assertEq(txType, 0); + + /// @dev 0 for init + assertEq(callbackType, 0); + + /// @dev chain id of optimism is 10 + assertEq(srcChainId, 10); + + /// @dev 0 for not multi vault + assertEq(multi, 0); + assertEq(srcSender, users[0]); + + assertEq(receiverAddress, users[0]); + } + + function _checkDstPayloadInit() internal { + vm.selectFork(FORKS[DST_CHAINS[0]]); + + vm.expectRevert(Error.INVALID_PAYLOAD_ID.selector); + IPayloadHelper(contracts[DST_CHAINS[0]][bytes32(bytes("PayloadHelper"))]).decodeCoreStateRegistryPayload(3); + + IPayloadHelper.DecodedDstPayload memory v = + IPayloadHelper(contracts[DST_CHAINS[0]][bytes32(bytes("PayloadHelper"))]).decodeCoreStateRegistryPayload(1); + IPayloadHelper(contracts[DST_CHAINS[0]][bytes32(bytes("PayloadHelper"))]).getDstPayloadProof(1); + + bytes[] memory extraDataGenerated = new bytes[](2); + extraDataGenerated[0] = abi.encode("500000"); + extraDataGenerated[1] = abi.encode("0"); + + /// @dev 0 for deposit + assertEq(v.txType, 0); + + /// @dev 0 for init + assertEq(v.callbackType, 0); + + /// @dev chain id of optimism is 10 + assertEq(v.srcChainId, 10); + + assertEq(v.srcPayloadId, 1); + + assertEq(v.receiverAddress, users[0]); + + for (uint256 i = 0; i < v.slippages.length; ++i) { + /// @dev TODO: fix this assertion considering exchange rates + // assertLe(v.amounts[i], AMOUNTS[POLY][0][i]); + assertEq(v.slippages[i], MAX_SLIPPAGE); + } + + /// @notice: just asserting if fees are greater than 0 + /// FIXME no way to write serious tests on forked testnet at this point. should come back to this later on. + (uint256 ambFees,) = IPaymentHelper(contracts[DST_CHAINS[0]][bytes32(bytes("PaymentHelper"))]).estimateAMBFees( + AMBs, DST_CHAINS[0], abi.encode(1), extraDataGenerated + ); + assertGe(ambFees, 0); + } + + function _checkDstPayloadReturn() internal { + vm.selectFork(FORKS[CHAIN_0]); + + IPayloadHelper.DecodedDstPayload memory v = + IPayloadHelper(contracts[CHAIN_0][bytes32(bytes("PayloadHelper"))]).decodeCoreStateRegistryPayload(1); + + /// @dev 0 for deposit + assertEq(v.txType, 0); + + /// @dev 1 for return + assertEq(v.callbackType, 1); + + /// @dev chain id of polygon is 137 + assertEq(v.srcChainId, 137); + assertEq(v.srcPayloadId, 1); + + for (uint256 i = 0; i < v.slippages.length; ++i) { + assertLe(v.amounts[i], AMOUNTS[POLY][0][i]); + assertEq(v.slippages[i], MAX_SLIPPAGE); + } + } + + struct CheckDstPayloadLiqDataInternalVars { + uint8[] bridgeIds; + bytes[] txDatas; + address[] tokens; + uint64[] liqDstChainIds; + uint256[] amounts; + uint256[] nativeAmounts; + } + + function _checkDstPayloadLiqData(address externalToken_) internal { + vm.selectFork(FORKS[DST_CHAINS[0]]); + CheckDstPayloadLiqDataInternalVars memory v; + + (v.txDatas, v.tokens,, v.bridgeIds, v.liqDstChainIds, v.amounts, v.nativeAmounts) = IPayloadHelper( + contracts[DST_CHAINS[0]][bytes32(bytes("PayloadHelper"))] + ).decodeCoreStateRegistryPayloadLiqData(2); + + assertEq(v.bridgeIds[0], 1); + + assertGt(v.txDatas[0].length, 0); + + assertEq(v.tokens[0], externalToken_); + + assertEq(v.liqDstChainIds[0], FINAL_LIQ_DST_WITHDRAW[POLY][0]); + + /// @dev number of superpositions to burn in withdraws are not meant to be same as deposit amounts + + assertEq(v.amounts, actualAmountWithdrawnPerDst[0]); + } +} diff --git a/test/unit/crosschain-data/utils/PayloadHelper.multiVault.t.sol b/test/unit/crosschain-data/utils/PayloadHelper.multiVault.t.sol index bed2734c6..ee40e7d27 100644 --- a/test/unit/crosschain-data/utils/PayloadHelper.multiVault.t.sol +++ b/test/unit/crosschain-data/utils/PayloadHelper.multiVault.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.23; /// Interfaces -import { IPayloadHelper } from "src/interfaces/IPayloadHelperV2.sol"; +import { IPayloadHelper } from "src/interfaces/IPayloadHelper.sol"; import { IPaymentHelperV2 as IPaymentHelper } from "src/interfaces/IPaymentHelperV2.sol"; import { DataLib } from "src/libraries/DataLib.sol"; @@ -209,7 +209,6 @@ contract PayloadHelperMultiTest is ProtocolActions { vm.selectFork(FORKS[DST_CHAINS[0]]); IPayloadHelper.DecodedDstPayload memory v = IPayloadHelper(contracts[DST_CHAINS[0]][bytes32(bytes("PayloadHelper"))]).decodeCoreStateRegistryPayload(1); - bytes[] memory extraDataGenerated = new bytes[](2); extraDataGenerated[0] = abi.encode("500000"); extraDataGenerated[1] = abi.encode("0"); diff --git a/test/unit/crosschain-data/utils/PayloadHelper.singleVault.t.sol b/test/unit/crosschain-data/utils/PayloadHelper.singleVault.t.sol index b9bade51b..2ed5e8cb7 100644 --- a/test/unit/crosschain-data/utils/PayloadHelper.singleVault.t.sol +++ b/test/unit/crosschain-data/utils/PayloadHelper.singleVault.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.23; /// Interfaces -import { IPayloadHelper } from "src/interfaces/IPayloadHelperV2.sol"; +import { IPayloadHelper } from "src/interfaces/IPayloadHelper.sol"; import { IPaymentHelperV2 as IPaymentHelper } from "src/interfaces/IPaymentHelperV2.sol"; import { IBaseRouter } from "src/interfaces/IBaseRouter.sol";