@sablier/bob 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/LICENSE-BUSL.md +82 -0
- package/LICENSE-GPL.md +470 -0
- package/LICENSE.md +9 -0
- package/README.md +81 -0
- package/artifacts/BobVaultShare.json +936 -0
- package/artifacts/SablierBob.json +1683 -0
- package/artifacts/SablierEscrow.json +1316 -0
- package/artifacts/SablierLidoAdapter.json +1649 -0
- package/artifacts/erc20/IERC20.json +226 -0
- package/artifacts/interfaces/IBobVaultShare.json +393 -0
- package/artifacts/interfaces/ISablierBob.json +1171 -0
- package/artifacts/interfaces/ISablierEscrow.json +999 -0
- package/artifacts/interfaces/ISablierLidoAdapter.json +1141 -0
- package/artifacts/interfaces/external/ICurveStETHPool.json +128 -0
- package/artifacts/interfaces/external/ILidoWithdrawalQueue.json +209 -0
- package/artifacts/interfaces/external/IStETH.json +262 -0
- package/artifacts/interfaces/external/IWETH9.json +259 -0
- package/artifacts/interfaces/external/IWstETH.json +311 -0
- package/artifacts/libraries/Errors.json +868 -0
- package/package.json +68 -0
- package/src/BobVaultShare.sol +119 -0
- package/src/SablierBob.sol +543 -0
- package/src/SablierEscrow.sol +288 -0
- package/src/SablierLidoAdapter.sol +549 -0
- package/src/abstracts/SablierBobState.sol +156 -0
- package/src/abstracts/SablierEscrowState.sol +159 -0
- package/src/interfaces/IBobVaultShare.sol +51 -0
- package/src/interfaces/ISablierBob.sol +261 -0
- package/src/interfaces/ISablierBobAdapter.sol +157 -0
- package/src/interfaces/ISablierBobState.sol +74 -0
- package/src/interfaces/ISablierEscrow.sol +148 -0
- package/src/interfaces/ISablierEscrowState.sol +77 -0
- package/src/interfaces/ISablierLidoAdapter.sol +110 -0
- package/src/interfaces/external/ICurveStETHPool.sol +31 -0
- package/src/interfaces/external/ILidoWithdrawalQueue.sol +67 -0
- package/src/interfaces/external/IStETH.sol +18 -0
- package/src/interfaces/external/IWETH9.sol +19 -0
- package/src/interfaces/external/IWstETH.sol +32 -0
- package/src/libraries/Errors.sol +189 -0
- package/src/types/Bob.sol +49 -0
- package/src/types/Escrow.sol +49 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
pragma solidity >=0.8.22;
|
|
3
|
+
|
|
4
|
+
import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
|
|
5
|
+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
6
|
+
|
|
7
|
+
import { IBobVaultShare } from "../interfaces/IBobVaultShare.sol";
|
|
8
|
+
import { ISablierBobAdapter } from "../interfaces/ISablierBobAdapter.sol";
|
|
9
|
+
import { ISablierBobState } from "../interfaces/ISablierBobState.sol";
|
|
10
|
+
import { Errors } from "../libraries/Errors.sol";
|
|
11
|
+
import { Bob } from "../types/Bob.sol";
|
|
12
|
+
|
|
13
|
+
/// @title SablierBobState
|
|
14
|
+
/// @notice See the documentation in {ISablierBobState}.
|
|
15
|
+
abstract contract SablierBobState is ISablierBobState {
|
|
16
|
+
/*//////////////////////////////////////////////////////////////////////////
|
|
17
|
+
STATE VARIABLES
|
|
18
|
+
//////////////////////////////////////////////////////////////////////////*/
|
|
19
|
+
|
|
20
|
+
/// @dev Default adapters mapped by token address.
|
|
21
|
+
mapping(IERC20 token => ISablierBobAdapter adapter) internal _defaultAdapters;
|
|
22
|
+
|
|
23
|
+
/// @inheritdoc ISablierBobState
|
|
24
|
+
address public override nativeToken;
|
|
25
|
+
|
|
26
|
+
/// @inheritdoc ISablierBobState
|
|
27
|
+
uint256 public override nextVaultId;
|
|
28
|
+
|
|
29
|
+
/// @dev Vaults mapped by unsigned integers.
|
|
30
|
+
mapping(uint256 vaultId => Bob.Vault vault) internal _vaults;
|
|
31
|
+
|
|
32
|
+
/*//////////////////////////////////////////////////////////////////////////
|
|
33
|
+
MODIFIERS
|
|
34
|
+
//////////////////////////////////////////////////////////////////////////*/
|
|
35
|
+
|
|
36
|
+
/// @dev Checks that `vaultId` does not reference a null vault.
|
|
37
|
+
modifier notNull(uint256 vaultId) {
|
|
38
|
+
_notNull(vaultId);
|
|
39
|
+
_;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/*//////////////////////////////////////////////////////////////////////////
|
|
43
|
+
CONSTRUCTOR
|
|
44
|
+
//////////////////////////////////////////////////////////////////////////*/
|
|
45
|
+
|
|
46
|
+
/// @notice Initializes the state variables.
|
|
47
|
+
constructor() {
|
|
48
|
+
// Set the next vault ID to 1.
|
|
49
|
+
nextVaultId = 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/*//////////////////////////////////////////////////////////////////////////
|
|
53
|
+
USER-FACING READ-ONLY FUNCTIONS
|
|
54
|
+
//////////////////////////////////////////////////////////////////////////*/
|
|
55
|
+
|
|
56
|
+
/// @inheritdoc ISablierBobState
|
|
57
|
+
function getAdapter(uint256 vaultId) external view override notNull(vaultId) returns (ISablierBobAdapter adapter) {
|
|
58
|
+
adapter = _vaults[vaultId].adapter;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// @inheritdoc ISablierBobState
|
|
62
|
+
function getDefaultAdapterFor(IERC20 token) external view override returns (ISablierBobAdapter adapter) {
|
|
63
|
+
adapter = _defaultAdapters[token];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// @inheritdoc ISablierBobState
|
|
67
|
+
function getExpiry(uint256 vaultId) external view override notNull(vaultId) returns (uint40 expiry) {
|
|
68
|
+
expiry = _vaults[vaultId].expiry;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// @inheritdoc ISablierBobState
|
|
72
|
+
function getLastSyncedAt(uint256 vaultId) external view override notNull(vaultId) returns (uint40 lastSyncedAt) {
|
|
73
|
+
lastSyncedAt = _vaults[vaultId].lastSyncedAt;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// @inheritdoc ISablierBobState
|
|
77
|
+
function getLastSyncedPrice(uint256 vaultId)
|
|
78
|
+
external
|
|
79
|
+
view
|
|
80
|
+
override
|
|
81
|
+
notNull(vaultId)
|
|
82
|
+
returns (uint128 lastSyncedPrice)
|
|
83
|
+
{
|
|
84
|
+
lastSyncedPrice = _vaults[vaultId].lastSyncedPrice;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// @inheritdoc ISablierBobState
|
|
88
|
+
function getOracle(uint256 vaultId) external view override notNull(vaultId) returns (AggregatorV3Interface oracle) {
|
|
89
|
+
oracle = _vaults[vaultId].oracle;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// @inheritdoc ISablierBobState
|
|
93
|
+
function getShareToken(uint256 vaultId)
|
|
94
|
+
external
|
|
95
|
+
view
|
|
96
|
+
override
|
|
97
|
+
notNull(vaultId)
|
|
98
|
+
returns (IBobVaultShare shareToken)
|
|
99
|
+
{
|
|
100
|
+
shareToken = _vaults[vaultId].shareToken;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// @inheritdoc ISablierBobState
|
|
104
|
+
function getTargetPrice(uint256 vaultId) external view override notNull(vaultId) returns (uint128 targetPrice) {
|
|
105
|
+
targetPrice = _vaults[vaultId].targetPrice;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// @inheritdoc ISablierBobState
|
|
109
|
+
function getUnderlyingToken(uint256 vaultId) external view override notNull(vaultId) returns (IERC20 token) {
|
|
110
|
+
token = _vaults[vaultId].token;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// @inheritdoc ISablierBobState
|
|
114
|
+
function isStakedInAdapter(uint256 vaultId) external view override notNull(vaultId) returns (bool stakedInAdapter) {
|
|
115
|
+
stakedInAdapter = _vaults[vaultId].isStakedInAdapter;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// @inheritdoc ISablierBobState
|
|
119
|
+
function statusOf(uint256 vaultId) external view override notNull(vaultId) returns (Bob.Status status) {
|
|
120
|
+
status = _statusOf(vaultId);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/*//////////////////////////////////////////////////////////////////////////
|
|
124
|
+
INTERNAL READ-ONLY FUNCTIONS
|
|
125
|
+
//////////////////////////////////////////////////////////////////////////*/
|
|
126
|
+
|
|
127
|
+
/// @dev Retrieves the vault's status without performing a null check.
|
|
128
|
+
function _statusOf(uint256 vaultId) internal view returns (Bob.Status) {
|
|
129
|
+
// Return EXPIRED if the vault has expired.
|
|
130
|
+
if (block.timestamp >= _vaults[vaultId].expiry) {
|
|
131
|
+
return Bob.Status.EXPIRED;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Return SETTLED if the last synced price is greater than or equal to the target price.
|
|
135
|
+
if (_vaults[vaultId].lastSyncedPrice >= _vaults[vaultId].targetPrice) {
|
|
136
|
+
return Bob.Status.SETTLED;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Otherwise, return ACTIVE.
|
|
140
|
+
return Bob.Status.ACTIVE;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/*//////////////////////////////////////////////////////////////////////////
|
|
144
|
+
PRIVATE READ-ONLY FUNCTIONS
|
|
145
|
+
//////////////////////////////////////////////////////////////////////////*/
|
|
146
|
+
|
|
147
|
+
/// @dev Reverts if `vaultId` references a null vault.
|
|
148
|
+
/// @dev A private function is used instead of inlining this logic in a modifier because Solidity copies modifiers
|
|
149
|
+
/// into every function that uses them.
|
|
150
|
+
function _notNull(uint256 vaultId) private view {
|
|
151
|
+
// A vault is considered null if its token address is zero, as tokens are always checked on creation.
|
|
152
|
+
if (address(_vaults[vaultId].token) == address(0)) {
|
|
153
|
+
revert Errors.SablierBobState_Null(vaultId);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
pragma solidity >=0.8.22;
|
|
3
|
+
|
|
4
|
+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
5
|
+
import { UD60x18 } from "@prb/math/src/UD60x18.sol";
|
|
6
|
+
|
|
7
|
+
import { ISablierEscrowState } from "../interfaces/ISablierEscrowState.sol";
|
|
8
|
+
import { Errors } from "../libraries/Errors.sol";
|
|
9
|
+
import { Escrow } from "../types/Escrow.sol";
|
|
10
|
+
|
|
11
|
+
/// @title SablierEscrowState
|
|
12
|
+
/// @notice See the documentation in {ISablierEscrowState}.
|
|
13
|
+
abstract contract SablierEscrowState is ISablierEscrowState {
|
|
14
|
+
/*//////////////////////////////////////////////////////////////////////////
|
|
15
|
+
CONSTANTS
|
|
16
|
+
//////////////////////////////////////////////////////////////////////////*/
|
|
17
|
+
|
|
18
|
+
/// @inheritdoc ISablierEscrowState
|
|
19
|
+
UD60x18 public constant override MAX_TRADE_FEE = UD60x18.wrap(0.02e18);
|
|
20
|
+
|
|
21
|
+
/*//////////////////////////////////////////////////////////////////////////
|
|
22
|
+
STATE VARIABLES
|
|
23
|
+
//////////////////////////////////////////////////////////////////////////*/
|
|
24
|
+
|
|
25
|
+
/// @inheritdoc ISablierEscrowState
|
|
26
|
+
address public override nativeToken;
|
|
27
|
+
|
|
28
|
+
/// @inheritdoc ISablierEscrowState
|
|
29
|
+
uint256 public override nextOrderId;
|
|
30
|
+
|
|
31
|
+
/// @inheritdoc ISablierEscrowState
|
|
32
|
+
UD60x18 public override tradeFee;
|
|
33
|
+
|
|
34
|
+
/// @dev Orders mapped by order ID.
|
|
35
|
+
mapping(uint256 orderId => Escrow.Order order) internal _orders;
|
|
36
|
+
|
|
37
|
+
/*//////////////////////////////////////////////////////////////////////////
|
|
38
|
+
CONSTRUCTOR
|
|
39
|
+
//////////////////////////////////////////////////////////////////////////*/
|
|
40
|
+
|
|
41
|
+
/// @notice Initializes the state variables.
|
|
42
|
+
/// @param initialTradeFee The initial trade fee percentage.
|
|
43
|
+
constructor(UD60x18 initialTradeFee) {
|
|
44
|
+
// Check: the trade fee is not greater than the maximum trade fee.
|
|
45
|
+
_notTooHigh(initialTradeFee);
|
|
46
|
+
|
|
47
|
+
// Set the next order ID to 1.
|
|
48
|
+
nextOrderId = 1;
|
|
49
|
+
|
|
50
|
+
// Set the initial trade fee.
|
|
51
|
+
tradeFee = initialTradeFee;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/*//////////////////////////////////////////////////////////////////////////
|
|
55
|
+
MODIFIERS
|
|
56
|
+
//////////////////////////////////////////////////////////////////////////*/
|
|
57
|
+
|
|
58
|
+
/// @dev Checks that `orderId` does not reference a null order.
|
|
59
|
+
modifier notNull(uint256 orderId) {
|
|
60
|
+
_notNull(orderId);
|
|
61
|
+
_;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/*//////////////////////////////////////////////////////////////////////////
|
|
65
|
+
USER-FACING READ-ONLY FUNCTIONS
|
|
66
|
+
//////////////////////////////////////////////////////////////////////////*/
|
|
67
|
+
|
|
68
|
+
/// @inheritdoc ISablierEscrowState
|
|
69
|
+
function getBuyer(uint256 orderId) external view override notNull(orderId) returns (address buyer) {
|
|
70
|
+
buyer = _orders[orderId].buyer;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// @inheritdoc ISablierEscrowState
|
|
74
|
+
function getBuyToken(uint256 orderId) external view override notNull(orderId) returns (IERC20 buyToken) {
|
|
75
|
+
buyToken = _orders[orderId].buyToken;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// @inheritdoc ISablierEscrowState
|
|
79
|
+
function getExpiryTime(uint256 orderId) external view override notNull(orderId) returns (uint40 expiryTime) {
|
|
80
|
+
expiryTime = _orders[orderId].expiryTime;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// @inheritdoc ISablierEscrowState
|
|
84
|
+
function getMinBuyAmount(uint256 orderId) external view override notNull(orderId) returns (uint128 minBuyAmount) {
|
|
85
|
+
minBuyAmount = _orders[orderId].minBuyAmount;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// @inheritdoc ISablierEscrowState
|
|
89
|
+
function getSellAmount(uint256 orderId) external view override notNull(orderId) returns (uint128 sellAmount) {
|
|
90
|
+
sellAmount = _orders[orderId].sellAmount;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// @inheritdoc ISablierEscrowState
|
|
94
|
+
function getSeller(uint256 orderId) external view override notNull(orderId) returns (address seller) {
|
|
95
|
+
seller = _orders[orderId].seller;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// @inheritdoc ISablierEscrowState
|
|
99
|
+
function getSellToken(uint256 orderId) external view override notNull(orderId) returns (IERC20 sellToken) {
|
|
100
|
+
sellToken = _orders[orderId].sellToken;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// @inheritdoc ISablierEscrowState
|
|
104
|
+
function statusOf(uint256 orderId) external view override notNull(orderId) returns (Escrow.Status status) {
|
|
105
|
+
status = _statusOf(orderId);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// @inheritdoc ISablierEscrowState
|
|
109
|
+
function wasCanceled(uint256 orderId) external view override notNull(orderId) returns (bool result) {
|
|
110
|
+
result = _orders[orderId].wasCanceled;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// @inheritdoc ISablierEscrowState
|
|
114
|
+
function wasFilled(uint256 orderId) external view override notNull(orderId) returns (bool result) {
|
|
115
|
+
result = _orders[orderId].wasFilled;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/*//////////////////////////////////////////////////////////////////////////
|
|
119
|
+
INTERNAL READ-ONLY FUNCTIONS
|
|
120
|
+
//////////////////////////////////////////////////////////////////////////*/
|
|
121
|
+
|
|
122
|
+
/// @dev Reverts if `newTradeFee` is greater than the maximum trade fee.
|
|
123
|
+
function _notTooHigh(UD60x18 newTradeFee) internal pure {
|
|
124
|
+
if (newTradeFee.gt(MAX_TRADE_FEE)) {
|
|
125
|
+
revert Errors.SablierEscrowState_NewTradeFeeTooHigh(newTradeFee, MAX_TRADE_FEE);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// @dev Return the order status without performing a null check.
|
|
130
|
+
function _statusOf(uint256 orderId) internal view returns (Escrow.Status) {
|
|
131
|
+
if (_orders[orderId].wasFilled) {
|
|
132
|
+
return Escrow.Status.FILLED;
|
|
133
|
+
}
|
|
134
|
+
if (_orders[orderId].wasCanceled) {
|
|
135
|
+
return Escrow.Status.CANCELLED;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
uint40 expiryTime = _orders[orderId].expiryTime;
|
|
139
|
+
|
|
140
|
+
// Return EXPIRED if the order has an expiry timestamp and it has expired.
|
|
141
|
+
if (expiryTime != 0 && block.timestamp >= expiryTime) {
|
|
142
|
+
return Escrow.Status.EXPIRED;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return Escrow.Status.OPEN;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/*//////////////////////////////////////////////////////////////////////////
|
|
149
|
+
PRIVATE READ-ONLY FUNCTIONS
|
|
150
|
+
//////////////////////////////////////////////////////////////////////////*/
|
|
151
|
+
|
|
152
|
+
/// @dev Reverts if `orderId` references a null order.
|
|
153
|
+
function _notNull(uint256 orderId) private view {
|
|
154
|
+
// An order is considered null if its seller address is zero.
|
|
155
|
+
if (_orders[orderId].seller == address(0)) {
|
|
156
|
+
revert Errors.SablierEscrowState_Null(orderId);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
pragma solidity >=0.8.22;
|
|
3
|
+
|
|
4
|
+
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
|
5
|
+
|
|
6
|
+
/// @title IBobVaultShare
|
|
7
|
+
/// @notice Interface for the ERC-20 token representing shares in a Bob vault.
|
|
8
|
+
interface IBobVaultShare is IERC20Metadata {
|
|
9
|
+
/*//////////////////////////////////////////////////////////////////////////
|
|
10
|
+
READ-ONLY FUNCTIONS
|
|
11
|
+
//////////////////////////////////////////////////////////////////////////*/
|
|
12
|
+
|
|
13
|
+
/// @notice Returns the address of the Bob contract with the authority to mint and burn tokens.
|
|
14
|
+
/// @dev This is an immutable state variable.
|
|
15
|
+
function SABLIER_BOB() external view returns (address);
|
|
16
|
+
|
|
17
|
+
/// @notice Returns the vault ID this share token represents.
|
|
18
|
+
/// @dev This is an immutable state variable.
|
|
19
|
+
function VAULT_ID() external view returns (uint256);
|
|
20
|
+
|
|
21
|
+
/*//////////////////////////////////////////////////////////////////////////
|
|
22
|
+
STATE-CHANGING FUNCTIONS
|
|
23
|
+
//////////////////////////////////////////////////////////////////////////*/
|
|
24
|
+
|
|
25
|
+
/// @notice Mints `amount` tokens to `to`.
|
|
26
|
+
///
|
|
27
|
+
/// @dev Emits a {Transfer} event.
|
|
28
|
+
///
|
|
29
|
+
/// Requirements:
|
|
30
|
+
/// - The caller must be the SablierBob contract.
|
|
31
|
+
/// - `vaultId` must be equal to the {VAULT_ID}.
|
|
32
|
+
///
|
|
33
|
+
/// @param vaultId The vault ID that this share token represents.
|
|
34
|
+
/// @param to The address to mint tokens to.
|
|
35
|
+
/// @param amount The amount of tokens to mint.
|
|
36
|
+
function mint(uint256 vaultId, address to, uint256 amount) external;
|
|
37
|
+
|
|
38
|
+
/// @notice Burns `amount` tokens from `from`.
|
|
39
|
+
///
|
|
40
|
+
/// @dev Emits a {Transfer} event.
|
|
41
|
+
///
|
|
42
|
+
/// Requirements:
|
|
43
|
+
/// - The caller must be the SablierBob contract.
|
|
44
|
+
/// - `vaultId` must be equal to the {VAULT_ID}.
|
|
45
|
+
/// - `from` must have at least `amount` tokens.
|
|
46
|
+
///
|
|
47
|
+
/// @param vaultId The vault ID that this share token represents.
|
|
48
|
+
/// @param from The address to burn tokens from.
|
|
49
|
+
/// @param amount The amount of tokens to burn.
|
|
50
|
+
function burn(uint256 vaultId, address from, uint256 amount) external;
|
|
51
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
pragma solidity >=0.8.22;
|
|
3
|
+
|
|
4
|
+
import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
|
|
5
|
+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
6
|
+
import { IComptrollerable } from "@sablier/evm-utils/src/interfaces/IComptrollerable.sol";
|
|
7
|
+
|
|
8
|
+
import { IBobVaultShare } from "./IBobVaultShare.sol";
|
|
9
|
+
import { ISablierBobAdapter } from "./ISablierBobAdapter.sol";
|
|
10
|
+
import { ISablierBobState } from "./ISablierBobState.sol";
|
|
11
|
+
|
|
12
|
+
/// @title ISablierBob
|
|
13
|
+
/// @notice Price-gated vaults that unlock deposited tokens when the price returned by the oracle is greater than or
|
|
14
|
+
/// equal to the target price set by the vault creator. The tokens are also unlocked if the vault expires. When a vault
|
|
15
|
+
/// is configured with a adapter, the protocol automatically stakes the tokens via adapter and earns yield on the
|
|
16
|
+
/// deposit amount.
|
|
17
|
+
interface ISablierBob is IComptrollerable, ISablierBobState {
|
|
18
|
+
/*//////////////////////////////////////////////////////////////////////////
|
|
19
|
+
EVENTS
|
|
20
|
+
//////////////////////////////////////////////////////////////////////////*/
|
|
21
|
+
|
|
22
|
+
/// @notice Emitted when a new vault is created.
|
|
23
|
+
event CreateVault(
|
|
24
|
+
uint256 indexed vaultId,
|
|
25
|
+
IERC20 indexed token,
|
|
26
|
+
AggregatorV3Interface indexed oracle,
|
|
27
|
+
ISablierBobAdapter adapter,
|
|
28
|
+
IBobVaultShare shareToken,
|
|
29
|
+
uint128 targetPrice,
|
|
30
|
+
uint40 expiry
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
/// @notice Emitted when a user deposits tokens into a vault.
|
|
34
|
+
event Enter(uint256 indexed vaultId, address indexed user, uint128 amountReceived, uint128 sharesMinted);
|
|
35
|
+
|
|
36
|
+
/// @notice Emitted when a user redeems their shares from a settled vault.
|
|
37
|
+
event Redeem(
|
|
38
|
+
uint256 indexed vaultId,
|
|
39
|
+
address indexed user,
|
|
40
|
+
uint128 amountReceived,
|
|
41
|
+
uint128 sharesBurned,
|
|
42
|
+
uint256 fee
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
/// @notice Emitted when the comptroller sets a new default adapter for a token.
|
|
46
|
+
event SetDefaultAdapter(IERC20 indexed token, ISablierBobAdapter indexed adapter);
|
|
47
|
+
|
|
48
|
+
/// @notice Emitted when the native token address is set by the comptroller.
|
|
49
|
+
event SetNativeToken(address indexed comptroller, address nativeToken);
|
|
50
|
+
|
|
51
|
+
/// @notice Emitted when a vault's price is synced from the oracle.
|
|
52
|
+
event SyncPriceFromOracle(
|
|
53
|
+
uint256 indexed vaultId,
|
|
54
|
+
AggregatorV3Interface indexed oracle,
|
|
55
|
+
uint128 latestPrice,
|
|
56
|
+
uint40 syncedAt
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
/// @notice Emitted when tokens staked in the adapter for a given vault are unstaked.
|
|
60
|
+
event UnstakeFromAdapter(
|
|
61
|
+
uint256 indexed vaultId,
|
|
62
|
+
ISablierBobAdapter indexed adapter,
|
|
63
|
+
uint128 wrappedTokenUnstakedAmount,
|
|
64
|
+
uint128 amountReceivedFromAdapter
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
/*//////////////////////////////////////////////////////////////////////////
|
|
68
|
+
READ-ONLY FUNCTIONS
|
|
69
|
+
//////////////////////////////////////////////////////////////////////////*/
|
|
70
|
+
|
|
71
|
+
/// @notice Calculates the minimum fee in wei required to redeem from the given vault ID. Returns 0 for vaults with
|
|
72
|
+
/// an adapter, since the fee is taken from yield generated.
|
|
73
|
+
/// @dev Reverts if `vaultId` references a null vault.
|
|
74
|
+
/// @param vaultId The vault ID for the query.
|
|
75
|
+
function calculateMinFeeWei(uint256 vaultId) external view returns (uint256 minFeeWei);
|
|
76
|
+
|
|
77
|
+
/*//////////////////////////////////////////////////////////////////////////
|
|
78
|
+
STATE-CHANGING FUNCTIONS
|
|
79
|
+
//////////////////////////////////////////////////////////////////////////*/
|
|
80
|
+
|
|
81
|
+
/// @notice Creates a new vault with the specified parameters.
|
|
82
|
+
///
|
|
83
|
+
/// @dev Emits a {CreateVault} event.
|
|
84
|
+
///
|
|
85
|
+
/// Notes:
|
|
86
|
+
/// - A new ERC-20 share token is deployed for each vault to represent user's share of deposits in the vault.
|
|
87
|
+
/// - The default adapter for the token is copied as the vault adapter. Any change in the default adapter does not
|
|
88
|
+
/// affect existing vaults.
|
|
89
|
+
/// - Vault creator is responsible for choosing a valid oracle. They should use Chainlink oracles, as the
|
|
90
|
+
/// integration is based on their API.
|
|
91
|
+
///
|
|
92
|
+
/// Requirements:
|
|
93
|
+
/// - `token` must not be the zero address.
|
|
94
|
+
/// - `token` must implement `symbol()` and `decimals()` functions.
|
|
95
|
+
/// - `expiry` must be in the future.
|
|
96
|
+
/// - `oracle` must implement the Chainlink's {AggregatorV3Interface} interface.
|
|
97
|
+
/// - `oracle` must return a positive price when `latestRoundData()` is called.
|
|
98
|
+
/// - `oracle` must return a non-zero value no greater than 36 when `decimals()` is called.
|
|
99
|
+
/// - `targetPrice` must not be zero or greater than the current price returned by the provided oracle.
|
|
100
|
+
///
|
|
101
|
+
/// @param token The address of the ERC-20 token that will be accepted for deposits.
|
|
102
|
+
/// @param oracle The address of the price feed oracle for the deposit token.
|
|
103
|
+
/// @param expiry The Unix timestamp when the vault expires.
|
|
104
|
+
/// @param targetPrice The target price at which the vault settles, denominated in Chainlink's 8-decimal format for
|
|
105
|
+
/// USD prices, where 1e8 is $1.
|
|
106
|
+
/// @return vaultId The ID of the newly created vault.
|
|
107
|
+
function createVault(
|
|
108
|
+
IERC20 token,
|
|
109
|
+
AggregatorV3Interface oracle,
|
|
110
|
+
uint40 expiry,
|
|
111
|
+
uint128 targetPrice
|
|
112
|
+
)
|
|
113
|
+
external
|
|
114
|
+
returns (uint256 vaultId);
|
|
115
|
+
|
|
116
|
+
/// @notice Enter into a vault by depositing tokens into it and minting share tokens to the caller.
|
|
117
|
+
///
|
|
118
|
+
/// @dev Emits an {Enter} event.
|
|
119
|
+
///
|
|
120
|
+
/// Notes:
|
|
121
|
+
/// - If an adapter is configured for the vault, tokens are automatically staked for yield using the adapter.
|
|
122
|
+
/// - Share tokens are minted 1:1 with the deposited amount.
|
|
123
|
+
///
|
|
124
|
+
/// Requirements:
|
|
125
|
+
/// - `vaultId` must not reference a null vault.
|
|
126
|
+
/// - The vault must have ACTIVE status.
|
|
127
|
+
/// - `amount` must be greater than zero.
|
|
128
|
+
/// - The caller must have approved this contract to transfer `amount` tokens.
|
|
129
|
+
///
|
|
130
|
+
/// @param vaultId The ID of the vault to deposit into.
|
|
131
|
+
/// @param amount The amount of tokens to deposit.
|
|
132
|
+
function enter(uint256 vaultId, uint128 amount) external;
|
|
133
|
+
|
|
134
|
+
/// @notice Enter into a vault by depositing native token (such as ETH, POL, etc.) into it and minting share tokens
|
|
135
|
+
/// to the caller.
|
|
136
|
+
///
|
|
137
|
+
/// @dev Emits an {Enter} event.
|
|
138
|
+
///
|
|
139
|
+
/// Notes:
|
|
140
|
+
/// - `msg.value` is used as the deposit amount.
|
|
141
|
+
/// - See notes for {enter}.
|
|
142
|
+
///
|
|
143
|
+
/// Requirements:
|
|
144
|
+
/// - See requirements for {enter}.
|
|
145
|
+
/// - `msg.value` must be greater than zero and must not exceed `type(uint128).max`.
|
|
146
|
+
///
|
|
147
|
+
/// @param vaultId The ID of the vault to deposit into.
|
|
148
|
+
function enterWithNativeToken(uint256 vaultId) external payable;
|
|
149
|
+
|
|
150
|
+
/// @notice Called by adapter when share tokens for a given vault are transferred between users. This is required
|
|
151
|
+
/// for accounting of the yield generated by the adapter.
|
|
152
|
+
///
|
|
153
|
+
/// Requirements:
|
|
154
|
+
/// - The caller must be the share token contract stored in the given vault.
|
|
155
|
+
/// - The calculated wstETH transfer amount must not be zero.
|
|
156
|
+
///
|
|
157
|
+
/// @param vaultId The ID of the vault.
|
|
158
|
+
/// @param from The address transferring share tokens.
|
|
159
|
+
/// @param to The address receiving share tokens.
|
|
160
|
+
/// @param amount The number of share tokens being transferred.
|
|
161
|
+
/// @param fromBalanceBefore The number of share tokens the sender had before the transfer.
|
|
162
|
+
function onShareTransfer(
|
|
163
|
+
uint256 vaultId,
|
|
164
|
+
address from,
|
|
165
|
+
address to,
|
|
166
|
+
uint256 amount,
|
|
167
|
+
uint256 fromBalanceBefore
|
|
168
|
+
)
|
|
169
|
+
external;
|
|
170
|
+
|
|
171
|
+
/// @notice Redeem the tokens by burning user shares.
|
|
172
|
+
///
|
|
173
|
+
/// @dev Emits a {Redeem} event.
|
|
174
|
+
///
|
|
175
|
+
/// Notes:
|
|
176
|
+
/// - If no adapter is configured for the vault, a fee in the native token is applied.
|
|
177
|
+
/// - If an adapter is configured for the vault, a fee, in the deposit token, is deducted from yield generated by
|
|
178
|
+
/// the adapter.
|
|
179
|
+
/// - If unstake via Lido withdrawal queue contract is triggered, redeem will revert until the withdrawal from the
|
|
180
|
+
/// Lido queue is finalized.
|
|
181
|
+
///
|
|
182
|
+
/// Requirements:
|
|
183
|
+
/// - `vaultId` must not reference a null vault.
|
|
184
|
+
/// - Either block timestamp must be greater than or equal to the vault expiry or the latest price from the oracle
|
|
185
|
+
/// must be greater than or equal to the target price.
|
|
186
|
+
/// - The share balance of the caller must be greater than zero.
|
|
187
|
+
/// - If no adapter is configured for the vault, `msg.value` must be greater than or equal to the min fee required
|
|
188
|
+
/// in the native token.
|
|
189
|
+
///
|
|
190
|
+
/// @param vaultId The ID of the vault to redeem from.
|
|
191
|
+
/// @return transferAmount The amount of tokens transferred to the caller, after fees are deducted (only applicable
|
|
192
|
+
/// if adapter is set).
|
|
193
|
+
/// @return feeAmountDeductedFromYield The fee amount deducted from the yield. Zero if no adapter is set.
|
|
194
|
+
function redeem(uint256 vaultId)
|
|
195
|
+
external
|
|
196
|
+
payable
|
|
197
|
+
returns (uint128 transferAmount, uint128 feeAmountDeductedFromYield);
|
|
198
|
+
|
|
199
|
+
/// @notice Sets the default adapter for a specific token.
|
|
200
|
+
///
|
|
201
|
+
/// @dev Emits a {SetDefaultAdapter} event.
|
|
202
|
+
///
|
|
203
|
+
/// Notes:
|
|
204
|
+
/// - This only affects future vaults.
|
|
205
|
+
///
|
|
206
|
+
/// Requirements:
|
|
207
|
+
/// - The caller must be the comptroller.
|
|
208
|
+
/// - If new adapter is not zero address, it must implement {ISablierBobAdapter} interface.
|
|
209
|
+
///
|
|
210
|
+
/// @param token The token address to set the adapter for.
|
|
211
|
+
/// @param newAdapter The address of the new adapter.
|
|
212
|
+
function setDefaultAdapter(IERC20 token, ISablierBobAdapter newAdapter) external;
|
|
213
|
+
|
|
214
|
+
/// @notice Sets the native token address. Once set, it cannot be changed.
|
|
215
|
+
/// @dev For more information, see the documentation for {nativeToken}.
|
|
216
|
+
///
|
|
217
|
+
/// Emits a {SetNativeToken} event.
|
|
218
|
+
///
|
|
219
|
+
/// Requirements:
|
|
220
|
+
/// - `msg.sender` must be the comptroller.
|
|
221
|
+
/// - `newNativeToken` must not be zero address.
|
|
222
|
+
/// - The native token must not be already set.
|
|
223
|
+
/// @param newNativeToken The address of the native token.
|
|
224
|
+
function setNativeToken(address newNativeToken) external;
|
|
225
|
+
|
|
226
|
+
/// @notice Fetches the latest price from the oracle set for a vault and updates it in the vault storage.
|
|
227
|
+
///
|
|
228
|
+
/// @dev Emits a {SyncPriceFromOracle} event.
|
|
229
|
+
///
|
|
230
|
+
/// Notes:
|
|
231
|
+
/// - Oracle staleness is not validated on-chain when calling this function. Any price returned by the oracle is
|
|
232
|
+
/// accepted.
|
|
233
|
+
/// - Useful for syncing the price from oracle without calling {redeem} or {enter}. This function can be called by
|
|
234
|
+
/// anyone to settle vault when the price is above the target price.
|
|
235
|
+
///
|
|
236
|
+
/// Requirements:
|
|
237
|
+
/// - `vaultId` must not reference a null vault.
|
|
238
|
+
/// - The vault must have ACTIVE status.
|
|
239
|
+
/// - The oracle must return a positive price.
|
|
240
|
+
///
|
|
241
|
+
/// @param vaultId The ID of the vault to sync.
|
|
242
|
+
/// @return latestPrice The latest price fetched from the oracle, denominated in Chainlink's 8-decimal format for
|
|
243
|
+
/// USD prices, where 1e8 is $1.
|
|
244
|
+
function syncPriceFromOracle(uint256 vaultId) external returns (uint128 latestPrice);
|
|
245
|
+
|
|
246
|
+
/// @notice Unstake all tokens from the adapter for a given vault.
|
|
247
|
+
///
|
|
248
|
+
/// @dev Emits an {UnstakeFromAdapter} event.
|
|
249
|
+
///
|
|
250
|
+
/// Requirements:
|
|
251
|
+
/// - `vaultId` must not reference a null vault.
|
|
252
|
+
/// - The adapter set in the vault must not be zero address.
|
|
253
|
+
/// - Either block timestamp must be greater than or equal to the vault expiry or the latest price from the oracle
|
|
254
|
+
/// must be greater than or equal to the target price.
|
|
255
|
+
/// - The vault must not have been unstaked already.
|
|
256
|
+
/// - The amount staked must be greater than zero.
|
|
257
|
+
///
|
|
258
|
+
/// @param vaultId The ID of the vault.
|
|
259
|
+
/// @return amountReceivedFromAdapter The amount of tokens received from the adapter.
|
|
260
|
+
function unstakeTokensViaAdapter(uint256 vaultId) external returns (uint128 amountReceivedFromAdapter);
|
|
261
|
+
}
|