-
Notifications
You must be signed in to change notification settings - Fork 85
add converter parameter to TokenWrapper for WETH9-compatible interfac… [IWrapper v1.0.0, TokenWrapper v1.2.0] #1531
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
6c63ef9
633c97f
0379dff
ecb8b1c
a903247
5a6ab81
2d5148b
3ffe919
dcb8a6d
8843234
d364b8b
813ada6
59c8de4
8cf10c2
19f5d45
1fb5247
b3b2dba
bc81b9c
9a49316
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "stable": { | ||
| "converterAddress": "0xded1660192d4d82e7c0b628ba556861edbb5cada" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| // SPDX-License-Identifier: LGPL-3.0-only | ||
|
|
||
| pragma solidity ^0.8.17; | ||
|
|
||
| /// @title IWrapper | ||
| /// @notice Interface for token wrapper contracts | ||
| /// @author LI.FI (https://li.fi) | ||
| /// @custom:version 1.0.0 | ||
| interface IWrapper { | ||
| function deposit() external payable; | ||
|
|
||
| // solhint-disable-next-line explicit-types | ||
| function withdraw(uint wad) external; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,59 +1,94 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| // SPDX-License-Identifier: LGPL-3.0-only | ||
|
|
||
| pragma solidity ^0.8.17; | ||
|
|
||
| // solhint-disable-next-line no-unused-import | ||
| import { LibAsset } from "../Libraries/LibAsset.sol"; | ||
| import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
| import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; | ||
| import { WithdrawablePeriphery } from "../Helpers/WithdrawablePeriphery.sol"; | ||
| import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; | ||
|
|
||
| /// External wrapper interface | ||
| interface IWrapper { | ||
| function deposit() external payable; | ||
|
|
||
| // solhint-disable-next-line explicit-types | ||
| function withdraw(uint wad) external; | ||
| } | ||
| import { IWrapper } from "../Interfaces/IWrapper.sol"; | ||
| import { InvalidContract, NoTransferToNullAddress } from "../Errors/GenericErrors.sol"; | ||
|
|
||
| /// @title TokenWrapper | ||
| /// @author LI.FI (https://li.fi) | ||
| /// @notice Provides functionality for wrapping and unwrapping tokens | ||
| /// @custom:version 1.1.0 | ||
| /// @custom:version 1.2.0 | ||
| contract TokenWrapper is WithdrawablePeriphery { | ||
ezynda3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| uint256 private constant MAX_INT = 2 ** 256 - 1; | ||
| address public wrappedToken; | ||
| address public immutable WRAPPED_TOKEN; | ||
| address public immutable CONVERTER; | ||
| bool private immutable USE_CONVERTER; | ||
| uint256 private immutable SWAP_RATIO_MULTIPLIER; | ||
|
|
||
| /// Errors /// | ||
| error WithdrawFailure(); | ||
|
|
||
| /// Constructor /// | ||
| /// @notice Creates a new TokenWrapper contract | ||
| /// @param _wrappedToken Address of the wrapped token (e.g., WETH, or token returned by converter) | ||
| /// @param _converter Address of converter contract, or address(0) if wrapping 1:1 without conversion | ||
| /// @param _owner Address that will own this contract and can withdraw stuck tokens | ||
| /// @dev If converter is provided, all wrap/unwrap operations go through it for decimal or other conversions | ||
| // solhint-disable-next-line no-empty-blocks | ||
| constructor( | ||
| address _wrappedToken, | ||
| address _converter, | ||
|
||
| address _owner | ||
| ) WithdrawablePeriphery(_owner) { | ||
| wrappedToken = _wrappedToken; | ||
| IERC20(wrappedToken).approve(address(this), MAX_INT); | ||
| if (_wrappedToken == address(0)) revert NoTransferToNullAddress(); | ||
| if (_owner == address(0)) revert NoTransferToNullAddress(); | ||
|
Comment on lines
+38
to
+39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Use Per past review discussion, the team preference for constructor parameter validation is to use 🔧 Proposed fix- if (_wrappedToken == address(0)) revert NoTransferToNullAddress();
- if (_owner == address(0)) revert NoTransferToNullAddress();
+ if (_wrappedToken == address(0)) revert InvalidConfig();
+ if (_owner == address(0)) revert InvalidConfig();Based on past review comments. 🤖 Prompt for AI Agents |
||
|
|
||
| WRAPPED_TOKEN = _wrappedToken; | ||
| USE_CONVERTER = _converter != address(0); | ||
|
|
||
| if (USE_CONVERTER) { | ||
| if (!LibAsset.isContract(_converter)) revert InvalidContract(); | ||
| CONVERTER = _converter; | ||
| // Approve converter once for all future withdrawals (gas optimization) | ||
| LibAsset.maxApproveERC20( | ||
| IERC20(_wrappedToken), | ||
| _converter, | ||
| type(uint256).max | ||
| ); | ||
| // Calculate swap ratio based on decimal difference between native (18) and wrapped token | ||
| uint8 wrappedDecimals = IERC20Metadata(_wrappedToken).decimals(); | ||
| SWAP_RATIO_MULTIPLIER = 10 ** wrappedDecimals; | ||
| } else { | ||
| CONVERTER = _wrappedToken; | ||
| SWAP_RATIO_MULTIPLIER = 1 ether; // 1:1 ratio for 18 decimals | ||
| } | ||
| } | ||
|
|
||
| /// External Methods /// | ||
|
|
||
| /// @notice Wraps the native token | ||
| /// @notice Wraps the native token and transfers wrapped tokens to caller | ||
| /// @dev If converter is set, uses it to convert native to wrapped tokens using precalculated ratio | ||
| /// @dev If no converter, wraps native 1:1 and transfers msg.value of wrapped tokens | ||
| function deposit() external payable { | ||
| IWrapper(wrappedToken).deposit{ value: msg.value }(); | ||
| IERC20(wrappedToken).transfer(msg.sender, msg.value); | ||
| IWrapper(CONVERTER).deposit{ value: msg.value }(); | ||
| uint256 wrappedAmount = (msg.value * SWAP_RATIO_MULTIPLIER) / 1 ether; | ||
| SafeTransferLib.safeTransfer(WRAPPED_TOKEN, msg.sender, wrappedAmount); | ||
| } | ||
|
|
||
| /// @notice Unwraps all the caller's balance of wrapped token | ||
| /// @notice Unwraps all the caller's balance of wrapped token and returns native tokens | ||
| /// @dev Pulls wrapped tokens from msg.sender based on their balance (requires prior approval) | ||
| /// @dev Uses precalculated ratio to determine native token amount to return | ||
| function withdraw() external { | ||
| // While in a general purpose contract it would make sense | ||
| // to have `wad` equal to the minimum between the balance and the | ||
| // to have `amount` equal to the minimum between the balance and the | ||
| // given allowance, in our specific usecase allowance is always | ||
| // nearly MAX_UINT256. Using the balance only is a gas optimisation. | ||
| uint256 wad = IERC20(wrappedToken).balanceOf(msg.sender); | ||
| IERC20(wrappedToken).transferFrom(msg.sender, address(this), wad); | ||
| IWrapper(wrappedToken).withdraw(wad); | ||
| SafeTransferLib.safeTransferETH(msg.sender, wad); | ||
| uint256 amount = IERC20(WRAPPED_TOKEN).balanceOf(msg.sender); | ||
| SafeTransferLib.safeTransferFrom( | ||
| WRAPPED_TOKEN, | ||
| msg.sender, | ||
| address(this), | ||
| amount | ||
| ); | ||
|
|
||
| IWrapper(CONVERTER).withdraw(amount); | ||
| uint256 nativeAmount = (amount * 1 ether) / SWAP_RATIO_MULTIPLIER; | ||
| SafeTransferLib.safeTransferETH(msg.sender, nativeAmount); | ||
| } | ||
|
|
||
| // Needs to be able to receive native on `withdraw` | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.