@bananapus/core-v6 0.0.33 → 0.0.35
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 +75 -348
- package/ARCHITECTURE.md +86 -44
- package/AUDIT_INSTRUCTIONS.md +29 -42
- package/README.md +22 -3
- package/RISKS.md +45 -1
- package/SKILLS.md +16 -4
- package/USER_JOURNEYS.md +130 -30
- package/foundry.toml +2 -0
- package/package.json +2 -2
- package/references/entrypoints.md +1 -1
- package/script/Deploy.s.sol +2 -1
- package/src/JBERC20.sol +100 -30
- package/src/JBTerminalStore.sol +64 -23
- package/src/JBTokens.sol +1 -1
- package/src/abstract/JBPermissioned.sol +28 -0
- package/src/interfaces/IJBRulesetDataHook.sol +6 -1
- package/src/interfaces/IJBToken.sol +3 -3
- package/src/structs/JBAccountingContext.sol +0 -1
- package/src/structs/JBAfterCashOutRecordedContext.sol +0 -1
- package/src/structs/JBAfterPayRecordedContext.sol +0 -1
- package/src/structs/JBBeforeCashOutRecordedContext.sol +0 -1
- package/src/structs/JBBeforePayRecordedContext.sol +0 -1
- package/src/structs/JBCashOutHookSpecification.sol +0 -1
- package/src/structs/JBCurrencyAmount.sol +0 -1
- package/src/structs/JBFee.sol +0 -1
- package/src/structs/JBFundAccessLimitGroup.sol +0 -1
- package/src/structs/JBPayHookSpecification.sol +0 -1
- package/src/structs/JBPermissionsData.sol +0 -1
- package/src/structs/JBRuleset.sol +0 -1
- package/src/structs/JBRulesetConfig.sol +0 -1
- package/src/structs/JBRulesetMetadata.sol +0 -1
- package/src/structs/JBRulesetWeightCache.sol +0 -1
- package/src/structs/JBRulesetWithMetadata.sol +0 -1
- package/src/structs/JBSingleAllowance.sol +0 -1
- package/src/structs/JBSplit.sol +0 -1
- package/src/structs/JBSplitGroup.sol +0 -1
- package/src/structs/JBSplitHookContext.sol +0 -1
- package/src/structs/JBTerminalConfig.sol +0 -1
- package/src/structs/JBTokenAmount.sol +0 -1
- package/test/TestCashOutHooks.sol +12 -2
- package/test/TestDataHookFuzzing.sol +4 -4
- package/test/TestForwardedTokenConsumption.sol +7 -1
- package/test/TestJBERC20Inheritance.sol +3 -1
- package/test/TestTokenFlow.sol +2 -2
- package/test/audit/CashOutReenterPay.t.sol +5 -0
- package/test/audit/CodexHeldFeeRounding.t.sol +159 -0
- package/test/helpers/TestBaseWorkflow.sol +1 -1
- package/test/units/static/JBERC20/JBERC20Setup.sol +8 -3
- package/test/units/static/JBERC20/TestInitialize.sol +12 -13
- package/test/units/static/JBERC20/TestName.sol +1 -1
- package/test/units/static/JBERC20/TestNonces.sol +2 -1
- package/test/units/static/JBERC20/TestSymbol.sol +1 -1
- package/test/units/static/JBTerminalStore/TestPreviewCashOutFrom.sol +1 -1
- package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +4 -4
- package/test/units/static/JBTokens/JBTokensSetup.sol +5 -1
|
@@ -34,6 +34,34 @@ abstract contract JBPermissioned is Context, IJBPermissioned {
|
|
|
34
34
|
// -------------------------- internal views ------------------------- //
|
|
35
35
|
//*********************************************************************//
|
|
36
36
|
|
|
37
|
+
/// @notice Check whether an operator is the account or has the relevant permission.
|
|
38
|
+
/// @param operator The address to check.
|
|
39
|
+
/// @param account The account to allow.
|
|
40
|
+
/// @param projectId The project ID to check the permission under.
|
|
41
|
+
/// @param permissionId The required permission ID. The operator must have this permission within the specified
|
|
42
|
+
/// project ID.
|
|
43
|
+
/// @return Whether the operator is the account or has the permission.
|
|
44
|
+
function _hasPermissionFrom(
|
|
45
|
+
address operator,
|
|
46
|
+
address account,
|
|
47
|
+
uint256 projectId,
|
|
48
|
+
uint256 permissionId
|
|
49
|
+
)
|
|
50
|
+
internal
|
|
51
|
+
view
|
|
52
|
+
returns (bool)
|
|
53
|
+
{
|
|
54
|
+
return operator == account
|
|
55
|
+
|| PERMISSIONS.hasPermission({
|
|
56
|
+
operator: operator,
|
|
57
|
+
account: account,
|
|
58
|
+
projectId: projectId,
|
|
59
|
+
permissionId: permissionId,
|
|
60
|
+
includeRoot: true,
|
|
61
|
+
includeWildcardProjectId: true
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
37
65
|
/// @notice Require the message sender to be the account or have the relevant permission.
|
|
38
66
|
/// @param account The account to allow.
|
|
39
67
|
/// @param projectId The project ID to check the permission under.
|
|
@@ -19,7 +19,11 @@ interface IJBRulesetDataHook is IERC165 {
|
|
|
19
19
|
/// @return cashOutTaxRate The rate determining the reclaimable amount for a given surplus and token supply.
|
|
20
20
|
/// @return effectiveCashOutCount The effective token count to use for pricing the cash out. The terminal still
|
|
21
21
|
/// burns the caller-supplied token count.
|
|
22
|
-
/// @return effectiveTotalSupply The effective total supply to use for
|
|
22
|
+
/// @return effectiveTotalSupply The effective total supply to use for both the proportional reclaim and tax
|
|
23
|
+
/// calculations. For omnichain projects, this should include tokens on other chains so the tax cannot be bypassed.
|
|
24
|
+
/// @return effectiveSurplusValue The surplus value to use for the bonding curve calculation, denominated in the
|
|
25
|
+
/// same token, decimals, and currency as `context.surplus`. The terminal caps the reclaim at locally available
|
|
26
|
+
/// funds.
|
|
23
27
|
/// @return hookSpecifications The amount and data to send to cash out hooks instead of returning to the
|
|
24
28
|
/// beneficiary.
|
|
25
29
|
function beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata context)
|
|
@@ -29,6 +33,7 @@ interface IJBRulesetDataHook is IERC165 {
|
|
|
29
33
|
uint256 cashOutTaxRate,
|
|
30
34
|
uint256 effectiveCashOutCount,
|
|
31
35
|
uint256 effectiveTotalSupply,
|
|
36
|
+
uint256 effectiveSurplusValue,
|
|
32
37
|
JBCashOutHookSpecification[] memory hookSpecifications
|
|
33
38
|
);
|
|
34
39
|
|
|
@@ -26,11 +26,11 @@ interface IJBToken {
|
|
|
26
26
|
/// @param amount The amount of tokens to burn.
|
|
27
27
|
function burn(address account, uint256 amount) external;
|
|
28
28
|
|
|
29
|
-
/// @notice Initializes the token with a name, symbol, and
|
|
29
|
+
/// @notice Initializes the token with a name, symbol, and the JBTokens contract.
|
|
30
30
|
/// @param name The token's name.
|
|
31
31
|
/// @param symbol The token's symbol.
|
|
32
|
-
/// @param
|
|
33
|
-
function initialize(string memory name, string memory symbol, address
|
|
32
|
+
/// @param tokens The JBTokens contract that manages this token.
|
|
33
|
+
function initialize(string memory name, string memory symbol, address tokens) external;
|
|
34
34
|
|
|
35
35
|
/// @notice Mints tokens to an account.
|
|
36
36
|
/// @param account The address to mint tokens to.
|
|
@@ -5,7 +5,6 @@ pragma solidity ^0.8.0;
|
|
|
5
5
|
/// @custom:member decimals The number of decimals expected in that token's fixed point accounting.
|
|
6
6
|
/// @custom:member currency The currency that the token is priced in terms of. By convention, this is
|
|
7
7
|
/// `uint32(uint160(tokenAddress))` for tokens, or a constant ID from e.g. `JBCurrencyIds` for other currencies.
|
|
8
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
9
8
|
struct JBAccountingContext {
|
|
10
9
|
address token;
|
|
11
10
|
uint8 decimals;
|
|
@@ -16,7 +16,6 @@ import {JBTokenAmount} from "./JBTokenAmount.sol";
|
|
|
16
16
|
/// @custom:member beneficiary The address the reclaimed amount will be sent to.
|
|
17
17
|
/// @custom:member hookMetadata Extra data specified by the data hook, which is sent to the cash out hook.
|
|
18
18
|
/// @custom:member cashOutMetadata Extra data specified by the account cashing out, which is sent to the cash out hook.
|
|
19
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
20
19
|
struct JBAfterCashOutRecordedContext {
|
|
21
20
|
address holder;
|
|
22
21
|
uint256 projectId;
|
|
@@ -15,7 +15,6 @@ import {JBTokenAmount} from "./JBTokenAmount.sol";
|
|
|
15
15
|
/// @custom:member beneficiary The address which receives any tokens this payment yields.
|
|
16
16
|
/// @custom:member hookMetadata Extra data specified by the data hook, which is sent to the pay hook.
|
|
17
17
|
/// @custom:member payerMetadata Extra data specified by the payer, which is sent to the pay hook.
|
|
18
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
19
18
|
struct JBAfterPayRecordedContext {
|
|
20
19
|
address payer;
|
|
21
20
|
uint256 projectId;
|
|
@@ -20,7 +20,6 @@ import {JBTokenAmount} from "./JBTokenAmount.sol";
|
|
|
20
20
|
/// that charge their own fees — they can skip fees when value stays in the protocol (e.g. project-to-project
|
|
21
21
|
/// routing).
|
|
22
22
|
/// @custom:member metadata Extra data provided by the casher.
|
|
23
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
24
23
|
struct JBBeforeCashOutRecordedContext {
|
|
25
24
|
address terminal;
|
|
26
25
|
address holder;
|
|
@@ -15,7 +15,6 @@ import {JBTokenAmount} from "./JBTokenAmount.sol";
|
|
|
15
15
|
/// @custom:member weight The weight of the ruleset during which the payment is being made.
|
|
16
16
|
/// @custom:member reservedPercent The reserved percent of the ruleset the payment is being made during.
|
|
17
17
|
/// @custom:member metadata Extra data specified by the payer.
|
|
18
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
19
18
|
struct JBBeforePayRecordedContext {
|
|
20
19
|
address terminal;
|
|
21
20
|
address payer;
|
|
@@ -9,7 +9,6 @@ import {IJBCashOutHook} from "../interfaces/IJBCashOutHook.sol";
|
|
|
9
9
|
/// @custom:member noop A flag indicating if the hook callback should be skipped.
|
|
10
10
|
/// @custom:member amount The amount to send to the hook.
|
|
11
11
|
/// @custom:member metadata Metadata to pass to the hook.
|
|
12
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
13
12
|
struct JBCashOutHookSpecification {
|
|
14
13
|
IJBCashOutHook hook;
|
|
15
14
|
bool noop;
|
|
@@ -4,7 +4,6 @@ pragma solidity ^0.8.0;
|
|
|
4
4
|
/// @custom:member amount The amount of the currency.
|
|
5
5
|
/// @custom:member currency The currency. By convention, this is `uint32(uint160(tokenAddress))` for tokens, or a
|
|
6
6
|
/// constant ID from e.g. `JBCurrencyIds` for other currencies.
|
|
7
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
8
7
|
struct JBCurrencyAmount {
|
|
9
8
|
uint224 amount;
|
|
10
9
|
uint32 currency;
|
package/src/structs/JBFee.sol
CHANGED
|
@@ -5,7 +5,6 @@ pragma solidity ^0.8.0;
|
|
|
5
5
|
/// decimals as the terminal in which this struct was created.
|
|
6
6
|
/// @custom:member beneficiary The address that will receive the tokens that are minted as a result of the fee payment.
|
|
7
7
|
/// @custom:member unlockTimestamp The timestamp at which the fee is unlocked and can be processed.
|
|
8
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
9
8
|
struct JBFee {
|
|
10
9
|
uint256 amount;
|
|
11
10
|
address beneficiary;
|
|
@@ -20,7 +20,6 @@ import {JBCurrencyAmount} from "./JBCurrencyAmount.sol";
|
|
|
20
20
|
/// @custom:member surplusAllowances An array of surplus allowances. The surplus allowances cumulatively dictates the
|
|
21
21
|
/// maximum value of `token`s a project can pay out from its surplus (balance less payouts) in a terminal during a
|
|
22
22
|
/// ruleset. Each surplus allowance can have a unique currency and amount.
|
|
23
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
24
23
|
struct JBFundAccessLimitGroup {
|
|
25
24
|
address terminal;
|
|
26
25
|
address token;
|
|
@@ -9,7 +9,6 @@ import {IJBPayHook} from "../interfaces/IJBPayHook.sol";
|
|
|
9
9
|
/// @custom:member noop A flag indicating if the hook callback should be skipped.
|
|
10
10
|
/// @custom:member amount The amount to send to the hook.
|
|
11
11
|
/// @custom:member metadata Metadata to pass the hook.
|
|
12
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
13
12
|
struct JBPayHookSpecification {
|
|
14
13
|
IJBPayHook hook;
|
|
15
14
|
bool noop;
|
|
@@ -6,7 +6,6 @@ pragma solidity ^0.8.0;
|
|
|
6
6
|
/// permissions under this project's scope. An ID of 0 is a wildcard, which gives an operator permissions across all
|
|
7
7
|
/// projects.
|
|
8
8
|
/// @custom:member permissionIds The IDs of the permissions being given. See the `JBPermissionIds` library.
|
|
9
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
10
9
|
struct JBPermissionsData {
|
|
11
10
|
address operator;
|
|
12
11
|
uint64 projectId;
|
|
@@ -29,7 +29,6 @@ import {IJBRulesetApprovalHook} from "./../interfaces/IJBRulesetApprovalHook.sol
|
|
|
29
29
|
/// ruleset is rejected, it won't go into effect. An approval hook can be used to create rules which dictate how a
|
|
30
30
|
/// project owner can change their ruleset over time.
|
|
31
31
|
/// @custom:member metadata Extra data associated with a ruleset which can be used by other contracts.
|
|
32
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
33
32
|
struct JBRuleset {
|
|
34
33
|
uint48 cycleNumber;
|
|
35
34
|
uint48 id;
|
|
@@ -31,7 +31,6 @@ import {JBSplitGroup} from "./JBSplitGroup.sol";
|
|
|
31
31
|
/// its balance in each payment terminal while the ruleset is active. Amounts are fixed point numbers using the same
|
|
32
32
|
/// number of decimals as the corresponding terminal. The `_payoutLimit` and `_surplusAllowance` parameters must fit in
|
|
33
33
|
/// a `uint232`.
|
|
34
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
35
34
|
struct JBRulesetConfig {
|
|
36
35
|
uint48 mustStartAtOrAfter;
|
|
37
36
|
uint32 duration;
|
|
@@ -33,7 +33,6 @@ pragma solidity ^0.8.0;
|
|
|
33
33
|
/// @custom:member dataHook The data hook to use during this ruleset.
|
|
34
34
|
/// @custom:member metadata Metadata of the metadata, only the 14 least significant bits can be used, the 2 most
|
|
35
35
|
/// significant bits are disregarded.
|
|
36
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
37
36
|
struct JBRulesetMetadata {
|
|
38
37
|
uint16 reservedPercent;
|
|
39
38
|
uint16 cashOutTaxRate;
|
|
@@ -3,7 +3,6 @@ pragma solidity ^0.8.0;
|
|
|
3
3
|
|
|
4
4
|
/// @custom:member weight The cached weight value.
|
|
5
5
|
/// @custom:member weightCutMultiple The weight cut multiple that produces the given weight.
|
|
6
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
7
6
|
struct JBRulesetWeightCache {
|
|
8
7
|
uint112 weight;
|
|
9
8
|
uint168 weightCutMultiple;
|
|
@@ -6,7 +6,6 @@ import {JBRulesetMetadata} from "./JBRulesetMetadata.sol";
|
|
|
6
6
|
|
|
7
7
|
/// @custom:member ruleset The ruleset.
|
|
8
8
|
/// @custom:member metadata The ruleset's metadata.
|
|
9
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
10
9
|
struct JBRulesetWithMetadata {
|
|
11
10
|
JBRuleset ruleset;
|
|
12
11
|
JBRulesetMetadata metadata;
|
|
@@ -7,7 +7,6 @@ pragma solidity ^0.8.0;
|
|
|
7
7
|
/// @custom:member nonce An incrementing value indexed per owner,token,and spender for each signature.
|
|
8
8
|
/// @custom:member signature The signature over the permit data. Supports EOA signatures, compact signatures defined by
|
|
9
9
|
/// EIP-2098, and contract signatures defined by EIP-1271.
|
|
10
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
11
10
|
struct JBSingleAllowance {
|
|
12
11
|
uint256 sigDeadline;
|
|
13
12
|
uint160 amount;
|
package/src/structs/JBSplit.sol
CHANGED
|
@@ -30,7 +30,6 @@ import {IJBSplitHook} from "./../interfaces/IJBSplitHook.sol";
|
|
|
30
30
|
/// preserve those splits at the governance/configuration layer.
|
|
31
31
|
/// @custom:member hook A contract which will receive this split's tokens and properties, and can define custom
|
|
32
32
|
/// behavior.
|
|
33
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
34
33
|
struct JBSplit {
|
|
35
34
|
uint32 percent;
|
|
36
35
|
uint64 projectId;
|
|
@@ -6,7 +6,6 @@ import {JBSplit} from "./JBSplit.sol";
|
|
|
6
6
|
/// @custom:member groupId An identifier for the group. By convention, this ID is `uint256(uint160(tokenAddress))` for
|
|
7
7
|
/// payouts and `1` for reserved tokens.
|
|
8
8
|
/// @custom:member splits The splits in the group.
|
|
9
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
10
9
|
struct JBSplitGroup {
|
|
11
10
|
uint256 groupId;
|
|
12
11
|
JBSplit[] splits;
|
|
@@ -10,7 +10,6 @@ import {JBSplit} from "./JBSplit.sol";
|
|
|
10
10
|
/// @custom:member groupId The group the split belongs to. By convention, this ID is `uint256(uint160(tokenAddress))`
|
|
11
11
|
/// for payouts and `1` for reserved tokens.
|
|
12
12
|
/// @custom:member split The split which specified the hook.
|
|
13
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
14
13
|
struct JBSplitHookContext {
|
|
15
14
|
address token;
|
|
16
15
|
uint256 amount;
|
|
@@ -6,7 +6,6 @@ import {IJBTerminal} from "./../interfaces/IJBTerminal.sol";
|
|
|
6
6
|
|
|
7
7
|
/// @custom:member terminal The terminal to configure.
|
|
8
8
|
/// @custom:member accountingContextsToAccept The accounting contexts to accept from the terminal.
|
|
9
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
10
9
|
struct JBTerminalConfig {
|
|
11
10
|
IJBTerminal terminal;
|
|
12
11
|
JBAccountingContext[] accountingContextsToAccept;
|
|
@@ -6,7 +6,6 @@ pragma solidity ^0.8.0;
|
|
|
6
6
|
/// @custom:member currency The currency. By convention, this is `uint32(uint160(tokenAddress))` for tokens, or a
|
|
7
7
|
/// constant ID from e.g. `JBCurrencyIds` for other currencies.
|
|
8
8
|
/// @custom:member value The amount of tokens that was paid, as a fixed point number.
|
|
9
|
-
// forge-lint: disable-next-line(pascal-case-struct)
|
|
10
9
|
struct JBTokenAmount {
|
|
11
10
|
address token;
|
|
12
11
|
uint8 decimals;
|
|
@@ -207,7 +207,11 @@ contract TestCashOutHooks_Local is TestBaseWorkflow {
|
|
|
207
207
|
_DATA_HOOK,
|
|
208
208
|
abi.encodeWithSelector(IJBRulesetDataHook.beforeCashOutRecordedWith.selector),
|
|
209
209
|
abi.encode(
|
|
210
|
-
_ruleset.cashOutTaxRate(),
|
|
210
|
+
_ruleset.cashOutTaxRate(),
|
|
211
|
+
_beneficiaryTokenBalance / 2,
|
|
212
|
+
_beneficiaryTokenBalance,
|
|
213
|
+
_nativePayAmount,
|
|
214
|
+
_specifications
|
|
211
215
|
)
|
|
212
216
|
);
|
|
213
217
|
|
|
@@ -322,7 +326,13 @@ contract TestCashOutHooks_Local is TestBaseWorkflow {
|
|
|
322
326
|
vm.mockCall(
|
|
323
327
|
_DATA_HOOK,
|
|
324
328
|
abi.encodeWithSelector(IJBRulesetDataHook.beforeCashOutRecordedWith.selector),
|
|
325
|
-
abi.encode(
|
|
329
|
+
abi.encode(
|
|
330
|
+
_customCashOutTaxRate,
|
|
331
|
+
_customCashOutCount,
|
|
332
|
+
_customTotalSupply,
|
|
333
|
+
_nativeTerminalBalance, // Same as _nativePayAmount (no payouts); avoids stack depth limit.
|
|
334
|
+
_specifications
|
|
335
|
+
)
|
|
326
336
|
);
|
|
327
337
|
|
|
328
338
|
_terminal.cashOutTokensOf({
|
|
@@ -261,12 +261,12 @@ contract TestDataHookFuzzing_Local is TestBaseWorkflow {
|
|
|
261
261
|
uint256 cashOutCount = tokenBalance / 2;
|
|
262
262
|
_hookTotalSupply = bound(_hookTotalSupply, cashOutCount, tokenBalance * 10);
|
|
263
263
|
|
|
264
|
-
// Data hook returns: cashOutTaxRate=0, cashOutCount=half, custom totalSupply, no hook specs.
|
|
264
|
+
// Data hook returns: cashOutTaxRate=0, cashOutCount=half, custom totalSupply, local surplus, no hook specs.
|
|
265
265
|
JBCashOutHookSpecification[] memory _emptyCashOutSpecs = new JBCashOutHookSpecification[](0);
|
|
266
266
|
vm.mockCall(
|
|
267
267
|
_DATA_HOOK,
|
|
268
268
|
abi.encodeWithSelector(IJBRulesetDataHook.beforeCashOutRecordedWith.selector),
|
|
269
|
-
abi.encode(uint256(0), cashOutCount, _hookTotalSupply, _emptyCashOutSpecs)
|
|
269
|
+
abi.encode(uint256(0), cashOutCount, _hookTotalSupply, _payAmount, _emptyCashOutSpecs)
|
|
270
270
|
);
|
|
271
271
|
|
|
272
272
|
uint256 balanceBefore = address(this).balance;
|
|
@@ -336,7 +336,7 @@ contract TestDataHookFuzzing_Local is TestBaseWorkflow {
|
|
|
336
336
|
vm.mockCall(
|
|
337
337
|
_DATA_HOOK,
|
|
338
338
|
abi.encodeWithSelector(IJBRulesetDataHook.beforeCashOutRecordedWith.selector),
|
|
339
|
-
abi.encode(uint256(0), cashOutCount, tokenBalance, _specs)
|
|
339
|
+
abi.encode(uint256(0), cashOutCount, tokenBalance, _payAmount, _specs)
|
|
340
340
|
);
|
|
341
341
|
|
|
342
342
|
// Mock the cash out hook call.
|
|
@@ -500,7 +500,7 @@ contract TestDataHookFuzzing_Local is TestBaseWorkflow {
|
|
|
500
500
|
vm.mockCall(
|
|
501
501
|
_DATA_HOOK,
|
|
502
502
|
abi.encodeWithSelector(IJBRulesetDataHook.beforeCashOutRecordedWith.selector),
|
|
503
|
-
abi.encode(uint256(0), tokenBalance, tokenBalance, _specs)
|
|
503
|
+
abi.encode(uint256(0), tokenBalance, tokenBalance, _payAmount, _specs)
|
|
504
504
|
);
|
|
505
505
|
|
|
506
506
|
vm.expectRevert(
|
|
@@ -145,7 +145,13 @@ contract TestForwardedTokenConsumption_Local is TestBaseWorkflow {
|
|
|
145
145
|
vm.mockCall(
|
|
146
146
|
_DATA_HOOK,
|
|
147
147
|
abi.encodeWithSelector(IJBRulesetDataHook.beforeCashOutRecordedWith.selector),
|
|
148
|
-
abi.encode(
|
|
148
|
+
abi.encode(
|
|
149
|
+
0,
|
|
150
|
+
cashOutCount,
|
|
151
|
+
_controller.totalTokenSupplyWithReservedTokensOf(_projectId),
|
|
152
|
+
_PAY_AMOUNT,
|
|
153
|
+
specifications
|
|
154
|
+
)
|
|
149
155
|
);
|
|
150
156
|
|
|
151
157
|
vm.prank(multisig());
|
|
@@ -3,6 +3,8 @@ pragma solidity 0.8.28;
|
|
|
3
3
|
|
|
4
4
|
import {TestBaseWorkflow} from "./helpers/TestBaseWorkflow.sol";
|
|
5
5
|
import {JBERC20} from "../src/JBERC20.sol";
|
|
6
|
+
import {IJBPermissions} from "../src/interfaces/IJBPermissions.sol";
|
|
7
|
+
import {IJBProjects} from "../src/interfaces/IJBProjects.sol";
|
|
6
8
|
import {IJBRulesetApprovalHook} from "../src/interfaces/IJBRulesetApprovalHook.sol";
|
|
7
9
|
import {IJBToken} from "../src/interfaces/IJBToken.sol";
|
|
8
10
|
import {JBConstants} from "../src/libraries/JBConstants.sol";
|
|
@@ -15,7 +17,7 @@ import {JBTerminalConfig} from "../src/structs/JBTerminalConfig.sol";
|
|
|
15
17
|
|
|
16
18
|
import {ERC20Votes} from "../src/JBERC20.sol";
|
|
17
19
|
|
|
18
|
-
contract JBERC20Inheritance_Local is JBERC20, TestBaseWorkflow {
|
|
20
|
+
contract JBERC20Inheritance_Local is JBERC20(IJBPermissions(address(1)), IJBProjects(address(2))), TestBaseWorkflow {
|
|
19
21
|
/// This test is to verify that the inheritance order of JBERC20 is correct and that it calls the
|
|
20
22
|
/// `ERC20Votes._update()`
|
|
21
23
|
/// forge-config: default.allow_internal_expect_revert = true
|
package/test/TestTokenFlow.sol
CHANGED
|
@@ -99,8 +99,8 @@ contract TestTokenFlow_Local is TestBaseWorkflow {
|
|
|
99
99
|
});
|
|
100
100
|
} else {
|
|
101
101
|
// Create a new `IJBToken` and change it's owner to the `JBTokens` contract.
|
|
102
|
-
IJBToken _newToken = IJBToken(Clones.clone(address(new JBERC20())));
|
|
103
|
-
_newToken.initialize({name: "NewTestName", symbol: "NewTestSymbol",
|
|
102
|
+
IJBToken _newToken = IJBToken(Clones.clone(address(new JBERC20(jbPermissions(), jbProjects()))));
|
|
103
|
+
_newToken.initialize({name: "NewTestName", symbol: "NewTestSymbol", tokens: address(_tokens)});
|
|
104
104
|
|
|
105
105
|
// Mock the token can be added to the project.
|
|
106
106
|
vm.mockCall(
|
|
@@ -260,6 +260,7 @@ contract CashOutReenterPay is TestBaseWorkflow {
|
|
|
260
260
|
ruleset.cashOutTaxRate(), // Use the ruleset's 50% cash out tax rate.
|
|
261
261
|
cashOutCount, // Number of tokens being cashed out.
|
|
262
262
|
totalSupply, // Total supply for the bonding curve.
|
|
263
|
+
PAY_AMOUNT, // effectiveSurplusValue — full initial funding, no payouts yet.
|
|
263
264
|
specifications // Our malicious hook specification.
|
|
264
265
|
)
|
|
265
266
|
);
|
|
@@ -475,6 +476,9 @@ contract CashOutReenterPay is TestBaseWorkflow {
|
|
|
475
476
|
// Read the current total supply for the bonding curve calculation.
|
|
476
477
|
uint256 totalSupply = _tokens.totalSupplyOf(_projectId);
|
|
477
478
|
|
|
479
|
+
// Read the current surplus for the bonding curve.
|
|
480
|
+
uint256 surplus = jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN);
|
|
481
|
+
|
|
478
482
|
// Mock the data hook to return no hook specifications (simple cashout).
|
|
479
483
|
vm.mockCall(
|
|
480
484
|
DATA_HOOK,
|
|
@@ -483,6 +487,7 @@ contract CashOutReenterPay is TestBaseWorkflow {
|
|
|
483
487
|
ruleset.cashOutTaxRate(), // Pass through the ruleset's tax rate.
|
|
484
488
|
cashOutCount, // Number of tokens being cashed out.
|
|
485
489
|
totalSupply, // Current total supply.
|
|
490
|
+
surplus, // effectiveSurplusValue — current terminal balance.
|
|
486
491
|
new JBCashOutHookSpecification[](0) // No hooks for this cashout.
|
|
487
492
|
)
|
|
488
493
|
);
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.6;
|
|
3
|
+
|
|
4
|
+
import {TestBaseWorkflow} from "../helpers/TestBaseWorkflow.sol";
|
|
5
|
+
import {IJBController} from "../../src/interfaces/IJBController.sol";
|
|
6
|
+
import {IJBMultiTerminal} from "../../src/interfaces/IJBMultiTerminal.sol";
|
|
7
|
+
import {IJBRulesetApprovalHook} from "../../src/interfaces/IJBRulesetApprovalHook.sol";
|
|
8
|
+
import {JBConstants} from "../../src/libraries/JBConstants.sol";
|
|
9
|
+
import {JBAccountingContext} from "../../src/structs/JBAccountingContext.sol";
|
|
10
|
+
import {JBCurrencyAmount} from "../../src/structs/JBCurrencyAmount.sol";
|
|
11
|
+
import {JBFundAccessLimitGroup} from "../../src/structs/JBFundAccessLimitGroup.sol";
|
|
12
|
+
import {JBRulesetConfig} from "../../src/structs/JBRulesetConfig.sol";
|
|
13
|
+
import {JBRulesetMetadata} from "../../src/structs/JBRulesetMetadata.sol";
|
|
14
|
+
import {JBSplitGroup} from "../../src/structs/JBSplitGroup.sol";
|
|
15
|
+
import {JBTerminalConfig} from "../../src/structs/JBTerminalConfig.sol";
|
|
16
|
+
|
|
17
|
+
contract CodexHeldFeeRoundingTest is TestBaseWorkflow {
|
|
18
|
+
IJBController private _controller;
|
|
19
|
+
IJBMultiTerminal private _terminal;
|
|
20
|
+
|
|
21
|
+
uint256 private _projectId;
|
|
22
|
+
address private _projectOwner;
|
|
23
|
+
address private _beneficiary;
|
|
24
|
+
|
|
25
|
+
function setUp() public override {
|
|
26
|
+
super.setUp();
|
|
27
|
+
|
|
28
|
+
_projectOwner = multisig();
|
|
29
|
+
_beneficiary = beneficiary();
|
|
30
|
+
_terminal = jbMultiTerminal();
|
|
31
|
+
_controller = jbController();
|
|
32
|
+
|
|
33
|
+
JBRulesetMetadata memory metadata = JBRulesetMetadata({
|
|
34
|
+
reservedPercent: 0,
|
|
35
|
+
cashOutTaxRate: 0,
|
|
36
|
+
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
37
|
+
pausePay: false,
|
|
38
|
+
pauseCreditTransfers: false,
|
|
39
|
+
allowOwnerMinting: false,
|
|
40
|
+
allowSetCustomToken: false,
|
|
41
|
+
allowTerminalMigration: true,
|
|
42
|
+
allowSetTerminals: false,
|
|
43
|
+
ownerMustSendPayouts: false,
|
|
44
|
+
allowSetController: false,
|
|
45
|
+
allowAddAccountingContext: true,
|
|
46
|
+
allowAddPriceFeed: false,
|
|
47
|
+
holdFees: true,
|
|
48
|
+
useTotalSurplusForCashOuts: false,
|
|
49
|
+
useDataHookForPay: false,
|
|
50
|
+
useDataHookForCashOut: false,
|
|
51
|
+
dataHook: address(0),
|
|
52
|
+
metadata: 0
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
JBCurrencyAmount[] memory payoutLimits = new JBCurrencyAmount[](1);
|
|
56
|
+
payoutLimits[0] = JBCurrencyAmount({amount: 100, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))});
|
|
57
|
+
|
|
58
|
+
JBCurrencyAmount[] memory surplusAllowances = new JBCurrencyAmount[](1);
|
|
59
|
+
surplusAllowances[0] = JBCurrencyAmount({amount: 0, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))});
|
|
60
|
+
|
|
61
|
+
JBFundAccessLimitGroup[] memory fundAccessLimits = new JBFundAccessLimitGroup[](1);
|
|
62
|
+
fundAccessLimits[0] = JBFundAccessLimitGroup({
|
|
63
|
+
terminal: address(_terminal),
|
|
64
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
65
|
+
payoutLimits: payoutLimits,
|
|
66
|
+
surplusAllowances: surplusAllowances
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
|
|
70
|
+
rulesetConfigs[0] = JBRulesetConfig({
|
|
71
|
+
mustStartAtOrAfter: 0,
|
|
72
|
+
duration: 0,
|
|
73
|
+
weight: 0,
|
|
74
|
+
weightCutPercent: 0,
|
|
75
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
76
|
+
metadata: metadata,
|
|
77
|
+
splitGroups: new JBSplitGroup[](0),
|
|
78
|
+
fundAccessLimitGroups: fundAccessLimits
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
JBAccountingContext[] memory contexts = new JBAccountingContext[](1);
|
|
82
|
+
contexts[0] = JBAccountingContext({
|
|
83
|
+
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
JBTerminalConfig[] memory terminalConfigs = new JBTerminalConfig[](1);
|
|
87
|
+
terminalConfigs[0] = JBTerminalConfig({terminal: _terminal, accountingContextsToAccept: contexts});
|
|
88
|
+
|
|
89
|
+
// Project 1 is the fee project.
|
|
90
|
+
_controller.launchProjectFor({
|
|
91
|
+
owner: _projectOwner,
|
|
92
|
+
projectUri: "fee-project",
|
|
93
|
+
rulesetConfigurations: rulesetConfigs,
|
|
94
|
+
terminalConfigurations: terminalConfigs,
|
|
95
|
+
memo: ""
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
_projectId = _controller.launchProjectFor({
|
|
99
|
+
owner: _projectOwner,
|
|
100
|
+
projectUri: "project",
|
|
101
|
+
rulesetConfigurations: rulesetConfigs,
|
|
102
|
+
terminalConfigurations: terminalConfigs,
|
|
103
|
+
memo: ""
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function test_partialHeldFeeRepaymentCanEraseRemainingFee() external {
|
|
108
|
+
// Seed the project with enough balance to send a payout that holds fees.
|
|
109
|
+
_terminal.pay{value: 100}({
|
|
110
|
+
projectId: _projectId,
|
|
111
|
+
amount: 100,
|
|
112
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
113
|
+
beneficiary: _beneficiary,
|
|
114
|
+
minReturnedTokens: 0,
|
|
115
|
+
memo: "",
|
|
116
|
+
metadata: new bytes(0)
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
_terminal.sendPayoutsOf({
|
|
120
|
+
projectId: _projectId,
|
|
121
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
122
|
+
amount: 40,
|
|
123
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
124
|
+
minTokensPaidOut: 0
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// 40 gross produces a 1 wei fee and 39 wei net payout.
|
|
128
|
+
assertEq(address(_projectOwner).balance, 39);
|
|
129
|
+
|
|
130
|
+
vm.prank(_projectOwner);
|
|
131
|
+
_terminal.addToBalanceOf{value: 1}({
|
|
132
|
+
projectId: _projectId,
|
|
133
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
134
|
+
amount: 1,
|
|
135
|
+
shouldReturnHeldFees: true,
|
|
136
|
+
memo: "",
|
|
137
|
+
metadata: new bytes(0)
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// After repaying only 1 wei of the 39 wei payout, the fee should still be owed in full.
|
|
141
|
+
uint256 feeProjectBalanceBefore = jbTerminalStore().balanceOf(address(_terminal), 1, JBConstants.NATIVE_TOKEN);
|
|
142
|
+
assertEq(feeProjectBalanceBefore, 0);
|
|
143
|
+
|
|
144
|
+
vm.warp(block.timestamp + 2_419_200);
|
|
145
|
+
_terminal.processHeldFeesOf(_projectId, JBConstants.NATIVE_TOKEN, 10);
|
|
146
|
+
|
|
147
|
+
uint256 feeProjectBalanceAfter = jbTerminalStore().balanceOf(address(_terminal), 1, JBConstants.NATIVE_TOKEN);
|
|
148
|
+
uint256 projectBalanceAfter =
|
|
149
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN);
|
|
150
|
+
|
|
151
|
+
// The fee project never receives the original 1 wei fee.
|
|
152
|
+
assertEq(feeProjectBalanceAfter, 0);
|
|
153
|
+
// The payer project only gets its explicit top-up recorded.
|
|
154
|
+
assertEq(projectBalanceAfter, 61);
|
|
155
|
+
// One wei remains stranded in the terminal: actual native balance exceeds tracked balances.
|
|
156
|
+
assertEq(address(_terminal).balance, 62);
|
|
157
|
+
assertEq(address(_terminal).balance - (feeProjectBalanceAfter + projectBalanceAfter), 1);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -293,7 +293,7 @@ contract TestBaseWorkflow is JBTest, DeployPermit2 {
|
|
|
293
293
|
_jbPermissions = new JBPermissions(_trustedForwarder);
|
|
294
294
|
_jbProjects = new JBProjects(_multisig, address(0), _trustedForwarder);
|
|
295
295
|
_jbDirectory = new JBDirectory(_jbPermissions, _jbProjects, _multisig);
|
|
296
|
-
_jbErc20 = new JBERC20();
|
|
296
|
+
_jbErc20 = new JBERC20(_jbPermissions, _jbProjects);
|
|
297
297
|
_jbTokens = new JBTokens(_jbDirectory, _jbErc20);
|
|
298
298
|
_jbRulesets = new JBRulesets(_jbDirectory);
|
|
299
299
|
_jbPrices = new JBPrices(_jbDirectory, _jbPermissions, _jbProjects, _multisig, _trustedForwarder);
|
|
@@ -3,6 +3,8 @@ pragma solidity 0.8.28;
|
|
|
3
3
|
|
|
4
4
|
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
|
|
5
5
|
import {JBERC20} from "../../../../src/JBERC20.sol";
|
|
6
|
+
import {IJBPermissions} from "../../../../src/interfaces/IJBPermissions.sol";
|
|
7
|
+
import {IJBProjects} from "../../../../src/interfaces/IJBProjects.sol";
|
|
6
8
|
import {IJBToken} from "../../../../src/interfaces/IJBToken.sol";
|
|
7
9
|
import {JBTest} from "../../../helpers/JBTest.sol";
|
|
8
10
|
|
|
@@ -11,7 +13,10 @@ Contract that deploys a target contract with other mock contracts to satisfy the
|
|
|
11
13
|
Tests relative to this contract will be dependent on mock calls/emits and stdStorage.
|
|
12
14
|
*/
|
|
13
15
|
contract JBERC20Setup is JBTest {
|
|
14
|
-
|
|
16
|
+
// Mocks
|
|
17
|
+
address _tokens = makeAddr("tokens");
|
|
18
|
+
IJBProjects _projects = IJBProjects(makeAddr("projects"));
|
|
19
|
+
IJBPermissions _permissions = IJBPermissions(makeAddr("permissions"));
|
|
15
20
|
|
|
16
21
|
// Implementation (constructor sets _name = "invalid", cannot be initialized)
|
|
17
22
|
IJBToken public _implementation;
|
|
@@ -20,8 +25,8 @@ contract JBERC20Setup is JBTest {
|
|
|
20
25
|
IJBToken public _erc20;
|
|
21
26
|
|
|
22
27
|
function erc20Setup() public virtual {
|
|
23
|
-
// Deploy the implementation
|
|
24
|
-
_implementation = new JBERC20();
|
|
28
|
+
// Deploy the implementation with immutable permissions and projects
|
|
29
|
+
_implementation = new JBERC20(_permissions, _projects);
|
|
25
30
|
|
|
26
31
|
// Clone it — clones start with empty storage, so initialize() works
|
|
27
32
|
_erc20 = IJBToken(Clones.clone(address(_implementation)));
|