@rev-net/core-v6 0.0.66 → 0.0.68
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/references/operations.md +36 -17
- package/references/runtime.md +6 -5
- package/script/Deploy.s.sol +6 -3
- package/src/REVDeployer.sol +125 -285
- package/src/REVOwner.sol +325 -24
- package/src/interfaces/IREVDeployer.sol +0 -50
- package/src/interfaces/IREVOwner.sol +117 -6
- package/src/structs/REVOwnerAutoIssuance.sol +14 -0
- package/src/structs/REVOwnerExtraGrant.sol +12 -0
- package/src/structs/REVOwnerRevnetInit.sol +29 -0
package/src/REVOwner.sol
CHANGED
|
@@ -4,7 +4,10 @@ pragma solidity 0.8.28;
|
|
|
4
4
|
import {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHook.sol";
|
|
5
5
|
import {IJBBuybackHookRegistry} from "@bananapus/buyback-hook-v6/src/interfaces/IJBBuybackHookRegistry.sol";
|
|
6
6
|
import {IJBCashOutHook} from "@bananapus/core-v6/src/interfaces/IJBCashOutHook.sol";
|
|
7
|
+
import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
7
8
|
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
9
|
+
import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
10
|
+
import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
|
|
8
11
|
import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDataHook.sol";
|
|
9
12
|
import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
|
|
10
13
|
import {JBCashOuts} from "@bananapus/core-v6/src/libraries/JBCashOuts.sol";
|
|
@@ -16,16 +19,24 @@ import {JBBeforeCashOutRecordedContext} from "@bananapus/core-v6/src/structs/JBB
|
|
|
16
19
|
import {JBBeforePayRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforePayRecordedContext.sol";
|
|
17
20
|
import {JBCashOutHookSpecification} from "@bananapus/core-v6/src/structs/JBCashOutHookSpecification.sol";
|
|
18
21
|
import {JBPayHookSpecification} from "@bananapus/core-v6/src/structs/JBPayHookSpecification.sol";
|
|
22
|
+
import {JBPermissionsData} from "@bananapus/core-v6/src/structs/JBPermissionsData.sol";
|
|
19
23
|
import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
|
|
24
|
+
import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
|
|
20
25
|
import {IJBPeerChainAdjustedAccounts} from "@bananapus/suckers-v6/src/interfaces/IJBPeerChainAdjustedAccounts.sol";
|
|
21
26
|
import {IJBSuckerRegistry} from "@bananapus/suckers-v6/src/interfaces/IJBSuckerRegistry.sol";
|
|
22
27
|
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
23
28
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
29
|
+
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
30
|
+
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
|
|
24
31
|
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
25
32
|
import {mulDiv} from "@prb/math/src/Common.sol";
|
|
26
33
|
|
|
27
34
|
import {IREVDeployer} from "./interfaces/IREVDeployer.sol";
|
|
28
35
|
import {IREVLoans} from "./interfaces/IREVLoans.sol";
|
|
36
|
+
import {IREVOwner} from "./interfaces/IREVOwner.sol";
|
|
37
|
+
import {REVOwnerAutoIssuance} from "./structs/REVOwnerAutoIssuance.sol";
|
|
38
|
+
import {REVOwnerExtraGrant} from "./structs/REVOwnerExtraGrant.sol";
|
|
39
|
+
import {REVOwnerRevnetInit} from "./structs/REVOwnerRevnetInit.sol";
|
|
29
40
|
|
|
30
41
|
/// @notice The runtime hook for all revnets — set as every revnet's `dataHook` in ruleset metadata. At pay time, it
|
|
31
42
|
/// coordinates the 721 hook (NFT tier minting) with the buyback hook (secondary market swap routing) and scales weight
|
|
@@ -34,7 +45,7 @@ import {IREVLoans} from "./interfaces/IREVLoans.sol";
|
|
|
34
45
|
/// proceeds to the fee revnet via `afterCashOutRecordedWith`.
|
|
35
46
|
/// @dev Separated from `REVDeployer` to stay within the EIP-170 contract size limit. Also implements
|
|
36
47
|
/// `IJBPeerChainAdjustedAccounts` to expose loan state to peer-chain supply/surplus snapshots.
|
|
37
|
-
contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAccounts {
|
|
48
|
+
contract REVOwner is IREVOwner, IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAccounts, IERC721Receiver {
|
|
38
49
|
// A library that adds default safety checks to ERC20 functionality.
|
|
39
50
|
using SafeERC20 for IERC20;
|
|
40
51
|
|
|
@@ -46,26 +57,30 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
|
|
|
46
57
|
error REVOwner_CashOutDelayNotFinished(uint256 cashOutDelay, uint256 blockTimestamp);
|
|
47
58
|
error REVOwner_InvalidLoanSourceToken(uint256 revnetId, address token);
|
|
48
59
|
error REVOwner_NativeFeeValueMismatch(uint256 expected, uint256 actual);
|
|
60
|
+
error REVOwner_NothingToAutoIssue(uint256 revnetId, uint256 stageId, address beneficiary);
|
|
61
|
+
error REVOwner_NothingToBurn(uint256 revnetId, address holder);
|
|
62
|
+
error REVOwner_StageNotStarted(uint256 stageId);
|
|
49
63
|
error REVOwner_Unauthorized(address caller, address expectedCaller);
|
|
64
|
+
error REVOwner_UnauthorizedOperator(uint256 revnetId, address caller);
|
|
50
65
|
|
|
51
66
|
//*********************************************************************//
|
|
52
67
|
// --------------- public immutable stored properties ---------------- //
|
|
53
68
|
//*********************************************************************//
|
|
54
69
|
|
|
55
70
|
/// @notice The buyback hook used as a data hook to route payments through buyback pools.
|
|
56
|
-
IJBBuybackHookRegistry public immutable BUYBACK_HOOK;
|
|
71
|
+
IJBBuybackHookRegistry public immutable override BUYBACK_HOOK;
|
|
57
72
|
|
|
58
73
|
/// @notice The directory of terminals and controllers for Juicebox projects.
|
|
59
|
-
IJBDirectory public immutable DIRECTORY;
|
|
74
|
+
IJBDirectory public immutable override DIRECTORY;
|
|
60
75
|
|
|
61
76
|
/// @notice The Juicebox project ID of the revnet that receives cash out fees.
|
|
62
|
-
uint256 public immutable FEE_REVNET_ID;
|
|
77
|
+
uint256 public immutable override FEE_REVNET_ID;
|
|
63
78
|
|
|
64
79
|
/// @notice The loan contract used by all revnets.
|
|
65
|
-
IREVLoans public immutable LOANS;
|
|
80
|
+
IREVLoans public immutable override LOANS;
|
|
66
81
|
|
|
67
82
|
/// @notice Deploys and tracks suckers for revnets.
|
|
68
|
-
IJBSuckerRegistry public immutable SUCKER_REGISTRY;
|
|
83
|
+
IJBSuckerRegistry public immutable override SUCKER_REGISTRY;
|
|
69
84
|
|
|
70
85
|
//*********************************************************************//
|
|
71
86
|
// --------------------- public stored properties -------------------- //
|
|
@@ -74,15 +89,43 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
|
|
|
74
89
|
/// @notice The timestamp of when cashouts will become available to a specific revnet's participants.
|
|
75
90
|
/// @dev Only applies to existing revnets deploying onto a new network.
|
|
76
91
|
/// @custom:param revnetId The ID of the revnet to check the cash out delay for.
|
|
77
|
-
mapping(uint256 revnetId => uint256 cashOutDelay) public cashOutDelayOf;
|
|
92
|
+
mapping(uint256 revnetId => uint256 cashOutDelay) public override cashOutDelayOf;
|
|
78
93
|
|
|
79
94
|
/// @notice Each revnet's tiered ERC-721 hook.
|
|
80
95
|
/// @custom:param revnetId The ID of the revnet to look up.
|
|
81
|
-
mapping(uint256 revnetId => IJB721TiersHook tiered721Hook) public tiered721HookOf;
|
|
96
|
+
mapping(uint256 revnetId => IJB721TiersHook tiered721Hook) public override tiered721HookOf;
|
|
97
|
+
|
|
98
|
+
/// @notice The amount of project tokens to auto-issue at a given stage to a given beneficiary.
|
|
99
|
+
/// @custom:param revnetId The ID of the revnet to look up.
|
|
100
|
+
/// @custom:param stageId The ID of the ruleset stage to look up.
|
|
101
|
+
/// @custom:param beneficiary The address that will receive the auto-issued tokens.
|
|
102
|
+
mapping(uint256 revnetId => mapping(uint256 stageId => mapping(address beneficiary => uint256 count)))
|
|
103
|
+
public
|
|
104
|
+
override amountToAutoIssue;
|
|
82
105
|
|
|
83
106
|
/// @notice The deployer that manages revnet state.
|
|
84
107
|
/// @dev Set once via `setDeployer()` using the precomputed canonical REVDeployer address.
|
|
85
|
-
IREVDeployer public deployer;
|
|
108
|
+
IREVDeployer public override deployer;
|
|
109
|
+
|
|
110
|
+
/// @notice The controller used by all revnets to manage their projects.
|
|
111
|
+
/// @dev Cached from the deployer at `setDeployer()` time.
|
|
112
|
+
IJBController public override CONTROLLER;
|
|
113
|
+
|
|
114
|
+
/// @notice The permissions registry used to grant operator authority over revnets.
|
|
115
|
+
/// @dev Cached from the deployer at `setDeployer()` time.
|
|
116
|
+
IJBPermissions public override PERMISSIONS;
|
|
117
|
+
|
|
118
|
+
/// @notice The Juicebox project NFT contract.
|
|
119
|
+
/// @dev Cached from the deployer at `setDeployer()` time. Required for `onERC721Received` authentication.
|
|
120
|
+
IJBProjects public override PROJECTS;
|
|
121
|
+
|
|
122
|
+
//*********************************************************************//
|
|
123
|
+
// -------------------- internal stored properties ------------------- //
|
|
124
|
+
//*********************************************************************//
|
|
125
|
+
|
|
126
|
+
/// @notice Additional operator permissions configured per revnet on top of the protocol-default set.
|
|
127
|
+
/// @custom:param revnetId The ID of the revnet to look up.
|
|
128
|
+
mapping(uint256 revnetId => uint256[] permissionIds) internal _extraOperatorPermissions;
|
|
86
129
|
|
|
87
130
|
//*********************************************************************//
|
|
88
131
|
// -------------------- private stored properties -------------------- //
|
|
@@ -509,15 +552,80 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
|
|
|
509
552
|
}
|
|
510
553
|
}
|
|
511
554
|
|
|
512
|
-
/// @notice
|
|
513
|
-
///
|
|
514
|
-
///
|
|
515
|
-
/// @
|
|
516
|
-
|
|
555
|
+
/// @notice Bind every piece of revnet-scoped state managed by this contract in a single deployer call. Stores
|
|
556
|
+
/// the cash-out delay, the tiered ERC-721 hook, auto-issuance allocations, extra operator permissions, the
|
|
557
|
+
/// initial operator, and any integration-specific permission grants.
|
|
558
|
+
/// @dev Only callable by the canonical deployer during a revnet's initial configuration. Extra operator
|
|
559
|
+
/// permissions are appended before the operator is bootstrapped so the operator receives the merged set in a
|
|
560
|
+
/// single permissions write.
|
|
561
|
+
/// @param revnetId The ID of the revnet being initialized.
|
|
562
|
+
/// @param init The full initialization payload.
|
|
563
|
+
function initializeRevnet(uint256 revnetId, REVOwnerRevnetInit calldata init) external override {
|
|
564
|
+
// Only the canonical deployer may bind a revnet's runtime state. Any other caller would let an outsider
|
|
565
|
+
// overwrite cash-out delays, hook addresses, or operator permissions for an existing revnet.
|
|
517
566
|
if (msg.sender != address(deployer)) {
|
|
518
567
|
revert REVOwner_Unauthorized({caller: msg.sender, expectedCaller: address(deployer)});
|
|
519
568
|
}
|
|
520
|
-
|
|
569
|
+
|
|
570
|
+
// Store the cash-out delay if the deployer computed one (existing revnet landing on a new chain). A zero
|
|
571
|
+
// delay means cash outs are unlocked immediately, so skip the storage write to save gas in the common case.
|
|
572
|
+
if (init.cashOutDelay != 0) {
|
|
573
|
+
cashOutDelayOf[revnetId] = init.cashOutDelay;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Bind the tiered ERC-721 hook the deployer just created for this revnet so `beforePayRecordedWith` can
|
|
577
|
+
// route NFT-tier mints through it. The deployer always deploys a hook (empty or pre-tiered), so the zero
|
|
578
|
+
// guard is defensive.
|
|
579
|
+
if (address(init.tiered721Hook) != address(0)) {
|
|
580
|
+
tiered721HookOf[revnetId] = init.tiered721Hook;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Record every auto-issuance allocation that lands on this chain. The deployer pre-filtered by `chainId`
|
|
584
|
+
// and resolved each stage ID to its canonical ruleset ID, so each entry is ready to be claimed later via
|
|
585
|
+
// `autoIssueFor`. `+=` (instead of `=`) lets the deployer split the same beneficiary across multiple stage
|
|
586
|
+
// entries without one overwriting another.
|
|
587
|
+
for (uint256 i; i < init.autoIssuances.length;) {
|
|
588
|
+
REVOwnerAutoIssuance calldata autoIssuance = init.autoIssuances[i];
|
|
589
|
+
amountToAutoIssue[revnetId][autoIssuance.stageId][autoIssuance.beneficiary] += autoIssuance.count;
|
|
590
|
+
unchecked {
|
|
591
|
+
++i;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Append any deployer-supplied extra permissions (e.g. 721 hook admin permissions) to this revnet's
|
|
596
|
+
// operator set BEFORE bootstrapping the operator below — `_setOperatorOf` reads the merged set, so the
|
|
597
|
+
// operator must see these extras on its first permissions write.
|
|
598
|
+
for (uint256 i; i < init.extraOperatorPermissionIds.length;) {
|
|
599
|
+
_extraOperatorPermissions[revnetId].push(init.extraOperatorPermissionIds[i]);
|
|
600
|
+
unchecked {
|
|
601
|
+
++i;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Grant the operator the merged default + extra permission set in a single permissions write. Passing
|
|
606
|
+
// `address(0)` here is the explicit way to launch a revnet with no operator — no permissions get written
|
|
607
|
+
// because `_setPermissionsFor` is called with the zero address as the holder.
|
|
608
|
+
_setOperatorOf({revnetId: revnetId, operator: init.operator});
|
|
609
|
+
|
|
610
|
+
// Apply per-revnet permission grants for integrations that aren't the operator (e.g. the Croptop
|
|
611
|
+
// publisher needs `ADJUST_721_TIERS` to post NFTs on the revnet's behalf). Each grant is scoped to a
|
|
612
|
+
// specific (revnet, operator, permissionId) triple on this contract's account so it does not leak into
|
|
613
|
+
// the operator's general authority.
|
|
614
|
+
for (uint256 i; i < init.extraGrants.length;) {
|
|
615
|
+
REVOwnerExtraGrant calldata grant = init.extraGrants[i];
|
|
616
|
+
uint8[] memory permissionIds = new uint8[](1);
|
|
617
|
+
permissionIds[0] = grant.permissionId;
|
|
618
|
+
_setPermissionsFor({
|
|
619
|
+
account: address(this), operator: grant.operator, revnetId: revnetId, permissionIds: permissionIds
|
|
620
|
+
});
|
|
621
|
+
unchecked {
|
|
622
|
+
++i;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Emit a single marker event for off-chain indexers — the deployer side already emits granular events
|
|
627
|
+
// (`DeployRevnet`, `StoreAutoIssuanceAmount`, `SetCashOutDelay`) with the underlying data.
|
|
628
|
+
emit InitializeRevnet({revnetId: revnetId, caller: msg.sender});
|
|
521
629
|
}
|
|
522
630
|
|
|
523
631
|
/// @notice Bind the canonical deployer address exactly once.
|
|
@@ -525,30 +633,145 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
|
|
|
525
633
|
/// Only that deploy-time binder may call this, which avoids an ambient public initializer where any first caller
|
|
526
634
|
/// could seize the deployer role before the deterministic REVDeployer is actually deployed.
|
|
527
635
|
/// @param newDeployer The canonical REVDeployer instance that will manage revnet runtime state.
|
|
528
|
-
function setDeployer(IREVDeployer newDeployer) external {
|
|
636
|
+
function setDeployer(IREVDeployer newDeployer) external override {
|
|
529
637
|
// Only the account that deployed this REVOwner may complete the one-time deployer binding.
|
|
530
638
|
if (msg.sender != _DEPLOYER) revert REVOwner_Unauthorized({caller: msg.sender, expectedCaller: _DEPLOYER});
|
|
531
639
|
// Prevent the deployer binding from being overwritten after initialization.
|
|
532
640
|
if (address(deployer) != address(0)) revert REVOwner_AlreadyInitialized({deployer: address(deployer)});
|
|
533
641
|
// Store the canonical REVDeployer that is authorized to manage runtime hook state.
|
|
534
642
|
deployer = newDeployer;
|
|
643
|
+
// Cache references read from the deployer so on-chain ownership operations don't need to re-traverse the
|
|
644
|
+
// deployer for each call. These are immutable on the deployer side so a single snapshot is sufficient.
|
|
645
|
+
CONTROLLER = newDeployer.CONTROLLER();
|
|
646
|
+
PERMISSIONS = newDeployer.PERMISSIONS();
|
|
647
|
+
PROJECTS = newDeployer.PROJECTS();
|
|
648
|
+
|
|
649
|
+
// Grant the wildcard operator permissions used by every revnet, scoped on this contract's account.
|
|
650
|
+
// The loan contract is the singleton shared by all revnets and uses the surplus allowance of each
|
|
651
|
+
// revnet's terminal. The buyback hook registry configures pools on every revnet.
|
|
652
|
+
uint8[] memory loanPermissionIds = new uint8[](1);
|
|
653
|
+
loanPermissionIds[0] = JBPermissionIds.USE_ALLOWANCE;
|
|
654
|
+
_setPermissionsFor({
|
|
655
|
+
account: address(this), operator: address(LOANS), revnetId: 0, permissionIds: loanPermissionIds
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
uint8[] memory buybackPermissionIds = new uint8[](1);
|
|
659
|
+
buybackPermissionIds[0] = JBPermissionIds.SET_BUYBACK_POOL;
|
|
660
|
+
_setPermissionsFor({
|
|
661
|
+
account: address(this), operator: address(BUYBACK_HOOK), revnetId: 0, permissionIds: buybackPermissionIds
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
// The deployer drives sucker setup for every revnet (initial deploy + post-deploy via
|
|
665
|
+
// `REVDeployer.deploySuckersFor`). Grant it `DEPLOY_SUCKERS` and `MAP_SUCKER_TOKEN` against this contract's
|
|
666
|
+
// account so the sucker registry sees an authorized caller acting for the project owner (this contract).
|
|
667
|
+
uint8[] memory deployerPermissionIds = new uint8[](2);
|
|
668
|
+
deployerPermissionIds[0] = JBPermissionIds.DEPLOY_SUCKERS;
|
|
669
|
+
deployerPermissionIds[1] = JBPermissionIds.MAP_SUCKER_TOKEN;
|
|
670
|
+
_setPermissionsFor({
|
|
671
|
+
account: address(this), operator: address(newDeployer), revnetId: 0, permissionIds: deployerPermissionIds
|
|
672
|
+
});
|
|
535
673
|
}
|
|
536
674
|
|
|
537
|
-
/// @notice
|
|
538
|
-
/// @dev
|
|
539
|
-
///
|
|
540
|
-
/// @param
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
675
|
+
/// @notice Auto-mint a revnet's tokens from a stage for a beneficiary.
|
|
676
|
+
/// @dev Permissionless: anyone can trigger the mint once the stage has started. The recorded amount is consumed
|
|
677
|
+
/// (reset to zero) after the mint executes.
|
|
678
|
+
/// @param revnetId The ID of the revnet to auto-mint tokens for.
|
|
679
|
+
/// @param stageId The ID of the ruleset stage to auto-mint tokens from.
|
|
680
|
+
/// @param beneficiary The address to send auto-minted tokens to.
|
|
681
|
+
function autoIssueFor(uint256 revnetId, uint256 stageId, address beneficiary) external override {
|
|
682
|
+
// Get the ruleset for the stage to check if it has started.
|
|
683
|
+
(JBRuleset memory ruleset,) = CONTROLLER.getRulesetOf({projectId: revnetId, rulesetId: stageId});
|
|
684
|
+
|
|
685
|
+
// Make sure the stage has started.
|
|
686
|
+
// forge-lint: disable-next-line(block-timestamp)
|
|
687
|
+
if (ruleset.start > block.timestamp) {
|
|
688
|
+
revert REVOwner_StageNotStarted({stageId: stageId});
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
uint256 count = amountToAutoIssue[revnetId][stageId][beneficiary];
|
|
692
|
+
if (count == 0) {
|
|
693
|
+
revert REVOwner_NothingToAutoIssue({revnetId: revnetId, stageId: stageId, beneficiary: beneficiary});
|
|
544
694
|
}
|
|
545
|
-
|
|
695
|
+
|
|
696
|
+
// Reset before the external call to prevent re-entry from re-claiming.
|
|
697
|
+
amountToAutoIssue[revnetId][stageId][beneficiary] = 0;
|
|
698
|
+
|
|
699
|
+
emit AutoIssue({
|
|
700
|
+
revnetId: revnetId, stageId: stageId, beneficiary: beneficiary, count: count, caller: msg.sender
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
CONTROLLER.mintTokensOf({
|
|
704
|
+
projectId: revnetId, tokenCount: count, beneficiary: beneficiary, memo: "", useReservedPercent: false
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/// @notice Burn any of a revnet's project tokens that have accumulated on this contract.
|
|
709
|
+
/// @dev Tokens accrue here from reserved-token distributions when splits don't sum to 100% (the JBController
|
|
710
|
+
/// mints the leftover to the project owner — which is this contract).
|
|
711
|
+
/// @param revnetId The ID of the revnet to burn tokens for.
|
|
712
|
+
function burnHeldTokensOf(uint256 revnetId) external override {
|
|
713
|
+
// Ask the controller's token registry for this contract's combined credit + ERC-20 balance for the revnet.
|
|
714
|
+
// This is the residue from reserved-token distributions where the splits don't sum to 100% — the
|
|
715
|
+
// controller mints the leftover to the project owner, which is this contract.
|
|
716
|
+
uint256 balance = CONTROLLER.TOKENS().totalBalanceOf({holder: address(this), projectId: revnetId});
|
|
717
|
+
|
|
718
|
+
// If there's nothing held, fail early — burning zero would silently no-op and waste the caller's gas.
|
|
719
|
+
if (balance == 0) revert REVOwner_NothingToBurn({revnetId: revnetId, holder: address(this)});
|
|
720
|
+
|
|
721
|
+
// Burn the full held balance through the controller. The controller drains credits first, then any
|
|
722
|
+
// ERC-20 supply on this contract, so a single call always clears the residue regardless of how it accrued.
|
|
723
|
+
CONTROLLER.burnTokensOf({holder: address(this), projectId: revnetId, tokenCount: balance, memo: ""});
|
|
724
|
+
|
|
725
|
+
// Record the burn for off-chain accounting. `caller` is the address that paid gas, not necessarily a
|
|
726
|
+
// privileged operator — this entrypoint is intentionally permissionless.
|
|
727
|
+
emit BurnHeldTokens({revnetId: revnetId, count: balance, caller: msg.sender});
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/// @notice Change a revnet's operator.
|
|
731
|
+
/// @dev Only a revnet's current operator can rotate the operator. Passing `address(0)` relinquishes operator
|
|
732
|
+
/// powers permanently — the permissions move to the zero address which cannot execute transactions.
|
|
733
|
+
/// @param revnetId The ID of the revnet to change the operator for.
|
|
734
|
+
/// @param newOperator The new operator's address. Use `address(0)` to relinquish operator powers.
|
|
735
|
+
function setOperatorOf(uint256 revnetId, address newOperator) external override {
|
|
736
|
+
_checkIfIsOperatorOf({revnetId: revnetId, operator: msg.sender});
|
|
737
|
+
|
|
738
|
+
emit ReplaceOperator({revnetId: revnetId, newOperator: newOperator, caller: msg.sender});
|
|
739
|
+
|
|
740
|
+
// Remove operator permissions from the old operator.
|
|
741
|
+
_setPermissionsFor({
|
|
742
|
+
account: address(this), operator: msg.sender, revnetId: revnetId, permissionIds: new uint8[](0)
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
// Grant the default operator permissions to the new operator (no-op if `newOperator == address(0)`).
|
|
746
|
+
_setOperatorOf({revnetId: revnetId, operator: newOperator});
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/// @notice Required to receive the JBProjects NFT for each revnet that this contract owns.
|
|
750
|
+
/// @dev Only accepts NFTs from the canonical `JBProjects` contract.
|
|
751
|
+
function onERC721Received(address, address, uint256, bytes calldata) external view returns (bytes4) {
|
|
752
|
+
if (msg.sender != address(PROJECTS)) revert();
|
|
753
|
+
return IERC721Receiver.onERC721Received.selector;
|
|
546
754
|
}
|
|
547
755
|
|
|
548
756
|
//*********************************************************************//
|
|
549
757
|
// -------------------------- public views --------------------------- //
|
|
550
758
|
//*********************************************************************//
|
|
551
759
|
|
|
760
|
+
/// @notice Check whether an address is a revnet's operator.
|
|
761
|
+
/// @param revnetId The ID of the revnet to check.
|
|
762
|
+
/// @param addr The address to check.
|
|
763
|
+
/// @return flag A flag indicating whether the address holds the revnet's operator permissions.
|
|
764
|
+
function isOperatorOf(uint256 revnetId, address addr) public view override returns (bool) {
|
|
765
|
+
return PERMISSIONS.hasPermissions({
|
|
766
|
+
operator: addr,
|
|
767
|
+
account: address(this),
|
|
768
|
+
projectId: revnetId,
|
|
769
|
+
permissionIds: _operatorPermissionIndexesOf(revnetId),
|
|
770
|
+
includeRoot: false,
|
|
771
|
+
includeWildcardProjectId: false
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
|
|
552
775
|
/// @notice Indicates if this contract adheres to the specified interface.
|
|
553
776
|
/// @dev See `IERC165.supportsInterface`.
|
|
554
777
|
/// @return A flag indicating if the provided interface ID is supported.
|
|
@@ -570,6 +793,44 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
|
|
|
570
793
|
return SUCKER_REGISTRY.isSuckerOf({projectId: revnetId, addr: addr});
|
|
571
794
|
}
|
|
572
795
|
|
|
796
|
+
/// @notice The default + custom operator permissions that should be held by a revnet's operator.
|
|
797
|
+
/// @param revnetId The ID of the revnet to look up.
|
|
798
|
+
/// @return allOperatorPermissions The merged permission ID list.
|
|
799
|
+
function _operatorPermissionIndexesOf(uint256 revnetId)
|
|
800
|
+
internal
|
|
801
|
+
view
|
|
802
|
+
returns (uint256[] memory allOperatorPermissions)
|
|
803
|
+
{
|
|
804
|
+
uint256[] memory customOperatorPermissionIndexes = _extraOperatorPermissions[revnetId];
|
|
805
|
+
|
|
806
|
+
allOperatorPermissions = new uint256[](9 + customOperatorPermissionIndexes.length);
|
|
807
|
+
allOperatorPermissions[0] = JBPermissionIds.SET_SPLIT_GROUPS;
|
|
808
|
+
allOperatorPermissions[1] = JBPermissionIds.SET_BUYBACK_POOL;
|
|
809
|
+
allOperatorPermissions[2] = JBPermissionIds.SET_BUYBACK_TWAP;
|
|
810
|
+
allOperatorPermissions[3] = JBPermissionIds.SET_PROJECT_URI;
|
|
811
|
+
allOperatorPermissions[4] = JBPermissionIds.SUCKER_SAFETY;
|
|
812
|
+
allOperatorPermissions[5] = JBPermissionIds.SET_BUYBACK_HOOK;
|
|
813
|
+
allOperatorPermissions[6] = JBPermissionIds.SET_ROUTER_TERMINAL;
|
|
814
|
+
allOperatorPermissions[7] = JBPermissionIds.SET_TOKEN_METADATA;
|
|
815
|
+
allOperatorPermissions[8] = JBPermissionIds.SIGN_FOR_ERC20;
|
|
816
|
+
|
|
817
|
+
for (uint256 i; i < customOperatorPermissionIndexes.length;) {
|
|
818
|
+
allOperatorPermissions[9 + i] = customOperatorPermissionIndexes[i];
|
|
819
|
+
unchecked {
|
|
820
|
+
++i;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/// @notice Reverts if `operator` is not the revnet's current operator.
|
|
826
|
+
/// @param revnetId The ID of the revnet to check.
|
|
827
|
+
/// @param operator The address to check.
|
|
828
|
+
function _checkIfIsOperatorOf(uint256 revnetId, address operator) internal view {
|
|
829
|
+
if (!isOperatorOf({revnetId: revnetId, addr: operator})) {
|
|
830
|
+
revert REVOwner_UnauthorizedOperator({revnetId: revnetId, caller: operator});
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
573
834
|
/// @notice Total outstanding local loan debt and collateral for a revnet.
|
|
574
835
|
/// @dev This is included in cash-out and peer-snapshot math because borrowed funds are still owed to the revnet
|
|
575
836
|
/// and collateral can re-enter supply when the loan is repaid.
|
|
@@ -661,4 +922,44 @@ contract REVOwner is IJBRulesetDataHook, IJBCashOutHook, IJBPeerChainAdjustedAcc
|
|
|
661
922
|
IERC20(token).safeIncreaseAllowance({spender: to, value: amount});
|
|
662
923
|
return 0;
|
|
663
924
|
}
|
|
925
|
+
|
|
926
|
+
/// @notice Grant the revnet's default operator permission set to `operator`.
|
|
927
|
+
/// @param revnetId The ID of the revnet to scope the permissions for.
|
|
928
|
+
/// @param operator The address to grant operator permissions to.
|
|
929
|
+
function _setOperatorOf(uint256 revnetId, address operator) internal {
|
|
930
|
+
uint256[] memory permissionIndexes = _operatorPermissionIndexesOf(revnetId);
|
|
931
|
+
uint8[] memory permissionIds = new uint8[](permissionIndexes.length);
|
|
932
|
+
|
|
933
|
+
for (uint256 i; i < permissionIndexes.length;) {
|
|
934
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
935
|
+
permissionIds[i] = uint8(permissionIndexes[i]);
|
|
936
|
+
unchecked {
|
|
937
|
+
++i;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
_setPermissionsFor({
|
|
942
|
+
account: address(this), operator: operator, revnetId: revnetId, permissionIds: permissionIds
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/// @notice Set the permissions on the JB permissions registry, scoped to this revnet.
|
|
947
|
+
/// @param account The account whose permission slot is being updated. Always `address(this)`.
|
|
948
|
+
/// @param operator The address whose permissions are being set.
|
|
949
|
+
/// @param revnetId The ID of the revnet to scope the permissions for.
|
|
950
|
+
/// @param permissionIds The permission IDs to grant (empty array revokes).
|
|
951
|
+
function _setPermissionsFor(
|
|
952
|
+
address account,
|
|
953
|
+
address operator,
|
|
954
|
+
uint256 revnetId,
|
|
955
|
+
uint8[] memory permissionIds
|
|
956
|
+
)
|
|
957
|
+
internal
|
|
958
|
+
{
|
|
959
|
+
JBPermissionsData memory permissionData =
|
|
960
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
961
|
+
JBPermissionsData({operator: operator, projectId: uint64(revnetId), permissionIds: permissionIds});
|
|
962
|
+
|
|
963
|
+
PERMISSIONS.setPermissionsFor({account: account, permissionsData: permissionData});
|
|
964
|
+
}
|
|
664
965
|
}
|
|
@@ -22,22 +22,6 @@ import {IREVLoans} from "./IREVLoans.sol";
|
|
|
22
22
|
|
|
23
23
|
/// @notice Deploys and manages revnets -- Juicebox projects with pre-configured tokenomics.
|
|
24
24
|
interface IREVDeployer {
|
|
25
|
-
/// @notice Emitted when tokens are auto-issued for a beneficiary during a stage.
|
|
26
|
-
/// @param revnetId The ID of the revnet.
|
|
27
|
-
/// @param stageId The ID of the stage.
|
|
28
|
-
/// @param beneficiary The address receiving the auto-issued tokens.
|
|
29
|
-
/// @param count The number of tokens auto-issued.
|
|
30
|
-
/// @param caller The address that triggered the auto-issuance.
|
|
31
|
-
event AutoIssue(
|
|
32
|
-
uint256 indexed revnetId, uint256 indexed stageId, address indexed beneficiary, uint256 count, address caller
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
/// @notice Emitted when held tokens are burned from the deployer contract.
|
|
36
|
-
/// @param revnetId The ID of the revnet whose tokens were burned.
|
|
37
|
-
/// @param count The number of tokens burned.
|
|
38
|
-
/// @param caller The address that triggered the burn.
|
|
39
|
-
event BurnHeldTokens(uint256 indexed revnetId, uint256 count, address caller);
|
|
40
|
-
|
|
41
25
|
/// @notice Emitted when a new revnet is deployed.
|
|
42
26
|
/// @param revnetId The ID of the deployed revnet.
|
|
43
27
|
/// @param configuration The revnet's configuration.
|
|
@@ -68,12 +52,6 @@ interface IREVDeployer {
|
|
|
68
52
|
address caller
|
|
69
53
|
);
|
|
70
54
|
|
|
71
|
-
/// @notice Emitted when the operator of a revnet is replaced.
|
|
72
|
-
/// @param revnetId The ID of the revnet.
|
|
73
|
-
/// @param newOperator The address of the new operator.
|
|
74
|
-
/// @param caller The address that replaced the operator.
|
|
75
|
-
event ReplaceOperator(uint256 indexed revnetId, address indexed newOperator, address caller);
|
|
76
|
-
|
|
77
55
|
/// @notice Emitted when the cash out delay is set for a revnet.
|
|
78
56
|
/// @param revnetId The ID of the revnet.
|
|
79
57
|
/// @param cashOutDelay The cash out delay in seconds.
|
|
@@ -90,13 +68,6 @@ interface IREVDeployer {
|
|
|
90
68
|
uint256 indexed revnetId, uint256 indexed stageId, address indexed beneficiary, uint256 count, address caller
|
|
91
69
|
);
|
|
92
70
|
|
|
93
|
-
/// @notice The number of revnet tokens that can be auto-minted for a beneficiary during a stage.
|
|
94
|
-
/// @param revnetId The ID of the revnet.
|
|
95
|
-
/// @param stageId The ID of the stage.
|
|
96
|
-
/// @param beneficiary The beneficiary of the auto-mint.
|
|
97
|
-
/// @return The number of tokens available to auto-issue.
|
|
98
|
-
function amountToAutoIssue(uint256 revnetId, uint256 stageId, address beneficiary) external view returns (uint256);
|
|
99
|
-
|
|
100
71
|
/// @notice The buyback hook used as a data hook to route payments through buyback pools.
|
|
101
72
|
/// @return The buyback hook contract.
|
|
102
73
|
function BUYBACK_HOOK() external view returns (IJBBuybackHookRegistry);
|
|
@@ -134,12 +105,6 @@ interface IREVDeployer {
|
|
|
134
105
|
/// @return The hook deployer contract.
|
|
135
106
|
function HOOK_DEPLOYER() external view returns (IJB721TiersHookDeployer);
|
|
136
107
|
|
|
137
|
-
/// @notice Check whether an address is a revnet's operator.
|
|
138
|
-
/// @param revnetId The ID of the revnet to check.
|
|
139
|
-
/// @param addr The address to check.
|
|
140
|
-
/// @return A flag indicating whether the address is the revnet's operator.
|
|
141
|
-
function isOperatorOf(uint256 revnetId, address addr) external view returns (bool);
|
|
142
|
-
|
|
143
108
|
/// @notice The loan contract used by all revnets.
|
|
144
109
|
/// @return The loans contract address.
|
|
145
110
|
function LOANS() external view returns (IREVLoans);
|
|
@@ -172,16 +137,6 @@ interface IREVDeployer {
|
|
|
172
137
|
/// @return The sucker registry contract.
|
|
173
138
|
function SUCKER_REGISTRY() external view returns (IJBSuckerRegistry);
|
|
174
139
|
|
|
175
|
-
/// @notice Auto-mint a revnet's tokens from a stage for a beneficiary.
|
|
176
|
-
/// @param revnetId The ID of the revnet to auto-mint tokens for.
|
|
177
|
-
/// @param stageId The ID of the stage to auto-mint tokens from.
|
|
178
|
-
/// @param beneficiary The address to send auto-minted tokens to.
|
|
179
|
-
function autoIssueFor(uint256 revnetId, uint256 stageId, address beneficiary) external;
|
|
180
|
-
|
|
181
|
-
/// @notice Burn any of a revnet's tokens held by this contract.
|
|
182
|
-
/// @param revnetId The ID of the revnet to burn tokens for.
|
|
183
|
-
function burnHeldTokensOf(uint256 revnetId) external;
|
|
184
|
-
|
|
185
140
|
/// @notice Deploy a revnet with a tiered ERC-721 hook and optional croptop posting support.
|
|
186
141
|
/// @dev Every revnet gets a 721 hook — pass an empty config if no tiers are needed initially.
|
|
187
142
|
/// @param revnetId The ID of the Juicebox project to initialize. Send 0 to deploy a new revnet.
|
|
@@ -230,9 +185,4 @@ interface IREVDeployer {
|
|
|
230
185
|
)
|
|
231
186
|
external
|
|
232
187
|
returns (address[] memory suckers);
|
|
233
|
-
|
|
234
|
-
/// @notice Change a revnet's operator. Only the current operator can call this.
|
|
235
|
-
/// @param revnetId The ID of the revnet.
|
|
236
|
-
/// @param newOperator The new operator's address.
|
|
237
|
-
function setOperatorOf(uint256 revnetId, address newOperator) external;
|
|
238
188
|
}
|