@bananapus/core-v6 0.0.63 → 0.0.64
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/package.json +1 -1
- package/src/JBController.sol +9 -1
- package/src/JBPrices.sol +251 -113
- package/src/JBProjects.sol +42 -1
- package/src/interfaces/IJBController.sol +1 -0
- package/src/interfaces/IJBPrices.sol +66 -27
- package/src/interfaces/IJBProjects.sol +18 -1
package/package.json
CHANGED
package/src/JBController.sol
CHANGED
|
@@ -62,6 +62,7 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
62
62
|
error JBController_AddingPriceFeedNotAllowed(uint256 projectId);
|
|
63
63
|
error JBController_CreditTransfersPaused(uint256 projectId, uint256 rulesetId);
|
|
64
64
|
error JBController_InvalidCashOutTaxRate(uint256 rate, uint256 limit);
|
|
65
|
+
error JBController_InvalidCreationFee(uint256 value, uint256 requiredFee);
|
|
65
66
|
error JBController_InvalidReservedPercent(uint256 percent, uint256 limit);
|
|
66
67
|
error JBController_MintNotAllowedAndNotTerminalOrHook(address caller);
|
|
67
68
|
error JBController_NoReservedTokens(uint256 projectId);
|
|
@@ -408,11 +409,18 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
408
409
|
string calldata memo
|
|
409
410
|
)
|
|
410
411
|
external
|
|
412
|
+
payable
|
|
411
413
|
override
|
|
412
414
|
returns (uint256 projectId)
|
|
413
415
|
{
|
|
416
|
+
// Forward the exact project creation fee to `JBProjects`.
|
|
417
|
+
uint256 creationFee = PROJECTS.creationFee();
|
|
418
|
+
if (msg.value != creationFee) {
|
|
419
|
+
revert JBController_InvalidCreationFee({value: msg.value, requiredFee: creationFee});
|
|
420
|
+
}
|
|
421
|
+
|
|
414
422
|
// Mint the project ERC-721 into the owner's wallet.
|
|
415
|
-
projectId = PROJECTS.createFor(owner);
|
|
423
|
+
projectId = PROJECTS.createFor{value: creationFee}(owner);
|
|
416
424
|
|
|
417
425
|
// If provided, set the project's metadata URI.
|
|
418
426
|
if (bytes(projectUri).length > 0) {
|
package/src/JBPrices.sol
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
pragma solidity 0.8.28;
|
|
3
3
|
|
|
4
4
|
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
5
|
-
import {
|
|
5
|
+
import {Context, ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
|
|
6
6
|
import {mulDiv} from "@prb/math/src/Common.sol";
|
|
7
7
|
|
|
8
8
|
import {JBControlled} from "./abstract/JBControlled.sol";
|
|
@@ -13,21 +13,19 @@ import {IJBPriceFeed} from "./interfaces/IJBPriceFeed.sol";
|
|
|
13
13
|
import {IJBPrices} from "./interfaces/IJBPrices.sol";
|
|
14
14
|
import {IJBProjects} from "./interfaces/IJBProjects.sol";
|
|
15
15
|
|
|
16
|
-
/// @notice
|
|
17
|
-
///
|
|
18
|
-
///
|
|
19
|
-
///
|
|
20
|
-
///
|
|
21
|
-
/// fund loss). The inverse of any registered feed is auto-calculated. Projects can have their own feeds; project ID 0
|
|
22
|
-
/// holds protocol-wide defaults.
|
|
16
|
+
/// @notice Resolves protocol currency conversions from append-only project and default price feeds.
|
|
17
|
+
/// @dev Each feed prices one unit of `unitCurrency` in `pricingCurrency`. Feeds cannot be changed or removed once
|
|
18
|
+
/// added. Later feeds for the same exact pair act as backups when earlier feeds revert or return zero. Project-specific
|
|
19
|
+
/// feeds are checked before project ID 0 defaults, and inverse prices are derived at read time when only the opposite
|
|
20
|
+
/// direction is configured. If no configured feed returns a non-zero price, the read reverts instead of guessing.
|
|
23
21
|
contract JBPrices is JBControlled, JBPermissioned, ERC2771Context, Ownable, IJBPrices {
|
|
24
22
|
//*********************************************************************//
|
|
25
23
|
// --------------------------- custom errors ------------------------- //
|
|
26
24
|
//*********************************************************************//
|
|
27
25
|
|
|
28
|
-
error
|
|
26
|
+
error JBPrices_PriceFeedAlreadyAdded(IJBPriceFeed feed);
|
|
29
27
|
error JBPrices_PriceFeedNotFound(uint256 projectId, uint256 pricingCurrency, uint256 unitCurrency);
|
|
30
|
-
error
|
|
28
|
+
error JBPrices_ZeroPriceFeed();
|
|
31
29
|
error JBPrices_ZeroPricingCurrency(uint256 projectId, uint256 pricingCurrency);
|
|
32
30
|
error JBPrices_ZeroUnitCurrency(uint256 projectId, uint256 unitCurrency);
|
|
33
31
|
|
|
@@ -46,23 +44,23 @@ contract JBPrices is JBControlled, JBPermissioned, ERC2771Context, Ownable, IJBP
|
|
|
46
44
|
IJBProjects public immutable override PROJECTS;
|
|
47
45
|
|
|
48
46
|
//*********************************************************************//
|
|
49
|
-
//
|
|
47
|
+
// -------------------- internal stored properties ------------------- //
|
|
50
48
|
//*********************************************************************//
|
|
51
49
|
|
|
52
|
-
/// @notice
|
|
53
|
-
/// @dev The
|
|
54
|
-
/// @custom:param projectId The ID of the project the feed applies to.
|
|
55
|
-
///
|
|
56
|
-
/// @custom:param
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
override priceFeedFor;
|
|
50
|
+
/// @notice Price feeds available to each project for each exact currency pair.
|
|
51
|
+
/// @dev The array is append-only. Index 0 remains the primary feed, and later indexes are backups.
|
|
52
|
+
/// @custom:param projectId The ID of the project the feed applies to. Project ID 0 stores protocol defaults.
|
|
53
|
+
/// @custom:param pricingCurrency The currency the feed's returned price is denominated in.
|
|
54
|
+
/// @custom:param unitCurrency The currency whose unit is priced by the feed.
|
|
55
|
+
mapping(
|
|
56
|
+
uint256 projectId => mapping(uint256 pricingCurrency => mapping(uint256 unitCurrency => IJBPriceFeed[]))
|
|
57
|
+
) internal _priceFeedsFor;
|
|
61
58
|
|
|
62
59
|
//*********************************************************************//
|
|
63
60
|
// ---------------------------- constructor -------------------------- //
|
|
64
61
|
//*********************************************************************//
|
|
65
62
|
|
|
63
|
+
/// @notice Initializes the price registry and its permissioned dependencies.
|
|
66
64
|
/// @param directory A contract storing directories of terminals and controllers for each project.
|
|
67
65
|
/// @param permissions A contract storing permissions.
|
|
68
66
|
/// @param projects A contract which mints ERC-721s that represent project ownership and transfers.
|
|
@@ -87,17 +85,16 @@ contract JBPrices is JBControlled, JBPermissioned, ERC2771Context, Ownable, IJBP
|
|
|
87
85
|
// ---------------------- external transactions ---------------------- //
|
|
88
86
|
//*********************************************************************//
|
|
89
87
|
|
|
90
|
-
/// @notice
|
|
91
|
-
///
|
|
92
|
-
///
|
|
93
|
-
///
|
|
94
|
-
///
|
|
95
|
-
///
|
|
96
|
-
///
|
|
97
|
-
/// @param
|
|
98
|
-
/// @param
|
|
99
|
-
/// @param
|
|
100
|
-
/// @param feed The address of the price feed to add.
|
|
88
|
+
/// @notice Adds an append-only price feed for a project's exact currency pair.
|
|
89
|
+
/// @dev Project ID 0 stores protocol defaults and can only be configured by this contract's owner. Non-zero
|
|
90
|
+
/// project IDs can only be configured by that project's controller. The feed is stored only for the exact
|
|
91
|
+
/// `pricingCurrency`/`unitCurrency` direction; the opposite direction is derived by `pricePerUnitOf` when needed.
|
|
92
|
+
/// @dev Later feeds for the same exact pair are backups. The existing feeds remain preferred, and this function
|
|
93
|
+
/// rejects duplicate feed addresses for the exact pair.
|
|
94
|
+
/// @param projectId The ID of the project to add the feed for, or 0 to add a protocol default.
|
|
95
|
+
/// @param pricingCurrency The currency that the feed's returned price is denominated in.
|
|
96
|
+
/// @param unitCurrency The currency whose unit is priced by the feed.
|
|
97
|
+
/// @param feed The price feed to add.
|
|
101
98
|
function addPriceFeedFor(
|
|
102
99
|
uint256 projectId,
|
|
103
100
|
uint256 pricingCurrency,
|
|
@@ -107,48 +104,26 @@ contract JBPrices is JBControlled, JBPermissioned, ERC2771Context, Ownable, IJBP
|
|
|
107
104
|
external
|
|
108
105
|
override
|
|
109
106
|
{
|
|
110
|
-
//
|
|
111
|
-
//
|
|
107
|
+
// Project 0 configures defaults for every project; each other project delegates feed configuration to its
|
|
108
|
+
// controller.
|
|
112
109
|
projectId == DEFAULT_PROJECT_ID ? _checkOwner() : _onlyControllerOf(projectId);
|
|
113
110
|
|
|
114
|
-
// Make sure the pricing currency isn't 0.
|
|
115
111
|
if (pricingCurrency == 0) {
|
|
116
112
|
revert JBPrices_ZeroPricingCurrency({projectId: projectId, pricingCurrency: pricingCurrency});
|
|
117
113
|
}
|
|
118
114
|
|
|
119
|
-
// Make sure the unit currency isn't 0.
|
|
120
115
|
if (unitCurrency == 0) revert JBPrices_ZeroUnitCurrency({projectId: projectId, unitCurrency: unitCurrency});
|
|
121
116
|
|
|
122
|
-
|
|
123
|
-
if (
|
|
124
|
-
priceFeedFor[DEFAULT_PROJECT_ID][pricingCurrency][unitCurrency] != IJBPriceFeed(address(0))
|
|
125
|
-
|| priceFeedFor[DEFAULT_PROJECT_ID][unitCurrency][pricingCurrency] != IJBPriceFeed(address(0))
|
|
126
|
-
) {
|
|
127
|
-
revert JBPrices_PriceFeedAlreadyExists({
|
|
128
|
-
feed: priceFeedFor[DEFAULT_PROJECT_ID][pricingCurrency][unitCurrency] != IJBPriceFeed(address(0))
|
|
129
|
-
? priceFeedFor[DEFAULT_PROJECT_ID][pricingCurrency][unitCurrency]
|
|
130
|
-
: priceFeedFor[DEFAULT_PROJECT_ID][unitCurrency][pricingCurrency]
|
|
131
|
-
});
|
|
132
|
-
}
|
|
117
|
+
if (feed == IJBPriceFeed(address(0))) revert JBPrices_ZeroPriceFeed();
|
|
133
118
|
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
)
|
|
139
|
-
revert JBPrices_PriceFeedAlreadyExists({
|
|
140
|
-
feed: priceFeedFor[projectId][pricingCurrency][unitCurrency] != IJBPriceFeed(address(0))
|
|
141
|
-
? priceFeedFor[projectId][pricingCurrency][unitCurrency]
|
|
142
|
-
: priceFeedFor[projectId][unitCurrency][pricingCurrency]
|
|
143
|
-
});
|
|
144
|
-
}
|
|
119
|
+
// Only exact-direction duplicates are rejected. Opposite-direction feeds can coexist and are used when deriving
|
|
120
|
+
// inverse prices.
|
|
121
|
+
_requireNewPriceFeed({
|
|
122
|
+
projectId: projectId, pricingCurrency: pricingCurrency, unitCurrency: unitCurrency, feed: feed
|
|
123
|
+
});
|
|
145
124
|
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
// alternative currency pairs. A default feed for a currency pair prevents per-project overrides to ensure
|
|
149
|
-
// price consistency; projects should use unused currency IDs for custom pricing.
|
|
150
|
-
// Store the feed.
|
|
151
|
-
priceFeedFor[projectId][pricingCurrency][unitCurrency] = feed;
|
|
125
|
+
// Keep existing feeds immutable: appending preserves the primary feed and adds this feed as the last fallback.
|
|
126
|
+
_priceFeedsFor[projectId][pricingCurrency][unitCurrency].push(feed);
|
|
152
127
|
|
|
153
128
|
emit AddPriceFeed({
|
|
154
129
|
projectId: projectId,
|
|
@@ -159,21 +134,86 @@ contract JBPrices is JBControlled, JBPermissioned, ERC2771Context, Ownable, IJBP
|
|
|
159
134
|
});
|
|
160
135
|
}
|
|
161
136
|
|
|
137
|
+
//*********************************************************************//
|
|
138
|
+
// ----------------------- external views ---------------------------- //
|
|
139
|
+
//*********************************************************************//
|
|
140
|
+
|
|
141
|
+
/// @notice Returns the feed stored at an exact pair's index.
|
|
142
|
+
/// @dev This view does not apply inverse or project-default fallback lookup. It reverts with Solidity's default
|
|
143
|
+
/// array bounds check if `index` is not configured.
|
|
144
|
+
/// @param projectId The ID of the project whose feed should be returned.
|
|
145
|
+
/// @param pricingCurrency The currency that the feed's returned price is denominated in.
|
|
146
|
+
/// @param unitCurrency The currency whose unit is priced by the feed.
|
|
147
|
+
/// @param index The index of the feed to return.
|
|
148
|
+
/// @return feed The configured price feed for the exact pair at `index`.
|
|
149
|
+
function priceFeedAt(
|
|
150
|
+
uint256 projectId,
|
|
151
|
+
uint256 pricingCurrency,
|
|
152
|
+
uint256 unitCurrency,
|
|
153
|
+
uint256 index
|
|
154
|
+
)
|
|
155
|
+
external
|
|
156
|
+
view
|
|
157
|
+
override
|
|
158
|
+
returns (IJBPriceFeed feed)
|
|
159
|
+
{
|
|
160
|
+
return _priceFeedsFor[projectId][pricingCurrency][unitCurrency][index];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/// @notice Returns the number of feeds configured for an exact currency pair.
|
|
164
|
+
/// @dev This count does not include feeds configured for the inverse direction or project ID 0 defaults.
|
|
165
|
+
/// @param projectId The ID of the project whose feed count should be returned.
|
|
166
|
+
/// @param pricingCurrency The currency that the feeds' returned prices are denominated in.
|
|
167
|
+
/// @param unitCurrency The currency whose unit is priced by the feeds.
|
|
168
|
+
/// @return count The number of configured price feeds for the exact pair.
|
|
169
|
+
function priceFeedCountFor(
|
|
170
|
+
uint256 projectId,
|
|
171
|
+
uint256 pricingCurrency,
|
|
172
|
+
uint256 unitCurrency
|
|
173
|
+
)
|
|
174
|
+
external
|
|
175
|
+
view
|
|
176
|
+
override
|
|
177
|
+
returns (uint256 count)
|
|
178
|
+
{
|
|
179
|
+
return _priceFeedsFor[projectId][pricingCurrency][unitCurrency].length;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/// @notice Returns the primary feed for an exact currency pair, or zero if none is configured.
|
|
183
|
+
/// @dev This view does not apply inverse or project-default fallback lookup. Use `pricePerUnitOf` to resolve a
|
|
184
|
+
/// usable price through the full backup path.
|
|
185
|
+
/// @param projectId The ID of the project whose primary feed should be returned.
|
|
186
|
+
/// @param pricingCurrency The currency that the feed's returned price is denominated in.
|
|
187
|
+
/// @param unitCurrency The currency whose unit is priced by the feed.
|
|
188
|
+
/// @return feed The first configured price feed for the exact pair, or the zero address if none exists.
|
|
189
|
+
function priceFeedFor(
|
|
190
|
+
uint256 projectId,
|
|
191
|
+
uint256 pricingCurrency,
|
|
192
|
+
uint256 unitCurrency
|
|
193
|
+
)
|
|
194
|
+
external
|
|
195
|
+
view
|
|
196
|
+
override
|
|
197
|
+
returns (IJBPriceFeed feed)
|
|
198
|
+
{
|
|
199
|
+
return _priceFeedsFor[projectId][pricingCurrency][unitCurrency].length == 0
|
|
200
|
+
? IJBPriceFeed(address(0))
|
|
201
|
+
: _priceFeedsFor[projectId][pricingCurrency][unitCurrency][0];
|
|
202
|
+
}
|
|
203
|
+
|
|
162
204
|
//*********************************************************************//
|
|
163
205
|
// -------------------------- public views --------------------------- //
|
|
164
206
|
//*********************************************************************//
|
|
165
207
|
|
|
166
|
-
/// @notice
|
|
167
|
-
///
|
|
168
|
-
///
|
|
169
|
-
///
|
|
170
|
-
///
|
|
171
|
-
/// @param
|
|
172
|
-
/// @param
|
|
173
|
-
/// @param
|
|
174
|
-
/// @
|
|
175
|
-
/// @return The `pricingCurrency` price of 1 `unitCurrency`, as a fixed point number with the specified number of
|
|
176
|
-
/// decimals.
|
|
208
|
+
/// @notice Returns the price of one `unitCurrency` unit denominated in `pricingCurrency`.
|
|
209
|
+
/// @dev Lookup order is project direct feeds, project inverse feeds, default direct feeds, then default inverse
|
|
210
|
+
/// feeds. Each feed list is tried in registration order, skipping feeds that revert or return zero. Reverts with
|
|
211
|
+
/// `JBPrices_PriceFeedNotFound` if no configured feed in that lookup path returns a non-zero price.
|
|
212
|
+
/// @param projectId The ID of the project to check first. Project ID 0 feeds are used as defaults.
|
|
213
|
+
/// @param pricingCurrency The currency that the returned price is denominated in.
|
|
214
|
+
/// @param unitCurrency The currency whose unit is being priced.
|
|
215
|
+
/// @param decimals The number of decimals the returned fixed point price should use.
|
|
216
|
+
/// @return price The `pricingCurrency` price of one `unitCurrency`, using `decimals` fixed point precision.
|
|
177
217
|
function pricePerUnitOf(
|
|
178
218
|
uint256 projectId,
|
|
179
219
|
uint256 pricingCurrency,
|
|
@@ -183,71 +223,84 @@ contract JBPrices is JBControlled, JBPermissioned, ERC2771Context, Ownable, IJBP
|
|
|
183
223
|
public
|
|
184
224
|
view
|
|
185
225
|
override
|
|
186
|
-
returns (uint256)
|
|
226
|
+
returns (uint256 price)
|
|
187
227
|
{
|
|
188
|
-
//
|
|
189
|
-
// desired number of decimals.
|
|
228
|
+
// Same-currency conversions are always 1 in the requested fixed-point precision.
|
|
190
229
|
if (pricingCurrency == unitCurrency) return 10 ** decimals;
|
|
191
230
|
|
|
192
|
-
|
|
193
|
-
IJBPriceFeed feed = priceFeedFor[projectId][pricingCurrency][unitCurrency];
|
|
231
|
+
bool found;
|
|
194
232
|
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
projectId: projectId, pricingCurrency: pricingCurrency, unitCurrency: unitCurrency, feed: feed
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
return price;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Try getting the inverse feed.
|
|
207
|
-
feed = priceFeedFor[projectId][unitCurrency][pricingCurrency];
|
|
208
|
-
|
|
209
|
-
// If it exists, return the inverse of its price.
|
|
210
|
-
// @dev The inverse calculation `(10^d * 10^d) / price` has acceptable precision when the feed price
|
|
211
|
-
// is in the range of ~1e9 to ~1e27 (for 18 decimals). Extreme prices outside this range may lose
|
|
212
|
-
// significant precision due to fixed-point division truncation.
|
|
213
|
-
if (feed != IJBPriceFeed(address(0))) {
|
|
214
|
-
uint256 inversePrice = feed.currentUnitPrice(decimals);
|
|
215
|
-
if (inversePrice == 0) {
|
|
216
|
-
revert JBPrices_ZeroPrice({
|
|
217
|
-
projectId: projectId, pricingCurrency: unitCurrency, unitCurrency: pricingCurrency, feed: feed
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
return mulDiv({x: 10 ** decimals, y: 10 ** decimals, denominator: inversePrice});
|
|
221
|
-
}
|
|
233
|
+
// Project-specific feeds take priority over defaults, including their configured backups.
|
|
234
|
+
(price, found) = _pricePerUnitOf({
|
|
235
|
+
projectId: projectId, pricingCurrency: pricingCurrency, unitCurrency: unitCurrency, decimals: decimals
|
|
236
|
+
});
|
|
237
|
+
if (found) return price;
|
|
222
238
|
|
|
223
|
-
// Check for a default feed (project ID 0) if not found.
|
|
224
239
|
if (projectId != DEFAULT_PROJECT_ID) {
|
|
225
|
-
|
|
240
|
+
// Project 0 feeds are the shared defaults. Avoid checking them twice when project ID 0 was requested.
|
|
241
|
+
(price, found) = _pricePerUnitOf({
|
|
226
242
|
projectId: DEFAULT_PROJECT_ID,
|
|
227
243
|
pricingCurrency: pricingCurrency,
|
|
228
244
|
unitCurrency: unitCurrency,
|
|
229
245
|
decimals: decimals
|
|
230
246
|
});
|
|
247
|
+
if (found) return price;
|
|
231
248
|
}
|
|
232
249
|
|
|
233
|
-
// No price feed available, revert.
|
|
234
250
|
revert JBPrices_PriceFeedNotFound({
|
|
235
251
|
projectId: projectId, pricingCurrency: pricingCurrency, unitCurrency: unitCurrency
|
|
236
252
|
});
|
|
237
253
|
}
|
|
238
254
|
|
|
239
255
|
//*********************************************************************//
|
|
240
|
-
//
|
|
256
|
+
// ----------------------- internal helpers -------------------------- //
|
|
241
257
|
//*********************************************************************//
|
|
242
258
|
|
|
259
|
+
/// @notice Reverts if `feed` is already configured for an exact currency pair.
|
|
260
|
+
/// @param projectId The ID of the project whose pair should be checked.
|
|
261
|
+
/// @param pricingCurrency The currency that the feed's returned price is denominated in.
|
|
262
|
+
/// @param unitCurrency The currency whose unit is priced by the feed.
|
|
263
|
+
/// @param feed The price feed to check.
|
|
264
|
+
function _requireNewPriceFeed(
|
|
265
|
+
uint256 projectId,
|
|
266
|
+
uint256 pricingCurrency,
|
|
267
|
+
uint256 unitCurrency,
|
|
268
|
+
IJBPriceFeed feed
|
|
269
|
+
)
|
|
270
|
+
internal
|
|
271
|
+
view
|
|
272
|
+
{
|
|
273
|
+
IJBPriceFeed[] storage feeds = _priceFeedsFor[projectId][pricingCurrency][unitCurrency];
|
|
274
|
+
uint256 numberOfFeeds = feeds.length;
|
|
275
|
+
|
|
276
|
+
for (uint256 i; i < numberOfFeeds;) {
|
|
277
|
+
if (feeds[i] == feed) revert JBPrices_PriceFeedAlreadyAdded({feed: feed});
|
|
278
|
+
|
|
279
|
+
unchecked {
|
|
280
|
+
++i;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
//*********************************************************************//
|
|
286
|
+
// ----------------------- internal views ---------------------------- //
|
|
287
|
+
//*********************************************************************//
|
|
288
|
+
|
|
289
|
+
/// @notice Returns the ERC-2771 context suffix length.
|
|
243
290
|
/// @dev `ERC-2771` specifies the context as being a single address (20 bytes).
|
|
244
|
-
|
|
291
|
+
/// @return contextSuffixLength The context suffix length.
|
|
292
|
+
function _contextSuffixLength()
|
|
293
|
+
internal
|
|
294
|
+
view
|
|
295
|
+
override(ERC2771Context, Context)
|
|
296
|
+
returns (uint256 contextSuffixLength)
|
|
297
|
+
{
|
|
245
298
|
return super._contextSuffixLength();
|
|
246
299
|
}
|
|
247
300
|
|
|
248
301
|
/// @notice The calldata. Preferred to use over `msg.data`.
|
|
249
|
-
/// @return
|
|
250
|
-
function _msgData() internal view override(ERC2771Context, Context) returns (bytes calldata) {
|
|
302
|
+
/// @return data The `msg.data` of this call.
|
|
303
|
+
function _msgData() internal view override(ERC2771Context, Context) returns (bytes calldata data) {
|
|
251
304
|
return ERC2771Context._msgData();
|
|
252
305
|
}
|
|
253
306
|
|
|
@@ -256,4 +309,89 @@ contract JBPrices is JBControlled, JBPermissioned, ERC2771Context, Ownable, IJBP
|
|
|
256
309
|
function _msgSender() internal view override(ERC2771Context, Context) returns (address sender) {
|
|
257
310
|
return ERC2771Context._msgSender();
|
|
258
311
|
}
|
|
312
|
+
|
|
313
|
+
/// @notice Returns the first non-zero direct price from `feeds`.
|
|
314
|
+
/// @dev Feeds are tried in registration order. A feed that reverts or returns zero is treated as unavailable.
|
|
315
|
+
/// @param feeds The direct price feeds to try.
|
|
316
|
+
/// @param decimals The number of decimals the returned fixed point price should use.
|
|
317
|
+
/// @return price The first non-zero price returned by the feeds.
|
|
318
|
+
/// @return found Whether a usable price was found.
|
|
319
|
+
function _priceFrom(
|
|
320
|
+
IJBPriceFeed[] storage feeds,
|
|
321
|
+
uint256 decimals
|
|
322
|
+
)
|
|
323
|
+
internal
|
|
324
|
+
view
|
|
325
|
+
returns (uint256 price, bool found)
|
|
326
|
+
{
|
|
327
|
+
uint256 numberOfFeeds = feeds.length;
|
|
328
|
+
for (uint256 i; i < numberOfFeeds;) {
|
|
329
|
+
// Try each feed independently so one unavailable oracle does not block later backups.
|
|
330
|
+
try feeds[i].currentUnitPrice(decimals) returns (uint256 returnedPrice) {
|
|
331
|
+
if (returnedPrice != 0) return (returnedPrice, true);
|
|
332
|
+
} catch {}
|
|
333
|
+
|
|
334
|
+
unchecked {
|
|
335
|
+
++i;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/// @notice Returns the first non-zero inverse price from `feeds`.
|
|
341
|
+
/// @dev Feeds are tried in registration order. A feed that reverts, returns zero, or inverts to zero at the
|
|
342
|
+
/// requested precision is treated as unavailable.
|
|
343
|
+
/// @param feeds The opposite-direction price feeds to invert.
|
|
344
|
+
/// @param decimals The number of decimals the returned fixed point price should use.
|
|
345
|
+
/// @return price The first non-zero inverse price returned by the feeds.
|
|
346
|
+
/// @return found Whether a usable inverse price was found.
|
|
347
|
+
function _priceFromInverse(
|
|
348
|
+
IJBPriceFeed[] storage feeds,
|
|
349
|
+
uint256 decimals
|
|
350
|
+
)
|
|
351
|
+
internal
|
|
352
|
+
view
|
|
353
|
+
returns (uint256 price, bool found)
|
|
354
|
+
{
|
|
355
|
+
uint256 numberOfFeeds = feeds.length;
|
|
356
|
+
for (uint256 i; i < numberOfFeeds;) {
|
|
357
|
+
// Each opposite-direction feed is optional; continue to backups if it cannot produce a usable price.
|
|
358
|
+
try feeds[i].currentUnitPrice(decimals) returns (uint256 inversePrice) {
|
|
359
|
+
if (inversePrice != 0) {
|
|
360
|
+
// Convert "unit per pricing" into "pricing per unit" using the same fixed-point precision.
|
|
361
|
+
uint256 invertedPrice = mulDiv({x: 10 ** decimals, y: 10 ** decimals, denominator: inversePrice});
|
|
362
|
+
if (invertedPrice != 0) return (invertedPrice, true);
|
|
363
|
+
}
|
|
364
|
+
} catch {}
|
|
365
|
+
|
|
366
|
+
unchecked {
|
|
367
|
+
++i;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/// @notice Returns a non-zero price from a project's direct or inverse feeds.
|
|
373
|
+
/// @dev Direct feeds are preferred over inverse feeds for the same project and pair.
|
|
374
|
+
/// @param projectId The ID of the project whose feeds should be checked.
|
|
375
|
+
/// @param pricingCurrency The currency that the returned price should be denominated in.
|
|
376
|
+
/// @param unitCurrency The currency whose unit is being priced.
|
|
377
|
+
/// @param decimals The number of decimals the returned fixed point price should use.
|
|
378
|
+
/// @return price The first usable direct or inverse price.
|
|
379
|
+
/// @return found Whether a usable price was found.
|
|
380
|
+
function _pricePerUnitOf(
|
|
381
|
+
uint256 projectId,
|
|
382
|
+
uint256 pricingCurrency,
|
|
383
|
+
uint256 unitCurrency,
|
|
384
|
+
uint256 decimals
|
|
385
|
+
)
|
|
386
|
+
internal
|
|
387
|
+
view
|
|
388
|
+
returns (uint256 price, bool found)
|
|
389
|
+
{
|
|
390
|
+
(price, found) =
|
|
391
|
+
_priceFrom({feeds: _priceFeedsFor[projectId][pricingCurrency][unitCurrency], decimals: decimals});
|
|
392
|
+
if (found) return (price, true);
|
|
393
|
+
|
|
394
|
+
(price, found) =
|
|
395
|
+
_priceFromInverse({feeds: _priceFeedsFor[projectId][unitCurrency][pricingCurrency], decimals: decimals});
|
|
396
|
+
}
|
|
259
397
|
}
|
package/src/JBProjects.sol
CHANGED
|
@@ -4,6 +4,7 @@ pragma solidity 0.8.28;
|
|
|
4
4
|
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
5
5
|
import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
|
|
6
6
|
import {ERC721, Context} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
|
7
|
+
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
|
|
7
8
|
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
8
9
|
|
|
9
10
|
import {IJBProjects} from "./interfaces/IJBProjects.sol";
|
|
@@ -13,10 +14,23 @@ import {IJBTokenUriResolver} from "./interfaces/IJBTokenUriResolver.sol";
|
|
|
13
14
|
/// rulesets, terminals, and permissions. Projects are created with `createFor` and the resulting token ID is used as
|
|
14
15
|
/// the project's ID across the entire protocol.
|
|
15
16
|
contract JBProjects is ERC721, ERC2771Context, Ownable, IJBProjects {
|
|
17
|
+
//*********************************************************************//
|
|
18
|
+
// --------------------------- custom errors ------------------------- //
|
|
19
|
+
//*********************************************************************//
|
|
20
|
+
|
|
21
|
+
error JBProjects_InvalidCreationFee(uint256 value, uint256 requiredFee);
|
|
22
|
+
error JBProjects_ZeroCreationFeeReceiver();
|
|
23
|
+
|
|
16
24
|
//*********************************************************************//
|
|
17
25
|
// --------------------- public stored properties -------------------- //
|
|
18
26
|
//*********************************************************************//
|
|
19
27
|
|
|
28
|
+
/// @notice The native-token fee required to create a project.
|
|
29
|
+
uint256 public override creationFee;
|
|
30
|
+
|
|
31
|
+
/// @notice The address that receives project creation fees.
|
|
32
|
+
address payable public override creationFeeReceiver;
|
|
33
|
+
|
|
20
34
|
/// @notice The number of projects that have been created using this contract.
|
|
21
35
|
/// @dev The count is incremented with each new project created.
|
|
22
36
|
/// @dev The resulting ERC-721 token ID for each project is the newly incremented count value.
|
|
@@ -51,6 +65,21 @@ contract JBProjects is ERC721, ERC2771Context, Ownable, IJBProjects {
|
|
|
51
65
|
// ---------------------- external transactions ---------------------- //
|
|
52
66
|
//*********************************************************************//
|
|
53
67
|
|
|
68
|
+
/// @notice Set the native-token fee required to create a project and the address that receives it.
|
|
69
|
+
/// @dev Only this contract's owner can change the fee. A non-zero fee requires a non-zero receiver.
|
|
70
|
+
/// @param fee The required creation fee. Set to 0 to disable creation fees.
|
|
71
|
+
/// @param receiver The address that receives project creation fees.
|
|
72
|
+
function setCreationFee(uint256 fee, address payable receiver) external override onlyOwner {
|
|
73
|
+
// Non-zero fees need somewhere to go.
|
|
74
|
+
if (fee != 0 && receiver == address(0)) revert JBProjects_ZeroCreationFeeReceiver();
|
|
75
|
+
|
|
76
|
+
// Store the fee configuration.
|
|
77
|
+
creationFee = fee;
|
|
78
|
+
creationFeeReceiver = receiver;
|
|
79
|
+
|
|
80
|
+
emit SetCreationFee({fee: fee, receiver: receiver, caller: _msgSender()});
|
|
81
|
+
}
|
|
82
|
+
|
|
54
83
|
/// @notice Set the contract that resolves project NFT metadata (the `tokenURI`). This controls what artwork and
|
|
55
84
|
/// JSON metadata is returned for each project's ERC-721 token.
|
|
56
85
|
/// @dev Only this contract's owner can change the resolver.
|
|
@@ -68,9 +97,14 @@ contract JBProjects is ERC721, ERC2771Context, Ownable, IJBProjects {
|
|
|
68
97
|
|
|
69
98
|
/// @notice Create a new project for the specified owner, which mints an NFT (ERC-721) into their wallet.
|
|
70
99
|
/// @dev Anyone can create a project on an owner's behalf.
|
|
100
|
+
/// @dev Requires exactly `creationFee` native tokens. The fee is forwarded after the project NFT is minted.
|
|
71
101
|
/// @param owner The address that will be the owner of the project.
|
|
72
102
|
/// @return projectId The token ID of the newly created project.
|
|
73
|
-
function createFor(address owner) public override returns (uint256 projectId) {
|
|
103
|
+
function createFor(address owner) public payable override returns (uint256 projectId) {
|
|
104
|
+
// Keep a reference to the fee. It must be paid exactly to avoid accidental overpayment.
|
|
105
|
+
uint256 fee = creationFee;
|
|
106
|
+
if (msg.value != fee) revert JBProjects_InvalidCreationFee({value: msg.value, requiredFee: fee});
|
|
107
|
+
|
|
74
108
|
// Increment the count, which will be used as the ID.
|
|
75
109
|
projectId = ++count;
|
|
76
110
|
|
|
@@ -78,6 +112,13 @@ contract JBProjects is ERC721, ERC2771Context, Ownable, IJBProjects {
|
|
|
78
112
|
|
|
79
113
|
// Mint the project.
|
|
80
114
|
_safeMint({to: owner, tokenId: projectId});
|
|
115
|
+
|
|
116
|
+
// Forward the fee if one is configured.
|
|
117
|
+
if (fee != 0) {
|
|
118
|
+
address payable receiver = creationFeeReceiver;
|
|
119
|
+
if (receiver == address(0)) revert JBProjects_ZeroCreationFeeReceiver();
|
|
120
|
+
Address.sendValue({recipient: receiver, amount: fee});
|
|
121
|
+
}
|
|
81
122
|
}
|
|
82
123
|
|
|
83
124
|
//*********************************************************************//
|
|
@@ -306,6 +306,7 @@ interface IJBController is IERC165, IJBProjectUriRegistry, IJBDirectoryAccessCon
|
|
|
306
306
|
string calldata memo
|
|
307
307
|
)
|
|
308
308
|
external
|
|
309
|
+
payable
|
|
309
310
|
returns (uint256 projectId);
|
|
310
311
|
|
|
311
312
|
/// @notice Queues a project's initial rulesets and sets up terminals for it.
|
|
@@ -4,14 +4,12 @@ pragma solidity ^0.8.0;
|
|
|
4
4
|
import {IJBPriceFeed} from "./IJBPriceFeed.sol";
|
|
5
5
|
import {IJBProjects} from "./IJBProjects.sol";
|
|
6
6
|
|
|
7
|
-
/// @notice
|
|
8
|
-
/// (typically Chainlink). Used when payout limits or surplus allowances are denominated in a different currency than
|
|
9
|
-
/// the token held in the terminal.
|
|
7
|
+
/// @notice Resolves protocol currency conversions from append-only project and default price feeds.
|
|
10
8
|
interface IJBPrices {
|
|
11
|
-
/// @notice
|
|
12
|
-
/// @param projectId The ID of the project the price feed was added for.
|
|
13
|
-
/// @param pricingCurrency The currency the feed's
|
|
14
|
-
/// @param unitCurrency The currency the feed
|
|
9
|
+
/// @notice Emitted when a price feed is added for an exact currency pair.
|
|
10
|
+
/// @param projectId The ID of the project the price feed was added for. Project ID 0 stores protocol defaults.
|
|
11
|
+
/// @param pricingCurrency The currency that the feed's returned price is denominated in.
|
|
12
|
+
/// @param unitCurrency The currency whose unit is priced by the feed.
|
|
15
13
|
/// @param feed The price feed that was added.
|
|
16
14
|
/// @param caller The address that added the price feed.
|
|
17
15
|
event AddPriceFeed(
|
|
@@ -22,17 +20,54 @@ interface IJBPrices {
|
|
|
22
20
|
address caller
|
|
23
21
|
);
|
|
24
22
|
|
|
25
|
-
/// @notice The project ID used
|
|
26
|
-
|
|
23
|
+
/// @notice The project ID used to store protocol default price feeds.
|
|
24
|
+
/// @return projectId The project ID used to store protocol defaults.
|
|
25
|
+
function DEFAULT_PROJECT_ID() external view returns (uint256 projectId);
|
|
27
26
|
|
|
28
27
|
/// @notice Mints ERC-721s that represent project ownership and transfers.
|
|
29
|
-
|
|
28
|
+
/// @return projects The project NFT contract.
|
|
29
|
+
function PROJECTS() external view returns (IJBProjects projects);
|
|
30
30
|
|
|
31
|
-
/// @notice Returns the
|
|
32
|
-
/// @
|
|
33
|
-
///
|
|
34
|
-
/// @param
|
|
35
|
-
/// @
|
|
31
|
+
/// @notice Returns the feed stored at an exact pair's index.
|
|
32
|
+
/// @dev This view does not apply inverse or project-default fallback lookup. It reverts with Solidity's default
|
|
33
|
+
/// array bounds check if `index` is not configured.
|
|
34
|
+
/// @param projectId The ID of the project whose feed should be returned.
|
|
35
|
+
/// @param pricingCurrency The currency that the feed's returned price is denominated in.
|
|
36
|
+
/// @param unitCurrency The currency whose unit is priced by the feed.
|
|
37
|
+
/// @param index The index of the feed to return.
|
|
38
|
+
/// @return feed The configured price feed for the exact pair at `index`.
|
|
39
|
+
function priceFeedAt(
|
|
40
|
+
uint256 projectId,
|
|
41
|
+
uint256 pricingCurrency,
|
|
42
|
+
uint256 unitCurrency,
|
|
43
|
+
uint256 index
|
|
44
|
+
)
|
|
45
|
+
external
|
|
46
|
+
view
|
|
47
|
+
returns (IJBPriceFeed);
|
|
48
|
+
|
|
49
|
+
/// @notice Returns the number of feeds configured for an exact currency pair.
|
|
50
|
+
/// @dev This count does not include feeds configured for the inverse direction or project ID 0 defaults.
|
|
51
|
+
/// @param projectId The ID of the project whose feed count should be returned.
|
|
52
|
+
/// @param pricingCurrency The currency that the feeds' returned prices are denominated in.
|
|
53
|
+
/// @param unitCurrency The currency whose unit is priced by the feeds.
|
|
54
|
+
/// @return count The number of configured price feeds for the exact pair.
|
|
55
|
+
function priceFeedCountFor(
|
|
56
|
+
uint256 projectId,
|
|
57
|
+
uint256 pricingCurrency,
|
|
58
|
+
uint256 unitCurrency
|
|
59
|
+
)
|
|
60
|
+
external
|
|
61
|
+
view
|
|
62
|
+
returns (uint256 count);
|
|
63
|
+
|
|
64
|
+
/// @notice Returns the primary feed for an exact currency pair, or zero if none is configured.
|
|
65
|
+
/// @dev This view only returns the stored primary feed address. It does not call the feed, skip unavailable feeds,
|
|
66
|
+
/// derive inverse feeds, or fall back to project ID 0 defaults.
|
|
67
|
+
/// @param projectId The ID of the project whose primary feed should be returned.
|
|
68
|
+
/// @param pricingCurrency The currency that the feed's returned price is denominated in.
|
|
69
|
+
/// @param unitCurrency The currency whose unit is priced by the feed.
|
|
70
|
+
/// @return feed The first configured price feed for the exact pair, or the zero address if none exists.
|
|
36
71
|
function priceFeedFor(
|
|
37
72
|
uint256 projectId,
|
|
38
73
|
uint256 pricingCurrency,
|
|
@@ -40,14 +75,16 @@ interface IJBPrices {
|
|
|
40
75
|
)
|
|
41
76
|
external
|
|
42
77
|
view
|
|
43
|
-
returns (IJBPriceFeed);
|
|
78
|
+
returns (IJBPriceFeed feed);
|
|
44
79
|
|
|
45
|
-
/// @notice Returns the
|
|
46
|
-
/// @
|
|
47
|
-
///
|
|
48
|
-
/// @param
|
|
49
|
-
/// @param
|
|
50
|
-
/// @
|
|
80
|
+
/// @notice Returns the price of one `unitCurrency` unit denominated in `pricingCurrency`.
|
|
81
|
+
/// @dev Lookup order is project direct feeds, project inverse feeds, default direct feeds, then default inverse
|
|
82
|
+
/// feeds. Each feed list is tried in registration order, skipping feeds that revert or return zero.
|
|
83
|
+
/// @param projectId The ID of the project to check first. Project ID 0 feeds are used as defaults.
|
|
84
|
+
/// @param pricingCurrency The currency that the returned price is denominated in.
|
|
85
|
+
/// @param unitCurrency The currency whose unit is being priced.
|
|
86
|
+
/// @param decimals The number of decimals the returned fixed point price should use.
|
|
87
|
+
/// @return price The `pricingCurrency` price of one `unitCurrency`, using `decimals` fixed point precision.
|
|
51
88
|
function pricePerUnitOf(
|
|
52
89
|
uint256 projectId,
|
|
53
90
|
uint256 pricingCurrency,
|
|
@@ -56,12 +93,14 @@ interface IJBPrices {
|
|
|
56
93
|
)
|
|
57
94
|
external
|
|
58
95
|
view
|
|
59
|
-
returns (uint256);
|
|
96
|
+
returns (uint256 price);
|
|
60
97
|
|
|
61
|
-
/// @notice Adds
|
|
62
|
-
/// @
|
|
63
|
-
///
|
|
64
|
-
/// @param
|
|
98
|
+
/// @notice Adds an append-only price feed for a project's exact currency pair.
|
|
99
|
+
/// @dev Project ID 0 stores protocol defaults and can only be configured by this contract's owner. Non-zero
|
|
100
|
+
/// project IDs can only be configured by that project's controller.
|
|
101
|
+
/// @param projectId The ID of the project to add the feed for, or 0 to add a protocol default.
|
|
102
|
+
/// @param pricingCurrency The currency that the feed's returned price is denominated in.
|
|
103
|
+
/// @param unitCurrency The currency whose unit is priced by the feed.
|
|
65
104
|
/// @param feed The price feed to add.
|
|
66
105
|
function addPriceFeedFor(
|
|
67
106
|
uint256 projectId,
|
|
@@ -13,11 +13,23 @@ interface IJBProjects is IERC721 {
|
|
|
13
13
|
/// @param caller The address that created the project.
|
|
14
14
|
event Create(uint256 indexed projectId, address indexed owner, address caller);
|
|
15
15
|
|
|
16
|
+
/// @notice The native-token fee required to create a project was set.
|
|
17
|
+
/// @param fee The required creation fee.
|
|
18
|
+
/// @param receiver The address that receives the fee.
|
|
19
|
+
/// @param caller The address that set the fee.
|
|
20
|
+
event SetCreationFee(uint256 fee, address payable receiver, address caller);
|
|
21
|
+
|
|
16
22
|
/// @notice The token URI resolver was set.
|
|
17
23
|
/// @param resolver The new token URI resolver.
|
|
18
24
|
/// @param caller The address that set the resolver.
|
|
19
25
|
event SetTokenUriResolver(IJBTokenUriResolver indexed resolver, address caller);
|
|
20
26
|
|
|
27
|
+
/// @notice Returns the native-token fee required to create a project.
|
|
28
|
+
function creationFee() external view returns (uint256);
|
|
29
|
+
|
|
30
|
+
/// @notice Returns the address that receives project creation fees.
|
|
31
|
+
function creationFeeReceiver() external view returns (address payable);
|
|
32
|
+
|
|
21
33
|
/// @notice Returns the total number of projects that have been created.
|
|
22
34
|
function count() external view returns (uint256);
|
|
23
35
|
|
|
@@ -27,7 +39,12 @@ interface IJBProjects is IERC721 {
|
|
|
27
39
|
/// @notice Creates a new project and mints the project's ERC-721 to the specified owner.
|
|
28
40
|
/// @param owner The address that will own the new project's ERC-721.
|
|
29
41
|
/// @return projectId The ID of the newly created project.
|
|
30
|
-
function createFor(address owner) external returns (uint256 projectId);
|
|
42
|
+
function createFor(address owner) external payable returns (uint256 projectId);
|
|
43
|
+
|
|
44
|
+
/// @notice Sets the native-token fee required to create a project and the address that receives it.
|
|
45
|
+
/// @param fee The required creation fee. Set to 0 to disable creation fees.
|
|
46
|
+
/// @param receiver The address that receives creation fees. Must be non-zero when `fee` is non-zero.
|
|
47
|
+
function setCreationFee(uint256 fee, address payable receiver) external;
|
|
31
48
|
|
|
32
49
|
/// @notice Sets the token URI resolver used to retrieve project token URIs.
|
|
33
50
|
/// @param resolver The new token URI resolver.
|