|
1 | 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
2 | 2 | pragma solidity >=0.8.22; |
3 | 3 |
|
4 | | -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
5 | 4 | import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol"; |
6 | 5 | import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; |
| 6 | +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
7 | 7 | import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; |
8 | | -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; |
9 | 8 | import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; |
| 9 | +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; |
10 | 10 | import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; |
11 | 11 | import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; |
12 | | -import { SafeOracle } from "@sablier/evm-utils/src/libraries/SafeOracle.sol"; |
13 | | -import { SafeTokenSymbol } from "@sablier/evm-utils/src/libraries/SafeTokenSymbol.sol"; |
14 | 12 | import { Comptrollerable } from "@sablier/evm-utils/src/Comptrollerable.sol"; |
15 | 13 | import { ISablierComptroller } from "@sablier/evm-utils/src/interfaces/ISablierComptroller.sol"; |
16 | | - |
| 14 | +import { SafeOracle } from "@sablier/evm-utils/src/libraries/SafeOracle.sol"; |
| 15 | +import { SafeTokenSymbol } from "@sablier/evm-utils/src/libraries/SafeTokenSymbol.sol"; |
17 | 16 | import { SablierBobState } from "./abstracts/SablierBobState.sol"; |
18 | 17 | import { BobVaultShare } from "./BobVaultShare.sol"; |
| 18 | +import { IWETH9 } from "./interfaces/external/IWETH9.sol"; |
19 | 19 | import { IBobVaultShare } from "./interfaces/IBobVaultShare.sol"; |
20 | 20 | import { ISablierBob } from "./interfaces/ISablierBob.sol"; |
21 | 21 | import { ISablierBobAdapter } from "./interfaces/ISablierBobAdapter.sol"; |
@@ -188,38 +188,31 @@ contract SablierBob is |
188 | 188 | notNull(vaultId) |
189 | 189 | onlyActive(vaultId) |
190 | 190 | { |
191 | | - // Check: the deposit amount is not zero. |
192 | | - if (amount == 0) { |
193 | | - revert Errors.SablierBob_DepositAmountZero(vaultId, msg.sender); |
194 | | - } |
195 | | - |
196 | | - // Effect: sync the price from oracle. |
197 | | - _syncPriceFromOracle(vaultId); |
198 | | - |
199 | | - // Check: the vault is still active after the price sync. |
200 | | - _revertIfSettledOrExpired(vaultId); |
201 | | - |
202 | | - // Cache storage variables. |
203 | | - ISablierBobAdapter adapter = _vaults[vaultId].adapter; |
204 | | - IERC20 token = _vaults[vaultId].token; |
| 191 | + // Enter the vault. |
| 192 | + _enter({ vaultId: vaultId, from: msg.sender, amount: amount, token: _vaults[vaultId].token }); |
| 193 | + } |
205 | 194 |
|
206 | | - // Interaction: transfer tokens from caller to this contract or the adapter. |
207 | | - if (address(adapter) != address(0)) { |
208 | | - // Interaction: Transfer token from caller to the adapter. |
209 | | - token.safeTransferFrom(msg.sender, address(adapter), amount); |
| 195 | + /// @inheritdoc ISablierBob |
| 196 | + function enterWithNativeToken(uint256 vaultId) |
| 197 | + external |
| 198 | + payable |
| 199 | + override |
| 200 | + nonReentrant |
| 201 | + notNull(vaultId) |
| 202 | + onlyActive(vaultId) |
| 203 | + { |
| 204 | + // Cache the vault's token. |
| 205 | + address token = address(_vaults[vaultId].token); |
210 | 206 |
|
211 | | - // Interaction: stake the tokens via the adapter. |
212 | | - adapter.stake(vaultId, msg.sender, amount); |
213 | | - } else { |
214 | | - // Interaction: Transfer tokens from caller to this contract. |
215 | | - token.safeTransferFrom(msg.sender, address(this), amount); |
216 | | - } |
| 207 | + // Interaction: call the deposit function in the vault tokens assuming it follows the IWETH9 interface. |
| 208 | + // Otherwise, it will revert. |
| 209 | + IWETH9(token).deposit{ value: msg.value }(); |
217 | 210 |
|
218 | | - // Interaction: mint share tokens to the caller. |
219 | | - _vaults[vaultId].shareToken.mint(vaultId, msg.sender, amount); |
| 211 | + // Cast `msg.value` to `uint128`. |
| 212 | + uint128 amount = msg.value.toUint128(); |
220 | 213 |
|
221 | | - // Log the deposit. |
222 | | - emit Enter({ vaultId: vaultId, user: msg.sender, amountReceived: amount, sharesMinted: amount }); |
| 214 | + // Enter the vault. |
| 215 | + _enter({ vaultId: vaultId, from: address(this), amount: amount, token: IERC20(token) }); |
223 | 216 | } |
224 | 217 |
|
225 | 218 | /// @inheritdoc ISablierBob |
@@ -443,7 +436,56 @@ contract SablierBob is |
443 | 436 | PRIVATE STATE-CHANGING FUNCTIONS |
444 | 437 | //////////////////////////////////////////////////////////////////////////*/ |
445 | 438 |
|
| 439 | + /// @dev Common function to enter into a vault by depositing tokens into it and minting share tokens to caller. |
| 440 | + /// @param vaultId The ID of the vault to deposit into. |
| 441 | + /// @param from The address holding the vault token when calling this function. In case of native token deposits, |
| 442 | + /// the vault tokens are held by this contract. |
| 443 | + /// @param amount The amount of tokens to deposit. |
| 444 | + /// @param token The ERC-20 token accepted by the vault. |
| 445 | + function _enter(uint256 vaultId, address from, uint128 amount, IERC20 token) private { |
| 446 | + // Check: the deposit amount is not zero. |
| 447 | + if (amount == 0) { |
| 448 | + revert Errors.SablierBob_DepositAmountZero(vaultId, msg.sender); |
| 449 | + } |
| 450 | + |
| 451 | + // Effect: sync the price from oracle. |
| 452 | + _syncPriceFromOracle(vaultId); |
| 453 | + |
| 454 | + // Check: the vault is still active after the price sync. |
| 455 | + _revertIfSettledOrExpired(vaultId); |
| 456 | + |
| 457 | + // Cache storage variables. |
| 458 | + ISablierBobAdapter adapter = _vaults[vaultId].adapter; |
| 459 | + |
| 460 | + // If adapter is set, transfer tokens to the adapter. |
| 461 | + if (address(adapter) != address(0)) { |
| 462 | + // Interaction: transfer tokens to the adapter. Use `safeTransfer` for the native token path since |
| 463 | + // the contract already holds the wrapped tokens. |
| 464 | + if (from == address(this)) { |
| 465 | + token.safeTransfer(address(adapter), amount); |
| 466 | + } else { |
| 467 | + token.safeTransferFrom(from, address(adapter), amount); |
| 468 | + } |
| 469 | + |
| 470 | + // Interaction: stake tokens via the adapter on behalf of the caller. |
| 471 | + adapter.stake(vaultId, msg.sender, amount); |
| 472 | + } |
| 473 | + // Otherwise, if `from` is `msg.sender`, transfer tokens to this contract. When this function is called by |
| 474 | + // `enterWithNativeToken`, the vault tokens are held by this contract already. |
| 475 | + else if (from == msg.sender) { |
| 476 | + // Interaction: transfer tokens from caller to this contract. |
| 477 | + token.safeTransferFrom(from, address(this), amount); |
| 478 | + } |
| 479 | + |
| 480 | + // Interaction: mint share tokens to the caller. |
| 481 | + _vaults[vaultId].shareToken.mint(vaultId, msg.sender, amount); |
| 482 | + |
| 483 | + // Log the deposit. |
| 484 | + emit Enter({ vaultId: vaultId, user: msg.sender, amountReceived: amount, sharesMinted: amount }); |
| 485 | + } |
| 486 | + |
446 | 487 | /// @notice Private function that reverts if the vault is settled or expired. |
| 488 | + /// @param vaultId The ID of the vault. |
447 | 489 | function _revertIfSettledOrExpired(uint256 vaultId) private view { |
448 | 490 | if (_statusOf(vaultId) != Bob.Status.ACTIVE) { |
449 | 491 | revert Errors.SablierBob_VaultNotActive(vaultId); |
|
0 commit comments