@bananapus/core-v6 0.0.30 → 0.0.32
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/ADMINISTRATION.md +43 -13
- package/ARCHITECTURE.md +62 -137
- package/AUDIT_INSTRUCTIONS.md +149 -428
- package/CHANGELOG.md +73 -0
- package/README.md +90 -201
- package/RISKS.md +27 -12
- package/SKILLS.md +31 -441
- package/STYLE_GUIDE.md +52 -19
- package/USER_JOURNEYS.md +76 -627
- package/package.json +1 -2
- package/references/entrypoints.md +160 -0
- package/references/types-errors-events.md +297 -0
- package/script/Deploy.s.sol +7 -2
- package/script/DeployPeriphery.s.sol +51 -4
- package/src/JBController.sol +45 -17
- package/src/JBDirectory.sol +26 -13
- package/src/JBFundAccessLimits.sol +28 -7
- package/src/JBMultiTerminal.sol +180 -86
- package/src/JBPermissions.sol +17 -17
- package/src/JBRulesets.sol +82 -23
- package/src/JBSplits.sol +31 -12
- package/src/JBTerminalStore.sol +137 -53
- package/src/JBTokens.sol +5 -2
- package/src/abstract/JBControlled.sol +10 -3
- package/src/abstract/JBPermissioned.sol +1 -1
- package/src/interfaces/IJBRulesetDataHook.sol +5 -4
- package/src/libraries/JBCashOuts.sol +1 -1
- package/src/libraries/JBConstants.sol +1 -1
- package/src/libraries/JBCurrencyIds.sol +1 -1
- package/src/libraries/JBFees.sol +1 -1
- package/src/libraries/JBFixedPointNumber.sol +1 -1
- package/src/libraries/JBMetadataResolver.sol +5 -2
- package/src/libraries/JBPayoutSplitGroupLib.sol +7 -2
- package/src/libraries/JBRulesetMetadataResolver.sol +1 -1
- package/src/libraries/JBSplitGroupIds.sol +1 -1
- package/src/libraries/JBSurplus.sol +5 -2
- package/src/structs/JBSplit.sol +4 -1
- package/test/TestForwardedTokenConsumption.sol +419 -0
- package/test/audit/CrossTerminalSurplusSpoof.t.sol +140 -0
- package/test/audit/CycledSurplusAllowanceReset.t.sol +184 -0
- package/test/units/static/JBController/TestPreviewMintOf.sol +5 -4
- package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +15 -12
- package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +6 -0
- package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +3 -0
- package/test/units/static/JBMultiTerminal/TestMigrateBalanceOf.sol +3 -0
- package/test/units/static/JBMultiTerminal/TestPay.sol +7 -15
- package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +1 -1
- package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +1 -1
- package/CHANGE_LOG.md +0 -479
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananapus/core-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.32",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
"artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'nana-core-v6'"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@bananapus/address-registry-v6": "^0.0.16",
|
|
30
29
|
"@bananapus/permission-ids-v6": "^0.0.15",
|
|
31
30
|
"@chainlink/contracts": "^1.5.0",
|
|
32
31
|
"@openzeppelin/contracts": "^5.6.1",
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Core Entry Points
|
|
2
|
+
|
|
3
|
+
Use this file when you already know the task is in `nana-core-v6` and need the concrete contract/function surface to open next.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
The core Juicebox V6 protocol on EVM: a modular system for launching treasury-backed tokens with configurable rulesets that govern payments, payouts, cash outs, and token issuance.
|
|
8
|
+
|
|
9
|
+
## Contracts
|
|
10
|
+
|
|
11
|
+
| Contract | Role |
|
|
12
|
+
|----------|------|
|
|
13
|
+
| `JBProjects` | ERC-721 project registry. Each NFT mint creates a new project ID. |
|
|
14
|
+
| `JBPermissions` | Packed `uint256` bitmap permissions. Operators get specific permission IDs scoped to projects. |
|
|
15
|
+
| `JBDirectory` | Maps project IDs to their controller (`IERC165`) and terminals (`IJBTerminal[]`). |
|
|
16
|
+
| `JBController` | Orchestrates rulesets, tokens, splits, fund access limits. Entry point for project lifecycle. |
|
|
17
|
+
| `JBMultiTerminal` | Handles ETH/ERC-20 payments, cash outs, payouts, surplus allowance, fees. Permit2 integration. |
|
|
18
|
+
| `JBTerminalStore` | Bookkeeping: balances, payout limit tracking, surplus calculation, bonding curve reclaim math. |
|
|
19
|
+
| `JBRulesets` | Stores/cycles rulesets with weight decay, approval hooks, and weight cache for gas-efficient long-running cycles. |
|
|
20
|
+
| `JBTokens` | Dual-balance system: credits (internal) + ERC-20. Credits burned first on burn. |
|
|
21
|
+
| `JBSplits` | Split configurations per project/ruleset/group. Packed storage for gas efficiency. |
|
|
22
|
+
| `JBFundAccessLimits` | Payout limits and surplus allowances per project/ruleset/terminal/token. |
|
|
23
|
+
| `JBPrices` | Price feed registry with project-specific and protocol-wide default feeds. Immutable once set. |
|
|
24
|
+
| `JBERC20` | Cloneable ERC-20 with Votes + Permit. Owned by `JBTokens`. Deployed via `Clones.clone()`. |
|
|
25
|
+
| `JBFeelessAddresses` | Allowlist for fee-exempt addresses. |
|
|
26
|
+
| `JBChainlinkV3PriceFeed` | Chainlink AggregatorV3 price feed with staleness threshold. Rejects negative/zero prices, incomplete rounds (`updatedAt == 0`), and stale answers carried from previous rounds (`answeredInRound < roundId`). |
|
|
27
|
+
| `JBChainlinkV3SequencerPriceFeed` | L2 sequencer-aware Chainlink feed (Optimism/Arbitrum) with grace period after restart. Treats any non-zero sequencer answer as down (`answer != 0`). |
|
|
28
|
+
| `JBDeadline` | Approval hook: rejects rulesets queued within `DURATION` seconds of start. Ships as `JBDeadline3Hours`, `JBDeadline1Day`, `JBDeadline3Days`, `JBDeadline7Days`. |
|
|
29
|
+
| `JBMatchingPriceFeed` | Always returns 1:1. For equivalent currencies (e.g. ETH/NATIVE_TOKEN). |
|
|
30
|
+
|
|
31
|
+
## Key Functions
|
|
32
|
+
|
|
33
|
+
### JBController
|
|
34
|
+
|
|
35
|
+
| Function | What it does |
|
|
36
|
+
|----------|--------------|
|
|
37
|
+
| `launchProjectFor(address owner, string uri, JBRulesetConfig[] rulesetConfigs, JBTerminalConfig[] terminalConfigs, string memo)` | Creates a project, queues its first rulesets, and configures terminals. Returns `projectId`. |
|
|
38
|
+
| `launchRulesetsFor(uint256 projectId, JBRulesetConfig[] rulesetConfigs, JBTerminalConfig[] terminalConfigs, string memo)` | Launches the first rulesets for an existing project that has none. |
|
|
39
|
+
| `queueRulesetsOf(uint256 projectId, JBRulesetConfig[] rulesetConfigs, string memo)` | Queues new rulesets for a project. Takes effect after the current ruleset ends (or immediately if duration is 0). |
|
|
40
|
+
| `mintTokensOf(uint256 projectId, uint256 tokenCount, address beneficiary, string memo, bool useReservedPercent)` | Mints project tokens. Requires `allowOwnerMinting` in the current ruleset or caller must be a terminal/hook with mint permission. |
|
|
41
|
+
| `burnTokensOf(address holder, uint256 projectId, uint256 tokenCount, string memo)` | Burns tokens from a holder. Requires holder's permission (`BURN_TOKENS`). |
|
|
42
|
+
| `sendReservedTokensToSplitsOf(uint256 projectId)` | Distributes accumulated reserved tokens to the reserved token split group. Returns token count sent. |
|
|
43
|
+
| `deployERC20For(uint256 projectId, string name, string symbol, bytes32 salt)` | Deploys a cloneable `JBERC20` for the project. Credits become claimable. |
|
|
44
|
+
| `claimTokensFor(address holder, uint256 projectId, uint256 count, address beneficiary)` | Redeems credits for ERC-20 tokens into beneficiary's wallet. |
|
|
45
|
+
| `setSplitGroupsOf(uint256 projectId, uint256 rulesetId, JBSplitGroup[] splitGroups)` | Sets the split groups for a project's ruleset. |
|
|
46
|
+
| `setTokenFor(uint256 projectId, IJBToken token)` | Sets an existing ERC-20 token for the project (requires `allowSetCustomToken` in ruleset). External token supply changes affect only that project's supply-sensitive pricing and cash-out math. |
|
|
47
|
+
| `setTokenMetadataOf(uint256 projectId, string name, string symbol)` | Sets the name and symbol of a project's ERC-20 token. Requires `SET_TOKEN_METADATA` permission. |
|
|
48
|
+
| `setUriOf(uint256 projectId, string uri)` | Sets the project's metadata URI. |
|
|
49
|
+
| `transferCreditsFrom(address holder, uint256 projectId, address recipient, uint256 creditCount)` | Transfers credits between addresses (reverts if `pauseCreditTransfers` is set in ruleset). |
|
|
50
|
+
| `addPriceFeedFor(uint256 projectId, uint256 pricingCurrency, uint256 unitCurrency, IJBPriceFeed feed)` | Registers a price feed (requires `allowAddPriceFeed` in ruleset). |
|
|
51
|
+
| `migrate(uint256 projectId, IERC165 to)` | Migrates the project to a new controller. Calls `beforeReceiveMigrationFrom`, `migrate`, updates directory, then `afterReceiveMigrationFrom`. |
|
|
52
|
+
| `currentRulesetOf(uint256 projectId)` | Returns the current ruleset and unpacked metadata. |
|
|
53
|
+
| `upcomingRulesetOf(uint256 projectId)` | Returns the upcoming ruleset and unpacked metadata. |
|
|
54
|
+
| `allRulesetsOf(uint256 projectId, uint256 startingId, uint256 size)` | Returns an array of rulesets with metadata, paginated. |
|
|
55
|
+
| `pendingReservedTokenBalanceOf(uint256 projectId)` | Returns accumulated reserved tokens not yet distributed. |
|
|
56
|
+
| `totalTokenSupplyWithReservedTokensOf(uint256 projectId)` | Returns total supply including pending reserved tokens. |
|
|
57
|
+
| `previewMintOf(uint256 projectId, uint256 tokenCount, bool useReservedPercent)` | Simulates a mint under the current ruleset. Returns `(beneficiaryTokenCount, reservedTokenCount)`. Reverts if `tokenCount` is 0. |
|
|
58
|
+
|
|
59
|
+
### JBMultiTerminal
|
|
60
|
+
|
|
61
|
+
| Function | What it does |
|
|
62
|
+
|----------|--------------|
|
|
63
|
+
| `pay(uint256 projectId, address token, uint256 amount, address beneficiary, uint256 minReturnedTokens, string memo, bytes metadata)` | Pays a project. Mints project tokens to beneficiary based on ruleset weight. Returns token count. |
|
|
64
|
+
| `cashOutTokensOf(address holder, uint256 projectId, uint256 cashOutCount, address tokenToReclaim, uint256 minTokensReclaimed, address payable beneficiary, bytes metadata)` | Burns project tokens and reclaims surplus terminal tokens via bonding curve. |
|
|
65
|
+
| `sendPayoutsOf(uint256 projectId, address token, uint256 amount, uint256 currency, uint256 minTokensPaidOut)` | Distributes payouts from the project's balance to its payout split group, up to the payout limit. |
|
|
66
|
+
| `useAllowanceOf(uint256 projectId, address token, uint256 amount, uint256 currency, uint256 minTokensPaidOut, address payable beneficiary, address payable feeBeneficiary, string memo)` | Withdraws from the project's surplus allowance to a beneficiary. The `feeBeneficiary` receives tokens minted by the fee payment. |
|
|
67
|
+
| `addToBalanceOf(uint256 projectId, address token, uint256 amount, bool shouldReturnHeldFees, string memo, bytes metadata)` | Adds funds to a project's balance without minting tokens. Can unlock held fees. |
|
|
68
|
+
| `migrateBalanceOf(uint256 projectId, address token, IJBTerminal to)` | Migrates a project's token balance to another terminal. Requires `allowTerminalMigration`. |
|
|
69
|
+
| `processHeldFeesOf(uint256 projectId, address token, uint256 count)` | Processes up to `count` held fees for a project, sending them to the fee beneficiary project. |
|
|
70
|
+
| `addAccountingContextsFor(uint256 projectId, JBAccountingContext[] accountingContexts)` | Adds new accounting contexts (token types) to a terminal for a project. |
|
|
71
|
+
| `currentSurplusOf(uint256 projectId, address[] tokens, uint256 decimals, uint256 currency)` | Returns the project's current surplus in this terminal. Empty `tokens` = all tokens. |
|
|
72
|
+
| `accountingContextForTokenOf(uint256 projectId, address token)` | Returns the accounting context for a specific token. |
|
|
73
|
+
| `accountingContextsOf(uint256 projectId)` | Returns all accounting contexts for a project. |
|
|
74
|
+
| `previewPayFor(uint256 projectId, address token, uint256 amount, address beneficiary, bytes metadata)` | Simulates a full payment including the reserved/beneficiary token split. Returns `(ruleset, beneficiaryTokenCount, reservedTokenCount, hookSpecifications)`. Composes `STORE.previewPayFrom` + `controller.previewMintOf`. |
|
|
75
|
+
| `previewCashOutFrom(address holder, uint256 projectId, uint256 cashOutCount, address tokenToReclaim, address payable beneficiary, bytes metadata)` | Simulates a full cash out including bonding curve and data hook effects. Cash-out data hooks may alter pricing inputs, but not the caller-supplied burn count. Returns `(ruleset, reclaimAmount, cashOutTaxRate, hookSpecifications)`. Delegates to `STORE.previewCashOutFrom`. |
|
|
76
|
+
| `heldFeesOf(uint256 projectId, address token, uint256 count)` | Returns up to `count` held fees for a project/token. |
|
|
77
|
+
|
|
78
|
+
### JBTerminalStore
|
|
79
|
+
|
|
80
|
+
| Function | What it does |
|
|
81
|
+
|----------|--------------|
|
|
82
|
+
| `recordPaymentFrom(address payer, JBTokenAmount amount, uint256 projectId, address beneficiary, bytes metadata)` | Records a payment. Applies data hook if enabled. Returns ruleset, token count, hook specifications. |
|
|
83
|
+
| `recordPayoutFor(uint256 projectId, address token, uint256 amount, uint256 currency)` | Records a payout. Enforces payout limits. Returns ruleset and amount paid out. |
|
|
84
|
+
| `recordCashOutFor(address holder, uint256 projectId, uint256 cashOutCount, address tokenToReclaim, bool beneficiaryIsFeeless, bytes metadata)` | Records a cash out. Computes reclaim via the bonding curve, including any pricing-only adjustments returned by the cash-out data hook. Returns ruleset, reclaim amount, tax rate, and hook specifications. |
|
|
85
|
+
| `recordUsedAllowanceOf(uint256 projectId, address token, uint256 amount, uint256 currency)` | Records surplus allowance usage. Enforces allowance limits. Returns ruleset and used amount. |
|
|
86
|
+
| `recordAddedBalanceFor(uint256 projectId, address token, uint256 amount)` | Records funds added to a project's balance. |
|
|
87
|
+
| `recordTerminalMigration(uint256 projectId, address token)` | Records a terminal migration, returning the full balance. |
|
|
88
|
+
| `balanceOf(address terminal, uint256 projectId, address token)` | Returns the balance of a project at a terminal for a given token. |
|
|
89
|
+
| `usedPayoutLimitOf(address terminal, uint256 projectId, address token, uint256 rulesetCycleNumber, uint256 currency)` | Returns the used payout limit for a project in a given cycle. |
|
|
90
|
+
| `usedSurplusAllowanceOf(address terminal, uint256 projectId, address token, uint256 rulesetId, uint256 currency)` | Returns the used surplus allowance for a project in a given ruleset. |
|
|
91
|
+
| `currentReclaimableSurplusOf(uint256 projectId, uint256 cashOutCount, uint256 totalSupply, uint256 surplus)` | Returns the reclaimable surplus given raw total supply and surplus values. Applies bonding curve from current ruleset. |
|
|
92
|
+
| `currentReclaimableSurplusOf(uint256 projectId, uint256 cashOutCount, IJBTerminal[] terminals, address[] tokens, uint256 decimals, uint256 currency)` | Returns the reclaimable surplus across specified terminals and tokens. Empty arrays default to all. |
|
|
93
|
+
| `currentTotalReclaimableSurplusOf(uint256 projectId, uint256 cashOutCount, uint256 decimals, uint256 currency)` | Convenience view: reclaimable surplus across all terminals and all tokens. |
|
|
94
|
+
| `currentSurplusOf(uint256 projectId, IJBTerminal[] terminals, address[] tokens, uint256 decimals, uint256 currency)` | Returns the current surplus across specified terminals and tokens. Empty arrays default to all. |
|
|
95
|
+
| `currentTotalSurplusOf(uint256 projectId, uint256 decimals, uint256 currency)` | Convenience view: total surplus across all terminals and all tokens. |
|
|
96
|
+
| `previewPayFrom(address terminal, address payer, JBTokenAmount amount, uint256 projectId, address beneficiary, bytes metadata)` | Simulates a payment without modifying state. Uses the explicit `terminal` parameter for balance/surplus lookups. Invokes data hooks if configured. Returns ruleset, token count, and hook specifications. |
|
|
97
|
+
| `previewCashOutFrom(address terminal, address holder, uint256 projectId, uint256 cashOutCount, address tokenToReclaim, bool beneficiaryIsFeeless, bytes metadata)` | Simulates a cash out without modifying state. Uses the explicit `terminal` parameter for balance/surplus lookups. Invokes data hooks if configured, including any pricing-only adjustments they return. Returns ruleset, reclaim amount, tax rate, and hook specifications. |
|
|
98
|
+
|
|
99
|
+
### JBRulesets
|
|
100
|
+
|
|
101
|
+
| Function | What it does |
|
|
102
|
+
|----------|--------------|
|
|
103
|
+
| `currentOf(uint256 projectId)` | Returns the currently active ruleset with decayed weight and correct cycle number. |
|
|
104
|
+
| `latestQueuedOf(uint256 projectId)` | Returns the latest queued ruleset and its approval status. |
|
|
105
|
+
| `queueFor(uint256 projectId, uint256 duration, uint256 weight, uint256 weightCutPercent, IJBRulesetApprovalHook approvalHook, uint256 metadata, uint256 mustStartAtOrAfter)` | Queues a new ruleset. Only callable by the project's controller. |
|
|
106
|
+
| `updateRulesetWeightCache(uint256 projectId, uint256 rulesetId)` | Updates the weight cache for long-running rulesets. Required when `weightCutMultiple > 20,000` to avoid gas limits. |
|
|
107
|
+
|
|
108
|
+
### JBPermissions
|
|
109
|
+
|
|
110
|
+
| Function | What it does |
|
|
111
|
+
|----------|--------------|
|
|
112
|
+
| `setPermissionsFor(address account, JBPermissionsData permissionsData)` | Grants or revokes operator permissions. ROOT operators can set non-ROOT permissions for others. |
|
|
113
|
+
| `hasPermission(address operator, address account, uint256 projectId, uint256 permissionId, bool includeRoot, bool includeWildcardProjectId)` | Checks if an operator has a specific permission. |
|
|
114
|
+
| `hasPermissions(address operator, address account, uint256 projectId, uint256[] permissionIds, bool includeRoot, bool includeWildcardProjectId)` | Checks if an operator has all specified permissions. |
|
|
115
|
+
|
|
116
|
+
### JBDirectory
|
|
117
|
+
|
|
118
|
+
| Function | What it does |
|
|
119
|
+
|----------|--------------|
|
|
120
|
+
| `controllerOf(uint256 projectId)` | Returns the project's controller as `IERC165`. |
|
|
121
|
+
| `terminalsOf(uint256 projectId)` | Returns the project's terminals as `IJBTerminal[]`. |
|
|
122
|
+
| `primaryTerminalOf(uint256 projectId, address token)` | Returns the project's primary terminal for a given token. |
|
|
123
|
+
| `isTerminalOf(uint256 projectId, IJBTerminal terminal)` | Checks if a terminal belongs to a project. |
|
|
124
|
+
| `setControllerOf(uint256 projectId, IERC165 controller)` | Sets the project's controller. |
|
|
125
|
+
| `setTerminalsOf(uint256 projectId, IJBTerminal[] terminals)` | Sets the project's terminals. |
|
|
126
|
+
| `setPrimaryTerminalOf(uint256 projectId, address token, IJBTerminal terminal)` | Sets the primary terminal for a token. Requires `ADD_TERMINALS` permission if the terminal is not already in the project's terminal list (implicit addition). |
|
|
127
|
+
| `setIsAllowedToSetFirstController(address addr, bool flag)` | Allows/disallows an address to set a project's first controller. Owner-only. |
|
|
128
|
+
|
|
129
|
+
### JBPrices
|
|
130
|
+
|
|
131
|
+
| Function | What it does |
|
|
132
|
+
|----------|--------------|
|
|
133
|
+
| `pricePerUnitOf(uint256 projectId, uint256 pricingCurrency, uint256 unitCurrency, uint256 decimals)` | Returns the price of 1 `unitCurrency` in `pricingCurrency`. Checks project-specific, inverse, then default feeds. |
|
|
134
|
+
| `addPriceFeedFor(uint256 projectId, uint256 pricingCurrency, uint256 unitCurrency, IJBPriceFeed feed)` | Registers a price feed. Project ID 0 sets protocol-wide defaults (owner-only). Immutable once set. |
|
|
135
|
+
|
|
136
|
+
### JBTokens
|
|
137
|
+
|
|
138
|
+
| Function | What it does |
|
|
139
|
+
|----------|--------------|
|
|
140
|
+
| `totalSupplyOf(uint256 projectId)` | Returns total supply: credits + ERC-20 tokens. |
|
|
141
|
+
| `totalBalanceOf(address holder, uint256 projectId)` | Returns combined credit + ERC-20 balance. |
|
|
142
|
+
| `creditBalanceOf(address holder, uint256 projectId)` | Returns the holder's credit balance. |
|
|
143
|
+
| `tokenOf(uint256 projectId)` | Returns the ERC-20 token for a project (`IJBToken`). |
|
|
144
|
+
| `setTokenMetadataFor(uint256 projectId, string name, string symbol)` | Sets the name and symbol of a project's token. Controller-only. |
|
|
145
|
+
|
|
146
|
+
### JBSplits
|
|
147
|
+
|
|
148
|
+
| Function | What it does |
|
|
149
|
+
|----------|--------------|
|
|
150
|
+
| `splitsOf(uint256 projectId, uint256 rulesetId, uint256 groupId)` | Returns splits for a project/ruleset/group. Falls back to ruleset ID 0 if none set. |
|
|
151
|
+
|
|
152
|
+
**Self-auth for `setSplitGroupsOf`**: A contract can set its own split groups without controller authorization if the lower 160 bits of the `groupId` match `msg.sender` AND the upper 96 bits are non-zero. GroupIds with zero upper 96 bits (bare addresses like `uint256(uint160(token))`) are protocol-reserved for terminal payout groups and always require controller auth.
|
|
153
|
+
|
|
154
|
+
### Other
|
|
155
|
+
|
|
156
|
+
| Function | What it does |
|
|
157
|
+
|----------|--------------|
|
|
158
|
+
| `setFeelessAddress(address addr, bool flag)` | Adds or removes an address from the fee exemption list. Owner-only. (`JBFeelessAddresses`) |
|
|
159
|
+
| `setControllerAllowed(uint256 projectId)` | Returns whether a project's controller can currently be set. (`IJBDirectoryAccessControl`) |
|
|
160
|
+
| `setTerminalsAllowed(uint256 projectId)` | Returns whether a project's terminals can currently be set. (`IJBDirectoryAccessControl`) |
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# Core Types, Errors, And Events
|
|
2
|
+
|
|
3
|
+
Use this file when you need deeper protocol reference material after the repo-local `SKILLS.md` has already routed you to `nana-core-v6`.
|
|
4
|
+
|
|
5
|
+
## Key Types
|
|
6
|
+
|
|
7
|
+
| Struct/Enum | Key Fields | Used In |
|
|
8
|
+
|-------------|------------|---------|
|
|
9
|
+
| `JBRuleset` | `cycleNumber (uint48)`, `id (uint48)`, `basedOnId (uint48)`, `start (uint48)`, `duration (uint32)`, `weight (uint112)`, `weightCutPercent (uint32)`, `approvalHook`, `metadata (uint256)` | `currentOf()`, `recordPaymentFrom()`, `recordCashOutFor()` return values |
|
|
10
|
+
| `JBRulesetConfig` | `mustStartAtOrAfter (uint48)`, `duration (uint32)`, `weight (uint112)`, `weightCutPercent (uint32)`, `approvalHook`, `metadata (JBRulesetMetadata)`, `splitGroups[]`, `fundAccessLimitGroups[]` | `launchProjectFor()`, `queueRulesetsOf()` input |
|
|
11
|
+
| `JBRulesetMetadata` | `reservedPercent (uint16)`, `cashOutTaxRate (uint16)`, `baseCurrency (uint32)`, `pausePay`, `pauseCreditTransfers`, `allowOwnerMinting`, `allowSetCustomToken`, `allowTerminalMigration`, `allowSetTerminals`, `allowSetController`, `allowAddAccountingContext`, `allowAddPriceFeed`, `ownerMustSendPayouts`, `holdFees`, `useTotalSurplusForCashOuts`, `useDataHookForPay`, `useDataHookForCashOut`, `dataHook (address)`, `metadata (uint16)` | Packed into `JBRuleset.metadata` |
|
|
12
|
+
| `JBSplit` | `percent (uint32)`, `projectId (uint64)`, `beneficiary (address payable)`, `preferAddToBalance`, `lockedUntil (uint48)`, `hook (IJBSplitHook)` | `splitsOf()`, `setSplitGroupsOf()` |
|
|
13
|
+
| `JBSplitGroup` | `groupId (uint256)`, `splits (JBSplit[])` | `JBRulesetConfig.splitGroups`, `setSplitGroupsOf()` |
|
|
14
|
+
| `JBAccountingContext` | `token (address)`, `decimals (uint8)`, `currency (uint32)` | Terminal token accounting, surplus/reclaim calculations |
|
|
15
|
+
| `JBTokenAmount` | `token (address)`, `decimals (uint8)`, `currency (uint32)`, `value (uint256)` | `recordPaymentFrom()` input |
|
|
16
|
+
| `JBTerminalConfig` | `terminal (IJBTerminal)`, `accountingContextsToAccept (JBAccountingContext[])` | `launchProjectFor()`, `launchRulesetsFor()` input |
|
|
17
|
+
| `JBCurrencyAmount` | `amount (uint224)`, `currency (uint32)` | Payout limits and surplus allowances |
|
|
18
|
+
| `JBFundAccessLimitGroup` | `terminal (address)`, `token (address)`, `payoutLimits (JBCurrencyAmount[])`, `surplusAllowances (JBCurrencyAmount[])` | `JBRulesetConfig.fundAccessLimitGroups` |
|
|
19
|
+
| `JBPermissionsData` | `operator (address)`, `projectId (uint64)`, `permissionIds (uint8[])` | `setPermissionsFor()` input |
|
|
20
|
+
| `JBFee` | `amount (uint256)`, `beneficiary (address)`, `unlockTimestamp (uint48)` | Held fees in `JBMultiTerminal` |
|
|
21
|
+
| `JBSingleAllowance` | `sigDeadline (uint256)`, `amount (uint160)`, `expiration (uint48)`, `nonce (uint48)`, `signature (bytes)` | Permit2 allowance in terminal payments |
|
|
22
|
+
| `JBRulesetWithMetadata` | `ruleset (JBRuleset)`, `metadata (JBRulesetMetadata)` | `allRulesetsOf()`, `currentRulesetOf()` return values |
|
|
23
|
+
| `JBRulesetWeightCache` | `weight (uint112)`, `weightCutMultiple (uint168)` | Weight caching for long-running rulesets in `JBRulesets` |
|
|
24
|
+
| `JBApprovalStatus` (enum) | `Empty`, `Upcoming`, `Active`, `ApprovalExpected`, `Approved`, `Failed` | Approval hook status for queued rulesets |
|
|
25
|
+
|
|
26
|
+
### Hook Structs
|
|
27
|
+
|
|
28
|
+
| Struct | Key Fields | Used In |
|
|
29
|
+
|--------|------------|---------|
|
|
30
|
+
| `JBBeforePayRecordedContext` | `terminal`, `payer`, `amount (JBTokenAmount)`, `projectId`, `rulesetId`, `beneficiary`, `weight`, `reservedPercent`, `metadata` | `IJBRulesetDataHook.beforePayRecordedWith()` input |
|
|
31
|
+
| `JBBeforeCashOutRecordedContext` | `terminal`, `holder`, `projectId`, `rulesetId`, `cashOutCount`, `totalSupply`, `surplus (JBTokenAmount)`, `useTotalSurplus`, `cashOutTaxRate`, `beneficiaryIsFeeless`, `metadata` | `IJBRulesetDataHook.beforeCashOutRecordedWith()` input |
|
|
32
|
+
| `JBAfterPayRecordedContext` | `payer`, `projectId`, `rulesetId`, `amount (JBTokenAmount)`, `forwardedAmount (JBTokenAmount)`, `weight`, `newlyIssuedTokenCount`, `beneficiary`, `hookMetadata`, `payerMetadata` | `IJBPayHook.afterPayRecordedWith()` input |
|
|
33
|
+
| `JBAfterCashOutRecordedContext` | `holder`, `projectId`, `rulesetId`, `cashOutCount`, `reclaimedAmount (JBTokenAmount)`, `forwardedAmount (JBTokenAmount)`, `cashOutTaxRate`, `beneficiary`, `hookMetadata`, `cashOutMetadata` | `IJBCashOutHook.afterCashOutRecordedWith()` input |
|
|
34
|
+
| `JBPayHookSpecification` | `hook (IJBPayHook)`, `noop (bool)`, `amount`, `metadata` | Returned by data hook; specifies which pay hooks to call and how much to forward. `noop = true` means informational-only (callback skipped, amount must be 0). |
|
|
35
|
+
| `JBCashOutHookSpecification` | `hook (IJBCashOutHook)`, `noop (bool)`, `amount`, `metadata` | Returned by data hook; specifies which cash out hooks to call and how much to forward. `noop = true` means informational-only (callback skipped, amount must be 0). |
|
|
36
|
+
| `JBSplitHookContext` | `token`, `amount`, `decimals`, `projectId`, `groupId`, `split (JBSplit)` | `IJBSplitHook.processSplitWith()` input |
|
|
37
|
+
|
|
38
|
+
### Constants (`JBConstants`)
|
|
39
|
+
|
|
40
|
+
| Constant | Value | Meaning |
|
|
41
|
+
|----------|-------|---------|
|
|
42
|
+
| `NATIVE_TOKEN` | `0x000000000000000000000000000000000000EEEe` | Sentinel address for native ETH |
|
|
43
|
+
| `MAX_RESERVED_PERCENT` | `10_000` | 100% reserved (basis points) |
|
|
44
|
+
| `MAX_CASH_OUT_TAX_RATE` | `10_000` | 100% tax rate (basis points) |
|
|
45
|
+
| `MAX_WEIGHT_CUT_PERCENT` | `1_000_000_000` | 100% weight cut (9-decimal precision) |
|
|
46
|
+
| `SPLITS_TOTAL_PERCENT` | `1_000_000_000` | 100% split allocation (9-decimal precision) |
|
|
47
|
+
| `MAX_FEE` | `1000` | 100% fee cap (the actual fee is 25 = 2.5%) |
|
|
48
|
+
|
|
49
|
+
### Currency IDs (`JBCurrencyIds`)
|
|
50
|
+
|
|
51
|
+
| ID | Currency |
|
|
52
|
+
|----|----------|
|
|
53
|
+
| `1` | ETH |
|
|
54
|
+
| `2` | USD |
|
|
55
|
+
|
|
56
|
+
### Split Group IDs (`JBSplitGroupIds`)
|
|
57
|
+
|
|
58
|
+
| ID | Group |
|
|
59
|
+
|----|-------|
|
|
60
|
+
| `1` | `RESERVED_TOKENS` -- reserved token distribution |
|
|
61
|
+
|
|
62
|
+
### Special Values
|
|
63
|
+
|
|
64
|
+
| Value | Context | Meaning |
|
|
65
|
+
|-------|---------|---------|
|
|
66
|
+
| `weight = 0` | `JBRuleset` / `JBRulesetConfig` | No token issuance for payments. |
|
|
67
|
+
| `weight = 1` | `JBRuleset` / `JBRulesetConfig` | Inherit decayed weight from previous ruleset (sentinel). |
|
|
68
|
+
| `duration = 0` | `JBRuleset` / `JBRulesetConfig` | Ruleset never expires; must be explicitly replaced by a new queued ruleset (takes effect immediately). |
|
|
69
|
+
| `projectId = 0` | `JBPermissionsData` | Wildcard: permission applies to ALL projects. Cannot be combined with ROOT (1). |
|
|
70
|
+
| `permissionId = 1` | `JBPermissions` | ROOT: grants all permissions for the scoped project. |
|
|
71
|
+
| `rulesetId = 0` | `JBSplits.splitsOf()` | Fallback split group used when no splits are set for a specific ruleset. |
|
|
72
|
+
| `projectId = 0` | `JBPrices.addPriceFeedFor()` | Sets a protocol-wide default price feed (owner-only). |
|
|
73
|
+
|
|
74
|
+
## Gotchas
|
|
75
|
+
|
|
76
|
+
- `IJBDirectory.controllerOf()` returns `IERC165`, NOT `address` -- must wrap: `address(directory.controllerOf(projectId))`
|
|
77
|
+
- `IJBDirectory.primaryTerminalOf()` returns `IJBTerminal`, NOT `address` -- must wrap: `address(directory.primaryTerminalOf(projectId, token))`
|
|
78
|
+
- `IJBDirectory.terminalsOf()` returns `IJBTerminal[]`, NOT `address[]`
|
|
79
|
+
- `pricePerUnitOf()` is on `IJBPrices`, NOT `IJBController` -- access via `IJBController(ctrl).PRICES().pricePerUnitOf(...)`
|
|
80
|
+
- `JBRulesetConfig` fields need explicit casts: `uint48 mustStartAtOrAfter`, `uint32 duration`, `uint112 weight`, `uint32 weightCutPercent`
|
|
81
|
+
- Zero-amount `pay{value:0}()` and zero-count `cashOutTokensOf(count=0)` are valid no-ops (mint/return 0)
|
|
82
|
+
- `sendPayoutsOf()` reverts when `amount > payout limit` -- does NOT auto-cap
|
|
83
|
+
- `IJBTokens.claimTokensFor()` takes 4 args: `(holder, projectId, count, beneficiary)` -- NOT 3
|
|
84
|
+
- `JBFeelessAddresses.setFeelessAddress()` NOT `setIsFeelessAddress()` -- the function name omits "Is"
|
|
85
|
+
- Named returns auto-return (no explicit `return` statement needed in Solidity)
|
|
86
|
+
- `bool` defaults to `false` (correct security default for metadata flags)
|
|
87
|
+
- Credits are burned before ERC-20 tokens in `JBTokens.burnFrom()`
|
|
88
|
+
- `JBRuleset.weight` is `uint112` with 18 decimals; `JBRuleset.metadata` is packed -- use `JBRulesetMetadataResolver` to unpack
|
|
89
|
+
- `JBERC20` is cloned via `Clones.clone()` -- its constructor sets invalid name/symbol; real values set in `initialize()`
|
|
90
|
+
- Fee is 2.5% (`FEE = 25` out of `MAX_FEE = 1000`)
|
|
91
|
+
- Project #1 is the fee beneficiary project (receives all protocol fees)
|
|
92
|
+
- **Fee-free cashout exemption is scoped to fee-free intra-terminal payout amounts.** `_feeFreeSurplusOf[projectId][token]` accumulates the value of fee-free payouts. After any outflow (payouts, `useAllowanceOf`, non-zero-tax or feeless cashouts), the counter is capped at the remaining balance — non-fee-free funds leave first, preserving the fee-free counter. During cashout with `cashOutTaxRate=0`, the 2.5% fee applies only up to this surplus, then depletes. Once consumed, subsequent cashouts are fee-free again. Cleared on terminal migration. This prevents a round-trip fee bypass (intra-terminal payout → zero-tax cashout) while scoping fees precisely to the fee-free inflow.
|
|
93
|
+
- `JBProjects` constructor optionally mints project #1 to `feeProjectOwner` -- if `address(0)`, no fee project is created
|
|
94
|
+
- `JBMultiTerminal` derives `DIRECTORY` from the provided `store` in its constructor -- not passed directly
|
|
95
|
+
- `JBPrices.pricePerUnitOf()` checks project-specific feed, then inverse, then falls back to `DEFAULT_PROJECT_ID = 0`
|
|
96
|
+
- `useAllowanceOf()` takes 8 args including `address payable feeBeneficiary` -- do NOT omit it
|
|
97
|
+
- Cash out tax rate of 0% = proportional (1:1) redemption; 100% = nothing reclaimable (all surplus locked). Do NOT confuse with a "cash out rate" where 100% means full redemption.
|
|
98
|
+
- `cashOutTaxRate` in `JBRulesetMetadata` is `uint16` (max 10,000 basis points), NOT 9-decimal precision
|
|
99
|
+
- `reservedPercent` in `JBRulesetMetadata` is `uint16` (max 10,000 basis points), NOT 9-decimal precision
|
|
100
|
+
- `weight` in `JBRuleset` is `uint112`, but `weight` in `JBRulesetConfig` is also `uint112` -- both use 18 decimals
|
|
101
|
+
- `JBSplits.splitsOf()` falls back to ruleset ID 0 if no splits are set for the given rulesetId
|
|
102
|
+
- Held fees are held for 28 days (`_FEE_HOLDING_SECONDS = 2,419,200`) before they can be processed
|
|
103
|
+
- `JBController`, `JBMultiTerminal`, `JBProjects`, `JBPrices`, `JBPermissions` all support ERC-2771 meta-transactions
|
|
104
|
+
- `JBRulesetMetadataResolver` bit layout: version (4 bits), reservedPercent (16), cashOutTaxRate (16), baseCurrency (32), 14 boolean flags (1 bit each), dataHook address (160), metadata (14)
|
|
105
|
+
- `IJBDirectoryAccessControl` has `setControllerAllowed()` and `setTerminalsAllowed()` -- NOT `setControllerAllowedFor()`
|
|
106
|
+
- Price feeds are immutable once set in `JBPrices` -- they cannot be replaced or removed
|
|
107
|
+
- `JBFundAccessLimits` requires payout limits and surplus allowances to be in strictly increasing currency order to prevent duplicates
|
|
108
|
+
- **Empty `fundAccessLimitGroups` = zero payouts, NOT unlimited.** If a ruleset's `fundAccessLimitGroups` array is empty (or has no entry for the terminal/token), `payoutLimitsOf()` returns an empty array → cumulative limit is 0 → `sendPayoutsOf()` reverts on any amount. To allow unlimited payouts, explicitly set a payout limit with `amount: type(uint224).max`.
|
|
109
|
+
- **`groupId` (uint256) vs `currency` (uint32) are different types for the same address.** `JBSplitGroup.groupId` is `uint256(uint160(tokenAddress))` while `JBAccountingContext.currency` is `uint32(uint160(tokenAddress))`. These truncate differently — only `NATIVE_TOKEN` (0x000000000000000000000000000000000000EEEe) matches by coincidence. Don't confuse them.
|
|
110
|
+
- **`JBAccountingContext.currency` is NOT `baseCurrency` — by design.** `baseCurrency` in ruleset metadata uses abstract real-world values (1 = ETH, 2 = USD) so rulesets are portable across chains — `baseCurrency=2` means "issue X tokens per USD" whether on Ethereum, Base, or Arbitrum. `JBAccountingContext.currency` uses token-derived values (`uint32(uint160(tokenAddress))`) because terminals track specific tokens at specific addresses — e.g. NATIVE_TOKEN = 61166, USDC on Ethereum = 909516616, USDC on Base = 3169378579. `JBPrices` mediates between the two: it converts token-derived currencies to/from abstract currencies (e.g. USDC token → USD concept, NATIVE_TOKEN → ETH concept) so that payout limits denominated in USD work correctly regardless of which token the terminal holds. The separation is what makes cross-chain consistency possible: same ruleset, different terminal accounting per chain.
|
|
111
|
+
- **Don't queue multiple identical rulesets.** A ruleset with a `duration` automatically cycles — no need to queue copies. Queue multiple rulesets only when configuration actually changes between periods (e.g. different weight, splits, or limits).
|
|
112
|
+
- **`NATIVE_TOKEN` represents a different token on each chain.** `NATIVE_TOKEN` (`0x000000000000000000000000000000000000EEEe`) is the token received via `msg.value` — ETH on Ethereum/Base/Optimism/Arbitrum, CELO on Celo, etc. Its currency is `uint32(uint160(NATIVE_TOKEN))` = 61166. A `JBMatchingPriceFeed` (returns 1:1) is deployed for `ETH:NATIVE_TOKEN` on ETH-native chains so that `baseCurrency=ETH` resolves correctly to the native token. On non-ETH-native chains, a different price feed would be needed.
|
|
113
|
+
- **Noop hook specifications are informational-only.** `noop = true` + `amount != 0` reverts with `JBTerminalStore_NoopHookSpecHasAmount`. Data hooks use noop specs to return diagnostics to preview clients without triggering a hook callback. The `noop` flag only suppresses the callback — parameter overrides (weight, tax rate, supply) from the data hook still apply.
|
|
114
|
+
|
|
115
|
+
## Permission IDs
|
|
116
|
+
|
|
117
|
+
Quick-reference for the most common `JBPermissionIds` values (from `@bananapus/permission-ids-v6`). Pass these to `JBPermissions.setPermissionsFor()`.
|
|
118
|
+
|
|
119
|
+
| ID | Name | Gates |
|
|
120
|
+
|----|------|-------|
|
|
121
|
+
| `1` | `ROOT` | All permissions for the scoped project. Cannot be combined with `projectId = 0`. |
|
|
122
|
+
| `2` | `QUEUE_RULESETS` | `JBController.queueRulesetsOf` |
|
|
123
|
+
| `3` | `LAUNCH_RULESETS` | `JBController.launchRulesetsFor` |
|
|
124
|
+
| `4` | `CASH_OUT_TOKENS` | `JBMultiTerminal.cashOutTokensOf` |
|
|
125
|
+
| `5` | `SEND_PAYOUTS` | `JBMultiTerminal.sendPayoutsOf` |
|
|
126
|
+
| `6` | `MIGRATE_TERMINAL` | `JBMultiTerminal.migrateBalanceOf` |
|
|
127
|
+
| `7` | `SET_PROJECT_URI` | `JBController.setUriOf` |
|
|
128
|
+
| `8` | `DEPLOY_ERC20` | `JBController.deployERC20For` |
|
|
129
|
+
| `9` | `SET_TOKEN` | `JBController.setTokenFor` |
|
|
130
|
+
| `10` | `MINT_TOKENS` | `JBController.mintTokensOf` |
|
|
131
|
+
| `11` | `BURN_TOKENS` | `JBController.burnTokensOf` |
|
|
132
|
+
| `12` | `CLAIM_TOKENS` | `JBController.claimTokensFor` |
|
|
133
|
+
| `13` | `TRANSFER_CREDITS` | `JBController.transferCreditsFrom` |
|
|
134
|
+
| `14` | `SET_CONTROLLER` | `JBDirectory.setControllerOf` |
|
|
135
|
+
| `15` | `SET_TERMINALS` | `JBDirectory.setTerminalsOf` (can remove primary terminal) |
|
|
136
|
+
| `16` | `SET_PRIMARY_TERMINAL` | `JBDirectory.setPrimaryTerminalOf` (also requires `ADD_TERMINALS` if the terminal is not already in the project's list) |
|
|
137
|
+
| `17` | `USE_ALLOWANCE` | `JBMultiTerminal.useAllowanceOf` |
|
|
138
|
+
| `18` | `SET_SPLIT_GROUPS` | `JBController.setSplitGroupsOf` |
|
|
139
|
+
| `19` | `ADD_PRICE_FEED` | `JBController.addPriceFeedFor` |
|
|
140
|
+
| `20` | `ADD_ACCOUNTING_CONTEXTS` | `JBMultiTerminal.addAccountingContextsFor` |
|
|
141
|
+
| `21` | `SET_TOKEN_METADATA` | `JBController.setTokenMetadataOf` |
|
|
142
|
+
|
|
143
|
+
IDs 22-33 are used by extension contracts (721 hook, buyback hook, router terminal, suckers).
|
|
144
|
+
|
|
145
|
+
## Common Errors
|
|
146
|
+
|
|
147
|
+
Errors an agent is most likely to encounter. All are custom errors (revert with selector).
|
|
148
|
+
|
|
149
|
+
| Error | Contract | When |
|
|
150
|
+
|-------|----------|------|
|
|
151
|
+
| `JBPermissioned_Unauthorized` | `JBPermissioned` | Caller lacks the required permission ID for the project. |
|
|
152
|
+
| `JBController_RulesetsArrayEmpty` | `JBController` | `launchProjectFor` / `queueRulesetsOf` called with empty rulesets array. |
|
|
153
|
+
| `JBController_RulesetsAlreadyLaunched` | `JBController` | `launchRulesetsFor` called on a project that already has rulesets. |
|
|
154
|
+
| `JBController_MintNotAllowedAndNotTerminalOrHook` | `JBController` | `mintTokensOf` called but `allowOwnerMinting` is false and caller is not a terminal/hook. |
|
|
155
|
+
| `JBController_ZeroTokensToMint` | `JBController` | `mintTokensOf` called with `tokenCount = 0`. |
|
|
156
|
+
| `JBController_ZeroTokensToBurn` | `JBController` | `burnTokensOf` called with `tokenCount = 0`. |
|
|
157
|
+
| `JBController_NoReservedTokens` | `JBController` | `sendReservedTokensToSplitsOf` called but no pending reserved tokens. |
|
|
158
|
+
| `JBController_CreditTransfersPaused` | `JBController` | `transferCreditsFrom` called but `pauseCreditTransfers` is set in ruleset. |
|
|
159
|
+
| `JBController_RulesetSetTokenNotAllowed` | `JBController` | `setTokenFor` called but `allowSetCustomToken` is false in ruleset. |
|
|
160
|
+
| `JBController_AddingPriceFeedNotAllowed` | `JBController` | `addPriceFeedFor` called but `allowAddPriceFeed` is false in ruleset. |
|
|
161
|
+
| `JBController_InvalidReservedPercent` | `JBController` | `reservedPercent` exceeds `MAX_RESERVED_PERCENT` (10,000). |
|
|
162
|
+
| `JBController_InvalidCashOutTaxRate` | `JBController` | `cashOutTaxRate` exceeds `MAX_CASH_OUT_TAX_RATE` (10,000). |
|
|
163
|
+
| `JBMultiTerminal_UnderMin` | `JBMultiTerminal` | Returned value was below the caller-specified minimum for minting, payouts, or cash outs. |
|
|
164
|
+
| `JBMultiTerminal_TokenNotAccepted` | `JBMultiTerminal` | Token has no accounting context for the project in this terminal. |
|
|
165
|
+
| `JBMultiTerminal_NoMsgValueAllowed` | `JBMultiTerminal` | `msg.value > 0` sent with an ERC-20 payment (not `NATIVE_TOKEN`). |
|
|
166
|
+
| `JBMultiTerminal_PermitAllowanceNotEnough` | `JBMultiTerminal` | Permit2 allowance insufficient for the payment amount. |
|
|
167
|
+
| `JBTerminalStore_RulesetPaymentPaused` | `JBTerminalStore` | `pausePay` is set in the current ruleset. |
|
|
168
|
+
| `JBTerminalStore_RulesetNotFound` | `JBTerminalStore` | No ruleset exists for the project (not launched). |
|
|
169
|
+
| `JBTerminalStore_InadequateControllerPayoutLimit` | `JBTerminalStore` | `sendPayoutsOf` amount exceeds the payout limit for this cycle. |
|
|
170
|
+
| `JBTerminalStore_InadequateControllerAllowance` | `JBTerminalStore` | `useAllowanceOf` amount exceeds the surplus allowance. |
|
|
171
|
+
| `JBTerminalStore_InadequateTerminalStoreBalance` | `JBTerminalStore` | Withdrawal exceeds the terminal's recorded balance. |
|
|
172
|
+
| `JBTerminalStore_InsufficientTokens` | `JBTerminalStore` | Cash out count exceeds the holder's token balance. |
|
|
173
|
+
| `JBTerminalStore_TerminalMigrationNotAllowed` | `JBTerminalStore` | `migrateBalanceOf` called but `allowTerminalMigration` is false. |
|
|
174
|
+
| `JBTerminalStore_NoopHookSpecHasAmount` | `JBTerminalStore` | Data hook returned a noop spec with `amount != 0`. |
|
|
175
|
+
| `JBTerminalStore_AccountingContextAlreadySet` | `JBTerminalStore` | Accounting context already exists for that token. |
|
|
176
|
+
| `JBDirectory_SetControllerNotAllowed` | `JBDirectory` | Controller change not allowed by the current ruleset. |
|
|
177
|
+
| `JBDirectory_SetTerminalsNotAllowed` | `JBDirectory` | Terminal change not allowed by the current ruleset. |
|
|
178
|
+
| `JBDirectory_DuplicateTerminals` | `JBDirectory` | Duplicate terminal in the terminals array. |
|
|
179
|
+
| `JBTokens_ProjectAlreadyHasToken` | `JBTokens` | `deployERC20For` / `setTokenFor` called but project already has an ERC-20. |
|
|
180
|
+
| `JBTokens_InsufficientCredits` | `JBTokens` | `claimTokensFor` count exceeds credit balance. |
|
|
181
|
+
| `JBTokens_TokensMustHave18Decimals` | `JBTokens` | Custom token does not use 18 decimals. |
|
|
182
|
+
| `JBSplits_TotalPercentExceeds100` | `JBSplits` | Split percentages sum exceeds `SPLITS_TOTAL_PERCENT`. |
|
|
183
|
+
| `JBPrices_PriceFeedAlreadyExists` | `JBPrices` | Feed already set for that currency pair (immutable). |
|
|
184
|
+
| `JBPrices_PriceFeedNotFound` | `JBPrices` | No feed found for the requested currency pair. |
|
|
185
|
+
| `JBPermissions_CantSetRootPermissionForWildcardProject` | `JBPermissions` | Tried to grant ROOT with `projectId = 0` (wildcard). |
|
|
186
|
+
| `JBRulesets_InvalidWeight` | `JBRulesets` | Weight exceeds `uint112.max`. |
|
|
187
|
+
| `JBRulesets_InvalidWeightCutPercent` | `JBRulesets` | `weightCutPercent` exceeds `MAX_WEIGHT_CUT_PERCENT`. |
|
|
188
|
+
| `JBFundAccessLimits_InvalidPayoutLimitCurrencyOrdering` | `JBFundAccessLimits` | Payout limit currencies not in strictly increasing order. |
|
|
189
|
+
|
|
190
|
+
## Key Events
|
|
191
|
+
|
|
192
|
+
The most important events for indexing and off-chain monitoring. Indexed params marked with `*`.
|
|
193
|
+
|
|
194
|
+
| Event | Interface | Key Params |
|
|
195
|
+
|-------|-----------|------------|
|
|
196
|
+
| `Pay` | `IJBTerminal` | `rulesetId*`, `rulesetCycleNumber*`, `projectId*`, `payer`, `beneficiary`, `amount`, `newlyIssuedTokenCount` |
|
|
197
|
+
| `CashOutTokens` | `IJBCashOutTerminal` | `rulesetId*`, `rulesetCycleNumber*`, `projectId*`, `holder`, `beneficiary`, `cashOutCount`, `cashOutTaxRate`, `reclaimAmount` |
|
|
198
|
+
| `SendPayouts` | `IJBPayoutTerminal` | `rulesetId*`, `rulesetCycleNumber*`, `projectId*`, `projectOwner`, `amount`, `amountPaidOut`, `fee`, `netLeftoverPayoutAmount` |
|
|
199
|
+
| `SendPayoutToSplit` | `IJBPayoutTerminal` | `projectId*`, `rulesetId*`, `group*`, `split`, `amount`, `netAmount` |
|
|
200
|
+
| `UseAllowance` | `IJBPayoutTerminal` | `rulesetId*`, `rulesetCycleNumber*`, `projectId*`, `beneficiary`, `amount`, `netAmountPaidOut` |
|
|
201
|
+
| `MintTokens` | `IJBController` | `beneficiary*`, `projectId*`, `tokenCount`, `beneficiaryTokenCount`, `reservedPercent` |
|
|
202
|
+
| `BurnTokens` | `IJBController` | `holder*`, `projectId*`, `tokenCount` |
|
|
203
|
+
| `SendReservedTokensToSplits` | `IJBController` | `rulesetId*`, `rulesetCycleNumber*`, `projectId*`, `owner`, `tokenCount`, `leftoverAmount` |
|
|
204
|
+
| `SendReservedTokensToSplit` | `IJBController` | `projectId*`, `rulesetId*`, `groupId*`, `split`, `tokenCount` |
|
|
205
|
+
| `SplitHookReverted` | `IJBController` | `projectId*`, `hook`, `reason` |
|
|
206
|
+
| `LaunchProject` | `IJBController` | `rulesetId`, `projectId`, `projectUri` |
|
|
207
|
+
| `QueueRulesets` | `IJBController` | `rulesetId`, `projectId` |
|
|
208
|
+
| `DeployERC20` | `IJBController` | `projectId*`, `deployer*`, `salt`, `saltHash`, `caller` |
|
|
209
|
+
| `SetUri` | `IJBController` | `projectId*`, `uri` |
|
|
210
|
+
| `AddToBalance` | `IJBTerminal` | `projectId*`, `amount`, `returnedFees` |
|
|
211
|
+
| `MigrateTerminal` | `IJBTerminal` | `projectId*`, `token*`, `to*`, `amount` |
|
|
212
|
+
| `HoldFee` | `IJBFeeTerminal` | `projectId*`, `token*`, `amount*`, `fee`, `beneficiary` |
|
|
213
|
+
| `ProcessFee` | `IJBFeeTerminal` | `projectId*`, `token*`, `amount*`, `wasHeld`, `beneficiary` |
|
|
214
|
+
| `ReturnHeldFees` | `IJBFeeTerminal` | `projectId*`, `token*`, `amount*`, `returnedFees`, `leftoverAmount` |
|
|
215
|
+
| `Create` | `IJBProjects` | `projectId*`, `owner*` |
|
|
216
|
+
| `OperatorPermissionsSet` | `IJBPermissions` | (operator, account, projectId, permissionIds, packed, caller) |
|
|
217
|
+
| `RulesetQueued` | `IJBRulesets` | (rulesetId, projectId, duration, weight, weightCutPercent, approvalHook, metadata, mustStartAtOrAfter, caller) |
|
|
218
|
+
| `SetSplit` | `IJBSplits` | (projectId, rulesetId, groupId, split, caller) |
|
|
219
|
+
| `AddPriceFeed` | `IJBPrices` | (projectId, pricingCurrency, unitCurrency, feed, caller) |
|
|
220
|
+
|
|
221
|
+
## Hook Interface Return Types
|
|
222
|
+
|
|
223
|
+
### `IJBRulesetDataHook.beforePayRecordedWith()`
|
|
224
|
+
|
|
225
|
+
```solidity
|
|
226
|
+
function beforePayRecordedWith(JBBeforePayRecordedContext calldata context)
|
|
227
|
+
external view
|
|
228
|
+
returns (
|
|
229
|
+
uint256 weight, // Overrides the ruleset's weight for token issuance
|
|
230
|
+
JBPayHookSpecification[] memory hookSpecifications // Pay hooks to call + amounts to forward
|
|
231
|
+
);
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The data hook can override `weight` to change how many tokens the payer receives. Return `hookSpecifications` to redirect funds to pay hooks (each spec has `hook`, `amount`, `metadata`, and `noop`). An empty array means all funds stay in the terminal balance.
|
|
235
|
+
|
|
236
|
+
### `IJBRulesetDataHook.beforeCashOutRecordedWith()`
|
|
237
|
+
|
|
238
|
+
```solidity
|
|
239
|
+
function beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata context)
|
|
240
|
+
external view
|
|
241
|
+
returns (
|
|
242
|
+
uint256 cashOutTaxRate, // Overrides the ruleset's cash out tax rate
|
|
243
|
+
uint256 effectiveCashOutCount, // Overrides the token count used for pricing only
|
|
244
|
+
uint256 effectiveTotalSupply, // Overrides total supply used for bonding curve calc
|
|
245
|
+
JBCashOutHookSpecification[] memory hookSpecifications // Cash out hooks to call + amounts to forward
|
|
246
|
+
);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The data hook can override `cashOutTaxRate` (0 = proportional, 10000 = nothing reclaimable), `effectiveCashOutCount`, and `effectiveTotalSupply` to shift cash-out pricing, and return `hookSpecifications` to redirect reclaimed funds to cash out hooks. The terminal still burns the caller-supplied `cashOutCount`.
|
|
250
|
+
|
|
251
|
+
### `IJBRulesetDataHook.hasMintPermissionFor()`
|
|
252
|
+
|
|
253
|
+
```solidity
|
|
254
|
+
function hasMintPermissionFor(uint256 projectId, JBRuleset memory ruleset, address addr)
|
|
255
|
+
external view returns (bool flag);
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Returns whether `addr` is allowed to mint tokens for the project. Called by `JBController.mintTokensOf` when the caller is not the owner and `allowOwnerMinting` is false -- the data hook can grant mint permission to specific addresses (e.g. suckers for omnichain bridging).
|
|
259
|
+
|
|
260
|
+
## Example Integration
|
|
261
|
+
|
|
262
|
+
```solidity
|
|
263
|
+
// SPDX-License-Identifier: MIT
|
|
264
|
+
pragma solidity 0.8.28;
|
|
265
|
+
|
|
266
|
+
import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
267
|
+
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
268
|
+
import {IJBMultiTerminal} from "@bananapus/core-v6/src/interfaces/IJBMultiTerminal.sol";
|
|
269
|
+
import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
|
|
270
|
+
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
271
|
+
|
|
272
|
+
contract PayProject {
|
|
273
|
+
IJBDirectory public immutable DIRECTORY;
|
|
274
|
+
|
|
275
|
+
constructor(IJBDirectory directory) {
|
|
276
|
+
DIRECTORY = directory;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/// @notice Pay a project with native ETH and receive project tokens.
|
|
280
|
+
function payProject(uint256 projectId) external payable returns (uint256 tokenCount) {
|
|
281
|
+
// Look up the project's primary terminal for native ETH.
|
|
282
|
+
IJBTerminal terminal = DIRECTORY.primaryTerminalOf(projectId, JBConstants.NATIVE_TOKEN);
|
|
283
|
+
require(address(terminal) != address(0), "No terminal");
|
|
284
|
+
|
|
285
|
+
// Pay the project. The msg.sender receives the minted tokens.
|
|
286
|
+
tokenCount = IJBMultiTerminal(address(terminal)).pay{value: msg.value}({
|
|
287
|
+
projectId: projectId,
|
|
288
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
289
|
+
amount: msg.value,
|
|
290
|
+
beneficiary: msg.sender,
|
|
291
|
+
minReturnedTokens: 0,
|
|
292
|
+
memo: "Paid via PayProject",
|
|
293
|
+
metadata: ""
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
```
|
package/script/Deploy.s.sol
CHANGED
|
@@ -34,6 +34,9 @@ contract Deploy is Script, Sphinx {
|
|
|
34
34
|
address private MANAGER;
|
|
35
35
|
|
|
36
36
|
/// @notice The address that will own the fee-project.
|
|
37
|
+
/// @dev Core deployment transfers project `#1` to this owner, but does not fully activate fee collection on its
|
|
38
|
+
/// own. Production fee collection only starts once the fee project's controller, terminals, and accounting
|
|
39
|
+
/// contexts are configured by follow-up deployment steps.
|
|
37
40
|
// forge-lint: disable-next-line(mixed-case-variable)
|
|
38
41
|
address private FEE_PROJECT_OWNER;
|
|
39
42
|
|
|
@@ -52,7 +55,8 @@ contract Deploy is Script, Sphinx {
|
|
|
52
55
|
function run() public sphinx {
|
|
53
56
|
// Set the manager, this can be changed and won't affect deployment addresses.
|
|
54
57
|
MANAGER = safeAddress();
|
|
55
|
-
// Set the owner of the fee project to be the project multisig.
|
|
58
|
+
// Set the owner of the fee project to be the project multisig. This does not by itself make fee collection
|
|
59
|
+
// live; project `#1` still needs its follow-up controller/terminal/accounting-context configuration.
|
|
56
60
|
FEE_PROJECT_OWNER = safeAddress();
|
|
57
61
|
|
|
58
62
|
// Deploy the protocol.
|
|
@@ -110,7 +114,8 @@ contract Deploy is Script, Sphinx {
|
|
|
110
114
|
projects.transferOwnership(MANAGER);
|
|
111
115
|
}
|
|
112
116
|
|
|
113
|
-
// Transfer ownership to the fee project owner.
|
|
117
|
+
// Transfer ownership to the fee project owner. Follow-up deployment steps must still finish configuring
|
|
118
|
+
// project `#1` before protocol fees are collected instead of being forgiven back to payer projects.
|
|
114
119
|
if (FEE_PROJECT_OWNER != safeAddress() && FEE_PROJECT_OWNER != address(0)) {
|
|
115
120
|
projects.safeTransferFrom({from: safeAddress(), to: FEE_PROJECT_OWNER, tokenId: 1});
|
|
116
121
|
}
|
|
@@ -41,8 +41,8 @@ contract DeployPeriphery is Script, Sphinx {
|
|
|
41
41
|
/// rulesets on any project via the JBController (bypassing normal JBPermissions checks). A compromised or
|
|
42
42
|
/// incorrect operator address could manipulate any project's rulesets across chains.
|
|
43
43
|
/// @dev This address should correspond to the deterministic CREATE2 deployment of the omnichain deployer contract
|
|
44
|
-
/// from the nana-omnichain-deployers-v6 repository.
|
|
45
|
-
/// before running this script.
|
|
44
|
+
/// from the nana-omnichain-deployers-v6 repository. This script only enforces that the address is nonzero, so
|
|
45
|
+
/// operators must verify it matches the intended deployment on every target chain before running this script.
|
|
46
46
|
// forge-lint: disable-next-line(mixed-case-variable)
|
|
47
47
|
address private OMNICHAIN_RULESET_OPERATOR = address(0x8f5DED85c40b50d223269C1F922A056E72101590);
|
|
48
48
|
|
|
@@ -66,7 +66,8 @@ contract DeployPeriphery is Script, Sphinx {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
function deploy() public sphinx {
|
|
69
|
-
// Validate the omnichain ruleset operator is set. See TRUST ASSUMPTION above.
|
|
69
|
+
// Validate the omnichain ruleset operator is set. See TRUST ASSUMPTION above. This is intentionally a
|
|
70
|
+
// nonzero-only check; correctness of the configured operator is verified out of band.
|
|
70
71
|
require(OMNICHAIN_RULESET_OPERATOR != address(0), "Omnichain ruleset operator not set");
|
|
71
72
|
|
|
72
73
|
// Deploy the ETH/USD price feed.
|
|
@@ -155,6 +156,19 @@ contract DeployPeriphery is Script, Sphinx {
|
|
|
155
156
|
feed: feed
|
|
156
157
|
}) {}
|
|
157
158
|
catch {}
|
|
159
|
+
// `addPriceFeedFor` can revert if this feed was already registered. Still require the feed to exist so a
|
|
160
|
+
// different failure mode can't silently skip this required deployment step.
|
|
161
|
+
require(
|
|
162
|
+
address(
|
|
163
|
+
core.prices
|
|
164
|
+
.priceFeedFor({
|
|
165
|
+
projectId: 0,
|
|
166
|
+
pricingCurrency: JBCurrencyIds.USD,
|
|
167
|
+
unitCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
168
|
+
})
|
|
169
|
+
) != address(0),
|
|
170
|
+
"Missing USD/native price feed"
|
|
171
|
+
);
|
|
158
172
|
|
|
159
173
|
// WARN: We are using the same price feed as the native token for the USD price feed. Which is only valid on
|
|
160
174
|
// chains where Ether is the native asset. We *NEED* to update this when we deploy to a non-ether chain!
|
|
@@ -163,6 +177,15 @@ contract DeployPeriphery is Script, Sphinx {
|
|
|
163
177
|
projectId: 0, pricingCurrency: JBCurrencyIds.USD, unitCurrency: JBCurrencyIds.ETH, feed: feed
|
|
164
178
|
}) {}
|
|
165
179
|
catch {}
|
|
180
|
+
// `addPriceFeedFor` can revert if this feed was already registered. Still require the feed to exist so a
|
|
181
|
+
// different failure mode can't silently skip this required deployment step.
|
|
182
|
+
require(
|
|
183
|
+
address(
|
|
184
|
+
core.prices
|
|
185
|
+
.priceFeedFor({projectId: 0, pricingCurrency: JBCurrencyIds.USD, unitCurrency: JBCurrencyIds.ETH})
|
|
186
|
+
) != address(0),
|
|
187
|
+
"Missing USD/ETH price feed"
|
|
188
|
+
);
|
|
166
189
|
|
|
167
190
|
// If the native asset for this chain is ether, then the conversion from native asset to ether is 1:1.
|
|
168
191
|
// NOTE: We need to refactor this the moment we add a chain where its native token is *NOT* ether.
|
|
@@ -175,6 +198,19 @@ contract DeployPeriphery is Script, Sphinx {
|
|
|
175
198
|
feed: matchingPriceFeed
|
|
176
199
|
}) {}
|
|
177
200
|
catch {}
|
|
201
|
+
// `addPriceFeedFor` can revert if this feed was already registered. Still require the feed to exist so a
|
|
202
|
+
// different failure mode can't silently skip this required deployment step.
|
|
203
|
+
require(
|
|
204
|
+
address(
|
|
205
|
+
core.prices
|
|
206
|
+
.priceFeedFor({
|
|
207
|
+
projectId: 0,
|
|
208
|
+
pricingCurrency: JBCurrencyIds.ETH,
|
|
209
|
+
unitCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
210
|
+
})
|
|
211
|
+
) != address(0),
|
|
212
|
+
"Missing ETH/native price feed"
|
|
213
|
+
);
|
|
178
214
|
|
|
179
215
|
// Deploy the USDC/USD price feed.
|
|
180
216
|
_deployUSDCFeed(L2GracePeriod);
|
|
@@ -283,11 +319,22 @@ contract DeployPeriphery is Script, Sphinx {
|
|
|
283
319
|
require(address(usdcFeed) != address(0), "Invalid USDC price feed");
|
|
284
320
|
|
|
285
321
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
322
|
+
uint32 usdcCurrencyId = uint32(uint160(usdc));
|
|
323
|
+
|
|
286
324
|
try core.prices
|
|
287
325
|
.addPriceFeedFor({
|
|
288
|
-
projectId: 0, pricingCurrency: JBCurrencyIds.USD, unitCurrency:
|
|
326
|
+
projectId: 0, pricingCurrency: JBCurrencyIds.USD, unitCurrency: usdcCurrencyId, feed: usdcFeed
|
|
289
327
|
}) {}
|
|
290
328
|
catch {}
|
|
329
|
+
// `addPriceFeedFor` can revert if this feed was already registered. Still require the feed to exist so a
|
|
330
|
+
// different failure mode can't silently skip this required deployment step.
|
|
331
|
+
require(
|
|
332
|
+
address(
|
|
333
|
+
core.prices
|
|
334
|
+
.priceFeedFor({projectId: 0, pricingCurrency: JBCurrencyIds.USD, unitCurrency: usdcCurrencyId})
|
|
335
|
+
) != address(0),
|
|
336
|
+
"Missing USD/USDC price feed"
|
|
337
|
+
);
|
|
291
338
|
}
|
|
292
339
|
|
|
293
340
|
/// @dev This helper predicts addresses using the Arachnid CREATE2 deployer, but actual deployments go through
|