From 1350cf9015276c977bb8fe6689220f4bf6523766 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Fri, 10 Jan 2025 19:06:00 +0300 Subject: [PATCH] Integrate WithdrawablePeriphery [ERC20Proxy v1.1.0,Executor v2.1.0,LiFiDEXAggregator v1.6.0,Receiver v2.1.0,ReceiverAcrossV3 v1.1.0,ReceiverStargateV2 v1.1.0,RelayerCelerIM v2.1.1,TokenWrapper v1.1.0] (#909) * Integrate WithdrawablePeriphery * update TokenWrapper deploy script * update TokenWrapper deploy script * remove redundant code * add audit report and update log * remove unneeded payable * update log * fix * fix version --- audit/auditLog.json | 41 +++++++++++++++-- ...025.01.09_WithdrawablePeripheryUpdates.pdf | Bin 0 -> 31492 bytes docs/Receiver.md | 14 ------ docs/ReceiverAcrossV3.md | 14 ------ docs/ReceiverStargateV2.md | 14 ------ script/deploy/facets/DeployTokenWrapper.s.sol | 16 ++++++- script/deploy/zksync/DeployTokenWrapper.s.sol | 24 +++++++--- src/Periphery/ERC20Proxy.sol | 13 ++---- src/Periphery/Executor.sol | 16 +++++-- src/Periphery/LiFiDEXAggregator.sol | 14 +++--- src/Periphery/Receiver.sol | 25 ++--------- src/Periphery/ReceiverAcrossV3.sol | 28 +++--------- src/Periphery/ReceiverStargateV2.sol | 26 ++--------- src/Periphery/RelayerCelerIM.sol | 42 ++++-------------- src/Periphery/TokenWrapper.sol | 10 +++-- test/solidity/Facets/CelerIMFacet.t.sol | 2 +- test/solidity/Periphery/Executor.t.sol | 2 +- test/solidity/Periphery/Receiver.t.sol | 10 ++--- .../solidity/Periphery/ReceiverAcrossV3.t.sol | 16 +++---- .../Periphery/ReceiverStargateV2.t.sol | 20 ++++----- test/solidity/Periphery/RelayerCelerIM.t.sol | 2 +- test/solidity/Periphery/TokenWrapper.t.sol | 18 +++++++- 22 files changed, 169 insertions(+), 198 deletions(-) create mode 100644 audit/reports/2025.01.09_WithdrawablePeripheryUpdates.pdf diff --git a/audit/auditLog.json b/audit/auditLog.json index 27a6a5dec..ccade496c 100644 --- a/audit/auditLog.json +++ b/audit/auditLog.json @@ -91,6 +91,13 @@ "auditReportPath": "./audit/reports/2025.01.09_ThorSwapFacet(v1.2.1).pdf", "auditCommitHash": "5005bf62858a9cb2a7628976ef870e91bb732a75" }, + "audit20250109_2": { + "auditCompletedOn": "09.01.2025", + "auditedBy": "Sujith Somraaj (individual security researcher)", + "auditorGitHandle": "sujithsomraaj", + "auditReportPath": "./audit/reports/2025.01.09_WithdrawablePeripheryUpdates.pdf", + "auditCommitHash": "ec76b33cedd69338e569c7994b99b03cb7e6d502" + }, "audit20250109_3": { "auditCompletedOn": "09.01.2025", "auditedBy": "Sujith Somraaj (individual security researcher)", @@ -129,6 +136,16 @@ "audit20241105" ] }, + "ERC20Proxy": { + "1.1.0": [ + "audit20250109_2" + ] + }, + "Executor": { + "2.1.0": [ + "audit20250109_2" + ] + }, "FeeCollector": { "1.0.1": [ "audit20250109_3" @@ -167,6 +184,9 @@ "1.5.0": [ "audit20241203" ], + "1.6.0": [ + "audit20250109_2" + ], "1.5.1": [ "audit20250109_3" ] @@ -187,6 +207,9 @@ "Receiver": { "2.0.3": [ "audit20250109_3" + ], + "2.1.0": [ + "audit20250109_2" ] }, "ReceiverAcrossV3": { @@ -196,17 +219,26 @@ "1.0.1": [ "audit20241206" ], - "1.0.3": [ - "audit20250109_3" + "1.1.0": [ + "audit20250109_2" ] }, "ReceiverStargateV2": { "1.0.1": [ "audit20250109_3" + ], + "1.1.0": [ + "audit20250109_2" ] }, "RelayerCelerIM": { - "2.0.1": [ + "1.0.3": [ + "audit20250109_3" + ], + "2.1.0": [ + "audit20250109_2" + ], + "2.1.1": [ "audit20250109_3" ] }, @@ -228,6 +260,9 @@ "TokenWrapper": { "1.0.1": [ "audit20250109_3" + ], + "1.1.0": [ + "audit20250109_2" ] }, "WithdrawablePeriphery": { diff --git a/audit/reports/2025.01.09_WithdrawablePeripheryUpdates.pdf b/audit/reports/2025.01.09_WithdrawablePeripheryUpdates.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6c55534fa75e246247808b1061f78801428c0ef9 GIT binary patch literal 31492 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 A@&Et; literal 0 HcmV?d00001 diff --git a/docs/Receiver.md b/docs/Receiver.md index 46d0b7f8f..420ee1c82 100644 --- a/docs/Receiver.md +++ b/docs/Receiver.md @@ -68,17 +68,3 @@ function swapAndCompleteBridgeTokens( address payable receiver ) ``` - -This method is used to send remaining tokens to receiver. - -```solidity -/// @notice Send remaining token to receiver -/// @param assetId token received from the other chain -/// @param receiver address that will receive tokens in the end -/// @param amount amount of token -function pullToken( - address assetId, - address payable receiver, - uint256 amount -) -``` diff --git a/docs/ReceiverAcrossV3.md b/docs/ReceiverAcrossV3.md index 3ee8cd5cc..51d19a188 100644 --- a/docs/ReceiverAcrossV3.md +++ b/docs/ReceiverAcrossV3.md @@ -23,17 +23,3 @@ The contract has one method which will (and can only) be called through the Acro bytes memory message ) ``` - -Furthermore there is one (admin) method that allows withdrawals of stuck tokens by LI.FI administrators: - -```solidity -/// @notice Send remaining token to receiver -/// @param assetId token received from the other chain -/// @param receiver address that will receive tokens in the end -/// @param amount amount of token -function pullToken( - address assetId, - address payable receiver, - uint256 amount -) -``` diff --git a/docs/ReceiverStargateV2.md b/docs/ReceiverStargateV2.md index c33c96c67..e656c0dab 100644 --- a/docs/ReceiverStargateV2.md +++ b/docs/ReceiverStargateV2.md @@ -24,17 +24,3 @@ The contract has one method which will be called through the LayerZero endpoint: bytes calldata ) ``` - -Furthermore there is one (admin) method that allows withdrawals of stuck tokens by LI.FI administrators: - -```solidity -/// @notice Send remaining token to receiver -/// @param assetId token received from the other chain -/// @param receiver address that will receive tokens in the end -/// @param amount amount of token -function pullToken( - address assetId, - address payable receiver, - uint256 amount -) -``` diff --git a/script/deploy/facets/DeployTokenWrapper.s.sol b/script/deploy/facets/DeployTokenWrapper.s.sol index ff964efc5..72d0806de 100644 --- a/script/deploy/facets/DeployTokenWrapper.s.sol +++ b/script/deploy/facets/DeployTokenWrapper.s.sol @@ -26,6 +26,12 @@ contract DeployScript is DeployScriptBase { "/config/networks.json" ); + // get path of global config file + string memory globalConfigPath = string.concat( + root, + "/config/global.json" + ); + // read file into json variable string memory tokenWrapperConfigJSON = vm.readFile(tokenWrapperConfig); @@ -34,6 +40,14 @@ contract DeployScript is DeployScriptBase { string.concat(".", network, ".wrappedNativeAddress") ); - return abi.encode(wrappedNativeAddress); + // read file into json variable + string memory globalConfigJson = vm.readFile(globalConfigPath); + + // extract refundWallet address + address refundWalletAddress = globalConfigJson.readAddress( + ".refundWallet" + ); + + return abi.encode(wrappedNativeAddress, refundWalletAddress); } } diff --git a/script/deploy/zksync/DeployTokenWrapper.s.sol b/script/deploy/zksync/DeployTokenWrapper.s.sol index 0ce17fcd7..72d0806de 100644 --- a/script/deploy/zksync/DeployTokenWrapper.s.sol +++ b/script/deploy/zksync/DeployTokenWrapper.s.sol @@ -20,20 +20,34 @@ contract DeployScript is DeployScriptBase { } function getConstructorArgs() internal override returns (bytes memory) { - // get path of global network config file - string memory networkConfig = string.concat( + // get path of global config file + string memory tokenWrapperConfig = string.concat( root, "/config/networks.json" ); + // get path of global config file + string memory globalConfigPath = string.concat( + root, + "/config/global.json" + ); + // read file into json variable - string memory networkConfigJSON = vm.readFile(networkConfig); + string memory tokenWrapperConfigJSON = vm.readFile(tokenWrapperConfig); // extract wrapped token address for the given network - address wrappedNativeAddress = networkConfigJSON.readAddress( + address wrappedNativeAddress = tokenWrapperConfigJSON.readAddress( string.concat(".", network, ".wrappedNativeAddress") ); - return abi.encode(wrappedNativeAddress); + // read file into json variable + string memory globalConfigJson = vm.readFile(globalConfigPath); + + // extract refundWallet address + address refundWalletAddress = globalConfigJson.readAddress( + ".refundWallet" + ); + + return abi.encode(wrappedNativeAddress, refundWalletAddress); } } diff --git a/src/Periphery/ERC20Proxy.sol b/src/Periphery/ERC20Proxy.sol index cdbe64abc..dd0318f47 100644 --- a/src/Periphery/ERC20Proxy.sol +++ b/src/Periphery/ERC20Proxy.sol @@ -1,27 +1,22 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { LibAsset } from "../Libraries/LibAsset.sol"; +import { WithdrawablePeriphery } from "../Helpers/WithdrawablePeriphery.sol"; /// @title ERC20 Proxy /// @author LI.FI (https://li.fi) /// @notice Proxy contract for safely transferring ERC20 tokens for swaps/executions -/// @custom:version 1.0.0 -contract ERC20Proxy is Ownable { +/// @custom:version 1.1.0 +contract ERC20Proxy is WithdrawablePeriphery { /// Storage /// mapping(address => bool) public authorizedCallers; - /// Errors /// - error UnAuthorized(); - /// Events /// event AuthorizationChanged(address indexed caller, bool authorized); /// Constructor - constructor(address _owner) { - transferOwnership(_owner); - } + constructor(address _owner) WithdrawablePeriphery(_owner) {} /// @notice Sets whether or not a specified caller is authorized to call this contract /// @param caller the caller to change authorization for diff --git a/src/Periphery/Executor.sol b/src/Periphery/Executor.sol index 8832b2606..4c7c11bca 100644 --- a/src/Periphery/Executor.sol +++ b/src/Periphery/Executor.sol @@ -7,6 +7,7 @@ import { LibAsset } from "../Libraries/LibAsset.sol"; import { UnAuthorized } from "lifi/Errors/GenericErrors.sol"; import { ILiFi } from "../Interfaces/ILiFi.sol"; import { IERC20Proxy } from "../Interfaces/IERC20Proxy.sol"; +import { WithdrawablePeriphery } from "../Helpers/WithdrawablePeriphery.sol"; import { ERC1155Holder } from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; import { ERC721Holder } from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -14,8 +15,14 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; /// @title Executor /// @author LI.FI (https://li.fi) /// @notice Arbitrary execution contract used for cross-chain swaps and message passing -/// @custom:version 2.0.0 -contract Executor is ILiFi, ReentrancyGuard, ERC1155Holder, ERC721Holder { +/// @custom:version 2.1.0 +contract Executor is + ILiFi, + ReentrancyGuard, + ERC1155Holder, + ERC721Holder, + WithdrawablePeriphery +{ /// Storage /// /// @notice The address of the ERC20Proxy contract @@ -64,7 +71,10 @@ contract Executor is ILiFi, ReentrancyGuard, ERC1155Holder, ERC721Holder { /// Constructor /// @notice Initialize local variables for the Executor /// @param _erc20Proxy The address of the ERC20Proxy contract - constructor(address _erc20Proxy) { + constructor( + address _erc20Proxy, + address _owner + ) WithdrawablePeriphery(_owner) { erc20Proxy = IERC20Proxy(_erc20Proxy); emit ERC20ProxySet(_erc20Proxy); } diff --git a/src/Periphery/LiFiDEXAggregator.sol b/src/Periphery/LiFiDEXAggregator.sol index 0bd818fe6..c8e926c4e 100644 --- a/src/Periphery/LiFiDEXAggregator.sol +++ b/src/Periphery/LiFiDEXAggregator.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { SafeERC20, IERC20, IERC20Permit } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { WithdrawablePeriphery } from "../Helpers/WithdrawablePeriphery.sol"; import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; address constant NATIVE_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; @@ -23,8 +23,8 @@ uint160 constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970 /// @title LiFi DEX Aggregator /// @author Ilya Lyalin (contract copied from: https://github.com/sushiswap/sushiswap/blob/c8c80dec821003eb72eb77c7e0446ddde8ca9e1e/protocols/route-processor/contracts/RouteProcessor4.sol) /// @notice Processes calldata to swap using various DEXs -/// @custom:version 1.5.1 -contract LiFiDEXAggregator is Ownable { +/// @custom:version 1.6.0 +contract LiFiDEXAggregator is WithdrawablePeriphery { using SafeERC20 for IERC20; using Approve for IERC20; using SafeERC20 for IERC20Permit; @@ -58,13 +58,17 @@ contract LiFiDEXAggregator is Ownable { modifier onlyOwnerOrPriviledgedUser() { require( - msg.sender == owner() || priviledgedUsers[msg.sender], + msg.sender == owner || priviledgedUsers[msg.sender], "RP: caller is not the owner or a privileged user" ); _; } - constructor(address _bentoBox, address[] memory priviledgedUserList) { + constructor( + address _bentoBox, + address[] memory priviledgedUserList, + address _owner + ) WithdrawablePeriphery(_owner) { bentoBox = IBentoBoxMinimal(_bentoBox); lastCalledPool = IMPOSSIBLE_POOL_ADDRESS; diff --git a/src/Periphery/Receiver.sol b/src/Periphery/Receiver.sol index 86a269c7e..f75f1bb04 100644 --- a/src/Periphery/Receiver.sol +++ b/src/Periphery/Receiver.sol @@ -8,14 +8,14 @@ import { LibSwap } from "../Libraries/LibSwap.sol"; import { LibAsset } from "../Libraries/LibAsset.sol"; import { ILiFi } from "../Interfaces/ILiFi.sol"; import { IExecutor } from "../Interfaces/IExecutor.sol"; -import { TransferrableOwnership } from "../Helpers/TransferrableOwnership.sol"; +import { WithdrawablePeriphery } from "../Helpers/WithdrawablePeriphery.sol"; import { ExternalCallFailed, UnAuthorized } from "../Errors/GenericErrors.sol"; /// @title Receiver /// @author LI.FI (https://li.fi) /// @notice Arbitrary execution contract used for cross-chain swaps and message passing -/// @custom:version 2.0.3 -contract Receiver is ILiFi, ReentrancyGuard, TransferrableOwnership { +/// @custom:version 2.1.0 +contract Receiver is ILiFi, ReentrancyGuard, WithdrawablePeriphery { using SafeERC20 for IERC20; /// Storage /// @@ -51,8 +51,7 @@ contract Receiver is ILiFi, ReentrancyGuard, TransferrableOwnership { address _amarokRouter, address _executor, uint256 _recoverGas - ) TransferrableOwnership(_owner) { - owner = _owner; + ) WithdrawablePeriphery(_owner) { sgRouter = _sgRouter; amarokRouter = _amarokRouter; executor = IExecutor(_executor); @@ -168,22 +167,6 @@ contract Receiver is ILiFi, ReentrancyGuard, TransferrableOwnership { } } - /// @notice Send remaining token to receiver - /// @param assetId token received from the other chain - /// @param receiver address that will receive tokens in the end - /// @param amount amount of token - function pullToken( - address assetId, - address payable receiver, - uint256 amount - ) external onlyOwner { - if (LibAsset.isNativeAsset(assetId)) { - SafeTransferLib.safeTransferETH(receiver, amount); - } else { - IERC20(assetId).safeTransfer(receiver, amount); - } - } - /// Private Methods /// /// @notice Performs a swap before completing a cross-chain transaction diff --git a/src/Periphery/ReceiverAcrossV3.sol b/src/Periphery/ReceiverAcrossV3.sol index 3c3f5b7db..947bbe343 100644 --- a/src/Periphery/ReceiverAcrossV3.sol +++ b/src/Periphery/ReceiverAcrossV3.sol @@ -5,15 +5,15 @@ import { LibSwap } from "../Libraries/LibSwap.sol"; import { LibAsset } from "../Libraries/LibAsset.sol"; import { ILiFi } from "../Interfaces/ILiFi.sol"; import { IExecutor } from "../Interfaces/IExecutor.sol"; -import { TransferrableOwnership } from "../Helpers/TransferrableOwnership.sol"; +import { WithdrawablePeriphery } from "../Helpers/WithdrawablePeriphery.sol"; import { ExternalCallFailed, UnAuthorized } from "../Errors/GenericErrors.sol"; import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; /// @title ReceiverAcrossV3 /// @author LI.FI (https://li.fi) /// @notice Arbitrary execution contract used for cross-chain swaps and message passing via AcrossV3 -/// @custom:version 1.0.3 -contract ReceiverAcrossV3 is ILiFi, TransferrableOwnership { +/// @custom:version 1.1.0 +contract ReceiverAcrossV3 is ILiFi, WithdrawablePeriphery { using SafeTransferLib for address; /// Storage /// @@ -33,8 +33,7 @@ contract ReceiverAcrossV3 is ILiFi, TransferrableOwnership { address _owner, address _executor, address _spokepool - ) TransferrableOwnership(_owner) { - owner = _owner; + ) WithdrawablePeriphery(_owner) { executor = IExecutor(_executor); spokepool = _spokepool; } @@ -71,27 +70,10 @@ contract ReceiverAcrossV3 is ILiFi, TransferrableOwnership { ); } - /// @notice Send remaining token to receiver - /// @param assetId address of the token to be withdrawn (not to be confused with StargateV2's assetIds which are uint16 values) - /// @param receiver address that will receive tokens in the end - /// @param amount amount of token - function pullToken( - address assetId, - address payable receiver, - uint256 amount - ) external onlyOwner { - if (LibAsset.isNativeAsset(assetId)) { - // solhint-disable-next-line avoid-low-level-calls - SafeTransferLib.safeTransferETH(receiver, amount); - } else { - assetId.safeTransfer(receiver, amount); - } - } - /// Private Methods /// /// @notice Performs a swap before completing a cross-chain transaction - // @notice Since Across will always send wrappedNative to contract, we do not need a native handling here + /// @notice Since Across will always send wrappedNative to contract, we do not need a native handling here /// @param _transactionId the transaction id associated with the operation /// @param _swapData array of data needed for swaps /// @param assetId address of the token received from the source chain (not to be confused with StargateV2's assetIds which are uint16 values) diff --git a/src/Periphery/ReceiverStargateV2.sol b/src/Periphery/ReceiverStargateV2.sol index c75d9d9bd..2f89611a6 100644 --- a/src/Periphery/ReceiverStargateV2.sol +++ b/src/Periphery/ReceiverStargateV2.sol @@ -8,7 +8,7 @@ import { LibAsset } from "../Libraries/LibAsset.sol"; import { OFTComposeMsgCodec } from "../Libraries/OFTComposeMsgCodec.sol"; import { ILiFi } from "../Interfaces/ILiFi.sol"; import { IExecutor } from "../Interfaces/IExecutor.sol"; -import { TransferrableOwnership } from "../Helpers/TransferrableOwnership.sol"; +import { WithdrawablePeriphery } from "../Helpers/WithdrawablePeriphery.sol"; import { ExternalCallFailed, UnAuthorized } from "../Errors/GenericErrors.sol"; import { ITokenMessaging } from "../Interfaces/IStargate.sol"; @@ -35,10 +35,10 @@ interface ILayerZeroComposer { /// @title ReceiverStargateV2 /// @author LI.FI (https://li.fi) /// @notice Arbitrary execution contract used for cross-chain swaps and message passing via Stargate V2 -/// @custom:version 1.0.1 +/// @custom:version 1.1.0 contract ReceiverStargateV2 is ILiFi, - TransferrableOwnership, + WithdrawablePeriphery, ILayerZeroComposer { using SafeERC20 for IERC20; @@ -64,8 +64,7 @@ contract ReceiverStargateV2 is address _tokenMessaging, address _endpointV2, uint256 _recoverGas - ) TransferrableOwnership(_owner) { - owner = _owner; + ) WithdrawablePeriphery(_owner) { executor = IExecutor(_executor); tokenMessaging = ITokenMessaging(_tokenMessaging); endpointV2 = _endpointV2; @@ -115,23 +114,6 @@ contract ReceiverStargateV2 is ); } - /// @notice Send remaining token to receiver - /// @param assetId address of the token to be withdrawn (not to be confused with StargateV2's assetIds which are uint16 values) - /// @param receiver address that will receive tokens in the end - /// @param amount amount of token - function pullToken( - address assetId, - address payable receiver, - uint256 amount - ) external onlyOwner { - if (LibAsset.isNativeAsset(assetId)) { - // solhint-disable-next-line avoid-low-level-calls - SafeTransferLib.safeTransferETH(receiver, amount); - } else { - IERC20(assetId).safeTransfer(receiver, amount); - } - } - /// Private Methods /// /// @notice Performs a swap before completing a cross-chain transaction diff --git a/src/Periphery/RelayerCelerIM.sol b/src/Periphery/RelayerCelerIM.sol index 820051d55..e7dfbc05e 100644 --- a/src/Periphery/RelayerCelerIM.sol +++ b/src/Periphery/RelayerCelerIM.sol @@ -10,7 +10,7 @@ import { LibUtil } from "../Libraries/LibUtil.sol"; import { ILiFi } from "../Interfaces/ILiFi.sol"; import { PeripheryRegistryFacet } from "../Facets/PeripheryRegistryFacet.sol"; import { IExecutor } from "../Interfaces/IExecutor.sol"; -import { TransferrableOwnership } from "../Helpers/TransferrableOwnership.sol"; +import { WithdrawablePeriphery } from "../Helpers/WithdrawablePeriphery.sol"; import { IMessageReceiverApp } from "celer-network/contracts/message/interfaces/IMessageReceiverApp.sol"; import { CelerIM } from "lifi/Helpers/CelerIMFacetBase.sol"; import { MessageSenderLib, MsgDataTypes, IMessageBus, IOriginalTokenVault, IPeggedTokenBridge, IOriginalTokenVaultV2, IPeggedTokenBridgeV2 } from "celer-network/contracts/message/libraries/MessageSenderLib.sol"; @@ -19,8 +19,8 @@ import { IBridge as ICBridge } from "celer-network/contracts/interfaces/IBridge. /// @title RelayerCelerIM /// @author LI.FI (https://li.fi) /// @notice Relayer contract for CelerIM that forwards calls and handles refunds on src side and acts receiver on dest -/// @custom:version 2.0.1 -contract RelayerCelerIM is ILiFi, TransferrableOwnership { +/// @custom:version 2.1.1 +contract RelayerCelerIM is ILiFi, WithdrawablePeriphery { using SafeERC20 for IERC20; /// Storage /// @@ -28,14 +28,6 @@ contract RelayerCelerIM is ILiFi, TransferrableOwnership { IMessageBus public cBridgeMessageBus; address public diamondAddress; - /// Events /// - - event LogWithdraw( - address indexed _assetAddress, - address indexed _to, - uint256 amount - ); - /// Modifiers /// modifier onlyCBridgeMessageBus() { @@ -53,8 +45,7 @@ contract RelayerCelerIM is ILiFi, TransferrableOwnership { address _cBridgeMessageBusAddress, address _owner, address _diamondAddress - ) TransferrableOwnership(_owner) { - owner = _owner; + ) WithdrawablePeriphery(_owner) { cBridgeMessageBus = IMessageBus(_cBridgeMessageBusAddress); diamondAddress = _diamondAddress; } @@ -402,23 +393,6 @@ contract RelayerCelerIM is ILiFi, TransferrableOwnership { } } - /// @notice Sends remaining token to given receiver address (for refund cases) - /// @param assetId Address of the token to be withdrawn - /// @param receiver Address that will receive tokens - /// @param amount Amount of tokens to be withdrawn - function withdraw( - address assetId, - address payable receiver, - uint256 amount - ) external onlyOwner { - if (LibAsset.isNativeAsset(assetId)) { - SafeTransferLib.safeTransferETH(receiver, amount); - } else { - IERC20(assetId).safeTransfer(receiver, amount); - } - emit LogWithdraw(assetId, receiver, amount); - } - /// @notice Triggers a cBridge refund with calldata produced by cBridge API /// @param _callTo The address to execute the calldata on /// @param _callData The data to execute @@ -451,9 +425,11 @@ contract RelayerCelerIM is ILiFi, TransferrableOwnership { // forward funds to _to address and emit event, if cBridge refund successful if (success) { - address sendTo = (LibUtil.isZeroAddress(_to)) ? msg.sender : _to; - LibAsset.transferAsset(_assetAddress, payable(sendTo), _amount); - emit LogWithdraw(_assetAddress, sendTo, _amount); + address payable sendTo = payable( + (LibUtil.isZeroAddress(_to)) ? msg.sender : _to + ); + LibAsset.transferAsset(_assetAddress, sendTo, _amount); + emit TokensWithdrawn(_assetAddress, sendTo, _amount); } else { revert WithdrawFailed(); } diff --git a/src/Periphery/TokenWrapper.sol b/src/Periphery/TokenWrapper.sol index ac72718dd..ffdd15c51 100644 --- a/src/Periphery/TokenWrapper.sol +++ b/src/Periphery/TokenWrapper.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.17; import { LibAsset } from "../Libraries/LibAsset.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { WithdrawablePeriphery } from "../Helpers/WithdrawablePeriphery.sol"; import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; /// External wrapper interface @@ -15,8 +16,8 @@ interface IWrapper { /// @title TokenWrapper /// @author LI.FI (https://li.fi) /// @notice Provides functionality for wrapping and unwrapping tokens -/// @custom:version 1.0.1 -contract TokenWrapper { +/// @custom:version 1.1.0 +contract TokenWrapper is WithdrawablePeriphery { uint256 private constant MAX_INT = 2 ** 256 - 1; address public wrappedToken; @@ -25,7 +26,10 @@ contract TokenWrapper { /// Constructor /// // solhint-disable-next-line no-empty-blocks - constructor(address _wrappedToken) { + constructor( + address _wrappedToken, + address _owner + ) WithdrawablePeriphery(_owner) { wrappedToken = _wrappedToken; IERC20(wrappedToken).approve(address(this), MAX_INT); } diff --git a/test/solidity/Facets/CelerIMFacet.t.sol b/test/solidity/Facets/CelerIMFacet.t.sol index e164970c4..85ecf72b1 100644 --- a/test/solidity/Facets/CelerIMFacet.t.sol +++ b/test/solidity/Facets/CelerIMFacet.t.sol @@ -101,7 +101,7 @@ contract CelerIMFacetTest is TestBaseFacet { // deploy periphery erc20Proxy = new ERC20Proxy(address(this)); - executor = new Executor(address(erc20Proxy)); + executor = new Executor(address(erc20Proxy), address(this)); celerIMFacet = new TestCelerIMFacet( IMessageBus(CBRIDGE_MESSAGEBUS_ETH), diff --git a/test/solidity/Periphery/Executor.t.sol b/test/solidity/Periphery/Executor.t.sol index 7dc1e51f1..a1c8652c6 100644 --- a/test/solidity/Periphery/Executor.t.sol +++ b/test/solidity/Periphery/Executor.t.sol @@ -70,7 +70,7 @@ contract ExecutorTest is DSTest { function setUp() public { gw = new MockGateway(); erc20Proxy = new ERC20Proxy(address(this)); - executor = new Executor(address(erc20Proxy)); + executor = new Executor(address(erc20Proxy), address(this)); vm.makePersistent(address(executor)); erc20Proxy.setAuthorizedCaller(address(executor), true); amm = new TestAMM(); diff --git a/test/solidity/Periphery/Receiver.t.sol b/test/solidity/Periphery/Receiver.t.sol index a77d24441..19b343d40 100644 --- a/test/solidity/Periphery/Receiver.t.sol +++ b/test/solidity/Periphery/Receiver.t.sol @@ -44,7 +44,7 @@ contract ReceiverTest is TestBase { ); erc20Proxy = new ERC20Proxy(address(this)); - executor = new Executor(address(erc20Proxy)); + executor = new Executor(address(erc20Proxy), address(this)); receiver = new Receiver( address(this), stargateRouter, @@ -61,7 +61,7 @@ contract ReceiverTest is TestBase { transferId = keccak256("123"); } - function test_revert_OwnerCanPullToken() public { + function test_revert_OwnerCanWithdrawToken() public { // send token to receiver vm.startPrank(USER_SENDER); dai.transfer(address(receiver), 1000); @@ -70,15 +70,15 @@ contract ReceiverTest is TestBase { // pull token vm.startPrank(USER_DIAMOND_OWNER); - receiver.pullToken(ADDRESS_DAI, payable(USER_RECEIVER), 1000); + receiver.withdrawToken(ADDRESS_DAI, payable(USER_RECEIVER), 1000); assertEq(1000, dai.balanceOf(USER_RECEIVER)); } - function test_revert_PullTokenNonOwner() public { + function test_revert_WithdrawTokenNonOwner() public { vm.startPrank(USER_SENDER); vm.expectRevert(UnAuthorized.selector); - receiver.pullToken(ADDRESS_DAI, payable(USER_RECEIVER), 1000); + receiver.withdrawToken(ADDRESS_DAI, payable(USER_RECEIVER), 1000); } // AMAROK-RELATED TESTS diff --git a/test/solidity/Periphery/ReceiverAcrossV3.t.sol b/test/solidity/Periphery/ReceiverAcrossV3.t.sol index 97d67f284..32f29428c 100644 --- a/test/solidity/Periphery/ReceiverAcrossV3.t.sol +++ b/test/solidity/Periphery/ReceiverAcrossV3.t.sol @@ -30,7 +30,7 @@ contract ReceiverAcrossV3Test is TestBase { initTestBase(); erc20Proxy = new ERC20Proxy(address(this)); - executor = new Executor(address(erc20Proxy)); + executor = new Executor(address(erc20Proxy), address(this)); receiver = new ReceiverAcrossV3( address(this), address(executor), @@ -61,7 +61,7 @@ contract ReceiverAcrossV3Test is TestBase { // pull token vm.startPrank(USER_DIAMOND_OWNER); - receiver.pullToken(ADDRESS_DAI, payable(USER_RECEIVER), 1000); + receiver.withdrawToken(ADDRESS_DAI, payable(USER_RECEIVER), 1000); assertEq(dai.balanceOf(USER_RECEIVER), initialBalance + 1000); } @@ -75,12 +75,12 @@ contract ReceiverAcrossV3Test is TestBase { // pull token vm.startPrank(USER_DIAMOND_OWNER); - receiver.pullToken(address(0), payable(USER_RECEIVER), 1 ether); + receiver.withdrawToken(address(0), payable(USER_RECEIVER), 1 ether); assertEq(USER_RECEIVER.balance, initialBalance + 1 ether); } - function test_PullTokenWillRevertIfExternalCallFails() public { + function test_WithdrawTokenWillRevertIfExternalCallFails() public { vm.deal(address(receiver), 1 ether); // deploy contract that cannot receive ETH @@ -88,19 +88,19 @@ contract ReceiverAcrossV3Test is TestBase { vm.startPrank(USER_DIAMOND_OWNER); - vm.expectRevert(abi.encodeWithSignature("ETHTransferFailed()")); + vm.expectRevert(abi.encodeWithSignature("ExternalCallFailed()")); - receiver.pullToken( + receiver.withdrawToken( address(0), payable(address(nonETHReceiver)), 1 ether ); } - function test_revert_PullTokenNonOwner() public { + function test_revert_WithdrawTokenNonOwner() public { vm.startPrank(USER_SENDER); vm.expectRevert(UnAuthorized.selector); - receiver.pullToken(ADDRESS_DAI, payable(USER_RECEIVER), 1000); + receiver.withdrawToken(ADDRESS_DAI, payable(USER_RECEIVER), 1000); } function test_revert_OnlySpokepoolCanCallHandleV3AcrossMessage() public { diff --git a/test/solidity/Periphery/ReceiverStargateV2.t.sol b/test/solidity/Periphery/ReceiverStargateV2.t.sol index 485bd5ace..a6a054caa 100644 --- a/test/solidity/Periphery/ReceiverStargateV2.t.sol +++ b/test/solidity/Periphery/ReceiverStargateV2.t.sol @@ -44,7 +44,7 @@ contract ReceiverStargateV2Test is TestBase { initTestBase(); erc20Proxy = new ERC20Proxy(address(this)); - executor = new Executor(address(erc20Proxy)); + executor = new Executor(address(erc20Proxy), address(this)); receiver = new ReceiverStargateV2( address(this), address(executor), @@ -59,7 +59,7 @@ contract ReceiverStargateV2Test is TestBase { transferId = keccak256("123"); } - function test_OwnerCanPullERC20Token() public { + function test_OwnerCanWithdrawERC20Token() public { // fund receiver with ERC20 tokens deal(ADDRESS_DAI, address(receiver), 1000); @@ -68,12 +68,12 @@ contract ReceiverStargateV2Test is TestBase { // pull token vm.startPrank(USER_DIAMOND_OWNER); - receiver.pullToken(ADDRESS_DAI, payable(USER_RECEIVER), 1000); + receiver.withdrawToken(ADDRESS_DAI, payable(USER_RECEIVER), 1000); assertEq(dai.balanceOf(USER_RECEIVER), initialBalance + 1000); } - function test_OwnerCanPullNativeToken() public { + function test_OwnerCanWithdrawNativeToken() public { // fund receiver with native tokens vm.deal(address(receiver), 1 ether); @@ -82,12 +82,12 @@ contract ReceiverStargateV2Test is TestBase { // pull token vm.startPrank(USER_DIAMOND_OWNER); - receiver.pullToken(address(0), payable(USER_RECEIVER), 1 ether); + receiver.withdrawToken(address(0), payable(USER_RECEIVER), 1 ether); assertEq(USER_RECEIVER.balance, initialBalance + 1 ether); } - function test_PullTokenWillRevertIfExternalCallFails() public { + function test_WithdrawTokenWillRevertIfExternalCallFails() public { vm.deal(address(receiver), 1 ether); // deploy contract that cannot receive ETH @@ -95,19 +95,19 @@ contract ReceiverStargateV2Test is TestBase { vm.startPrank(USER_DIAMOND_OWNER); - vm.expectRevert(abi.encodeWithSignature("ETHTransferFailed()")); + vm.expectRevert(abi.encodeWithSignature("ExternalCallFailed()")); - receiver.pullToken( + receiver.withdrawToken( address(0), payable(address(nonETHReceiver)), 1 ether ); } - function test_revert_PullTokenNonOwner() public { + function test_revert_WithdrawTokenNonOwner() public { vm.startPrank(USER_SENDER); vm.expectRevert(UnAuthorized.selector); - receiver.pullToken(ADDRESS_DAI, payable(USER_RECEIVER), 1000); + receiver.withdrawToken(ADDRESS_DAI, payable(USER_RECEIVER), 1000); } function _getValidLzComposeCalldata( diff --git a/test/solidity/Periphery/RelayerCelerIM.t.sol b/test/solidity/Periphery/RelayerCelerIM.t.sol index 63197ea79..89f98445b 100644 --- a/test/solidity/Periphery/RelayerCelerIM.t.sol +++ b/test/solidity/Periphery/RelayerCelerIM.t.sol @@ -44,7 +44,7 @@ contract RelayerCelerIMTest is TestBase { // deploy CelerIM Receiver erc20Proxy = new ERC20Proxy(address(this)); - executor = new Executor(address(erc20Proxy)); + executor = new Executor(address(erc20Proxy), address(this)); celerIMFacet = new CelerIMFacetMutable( IMessageBus(CBRIDGE_MESSAGEBUS_ETH), REFUND_WALLET, diff --git a/test/solidity/Periphery/TokenWrapper.t.sol b/test/solidity/Periphery/TokenWrapper.t.sol index 2c2e6850f..71df2ada2 100644 --- a/test/solidity/Periphery/TokenWrapper.t.sol +++ b/test/solidity/Periphery/TokenWrapper.t.sol @@ -6,7 +6,7 @@ import { console } from "../utils/Console.sol"; import { Vm } from "forge-std/Vm.sol"; import { TokenWrapper } from "lifi/Periphery/TokenWrapper.sol"; import { TestWrappedToken as ERC20 } from "../utils/TestWrappedToken.sol"; -import { IERC20 } from "lifi/Libraries/LibAsset.sol"; +import { IERC20, LibAsset } from "lifi/Libraries/LibAsset.sol"; contract TokenWrapperTest is DSTest { Vm internal immutable vm = Vm(HEVM_ADDRESS); @@ -15,7 +15,7 @@ contract TokenWrapperTest is DSTest { function setUp() public { wrappedToken = new ERC20("TestWrappedToken", "WTST", 18); - tokenWrapper = new TokenWrapper(address(wrappedToken)); + tokenWrapper = new TokenWrapper(address(wrappedToken), address(this)); vm.deal(address(this), 100 ether); } @@ -28,6 +28,20 @@ contract TokenWrapperTest is DSTest { assert(wrappedToken.balanceOf(address(this)) == 1 ether); } + function testCanWithdrawToken() public { + // Send some ETH to the contract + (bool success, ) = address(tokenWrapper).call{ value: 1 ether }(""); + require(success, "Failed to send ETH"); + + uint256 initialBalance = address(this).balance; + tokenWrapper.withdrawToken( + address(0), + payable(address(this)), + 1 ether + ); + assertEq(address(this).balance - initialBalance, 1 ether); + } + function testCanWithdraw() public { uint256 initialBalance = address(this).balance; vm.deal(address(wrappedToken), 100 ether);