@bananapus/core-v6 0.0.32 → 0.0.34

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.
@@ -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
@@ -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", owner: address(_tokens)});
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
- address _owner = makeAddr("owner");
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)));
@@ -4,7 +4,6 @@ pragma solidity 0.8.28;
4
4
  import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
5
5
  import {JBERC20} from "../../../../src/JBERC20.sol";
6
6
  import {JBERC20Setup} from "./JBERC20Setup.sol";
7
- import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
8
7
 
9
8
  contract TestInitialize_Local is JBERC20Setup {
10
9
  string _name = "Nana";
@@ -17,21 +16,21 @@ contract TestInitialize_Local is JBERC20Setup {
17
16
  function test_ImplementationCannotBeInitialized() external {
18
17
  // The implementation has _name = "invalid" set in constructor, so initialize() must revert.
19
18
  vm.expectRevert(JBERC20.JBERC20_AlreadyInitialized.selector);
20
- _implementation.initialize(_name, _symbol, _owner);
19
+ _implementation.initialize(_name, _symbol, _tokens);
21
20
  }
22
21
 
23
22
  function test_WhenANameIsAlreadySet() external {
24
23
  // it will revert
25
24
 
26
- _erc20.initialize(_name, _symbol, _owner);
25
+ _erc20.initialize(_name, _symbol, _tokens);
27
26
 
28
- // ensure ownership transferred
29
- address newOwner = Ownable(address(_erc20)).owner();
30
- assertEq(newOwner, _owner);
27
+ // ensure TOKENS is set
28
+ address setTokens = address(JBERC20(address(_erc20)).TOKENS());
29
+ assertEq(setTokens, _tokens);
31
30
 
32
31
  // will fail as internal name is no longer zero length
33
32
  vm.expectRevert();
34
- _erc20.initialize(_name, _symbol, _owner);
33
+ _erc20.initialize(_name, _symbol, _tokens);
35
34
  }
36
35
 
37
36
  function test_WhenName_EQNothing() external {
@@ -39,17 +38,17 @@ contract TestInitialize_Local is JBERC20Setup {
39
38
 
40
39
  // will fail as internal name is no longer than zero length
41
40
  vm.expectRevert();
42
- _erc20.initialize("", _symbol, _owner);
41
+ _erc20.initialize("", _symbol, _tokens);
43
42
  }
44
43
 
45
44
  function test_WhenNameIsValidAndNotAlreadySet() external {
46
- // it will set the name and symbol and transfer ownership
45
+ // it will set the name, symbol, and store references
47
46
 
48
- _erc20.initialize(_name, _symbol, _owner);
47
+ _erc20.initialize(_name, _symbol, _tokens);
49
48
 
50
- // ensure ownership transferred
51
- address newOwner = Ownable(address(_erc20)).owner();
52
- assertEq(newOwner, _owner);
49
+ // ensure TOKENS is set
50
+ address setTokens = address(JBERC20(address(_erc20)).TOKENS());
51
+ assertEq(setTokens, _tokens);
53
52
 
54
53
  // name is set
55
54
  string memory _setName = IERC20Metadata(address(_erc20)).name();
@@ -15,7 +15,7 @@ contract TestName_Local is JBERC20Setup {
15
15
 
16
16
  function test_WhenANameIsSet() external {
17
17
  // it will return the name
18
- _erc20.initialize("NANAPUS", "NANA", _owner);
18
+ _erc20.initialize("NANAPUS", "NANA", _tokens);
19
19
 
20
20
  string memory _setName = _token.name();
21
21
  assertEq(_setName, "NANAPUS");
@@ -6,6 +6,7 @@ import {JBERC20Setup} from "./JBERC20Setup.sol";
6
6
  import {SigUtils} from "./SigUtils.sol";
7
7
 
8
8
  contract TestNonces_Local is JBERC20Setup {
9
+ address _user = makeAddr("user");
9
10
  IERC20Permit _token;
10
11
  SigUtils sigUtils;
11
12
 
@@ -30,7 +31,7 @@ contract TestNonces_Local is JBERC20Setup {
30
31
  function test_WhenAUserHasNotCalledPermit() external view {
31
32
  // it will return zero
32
33
 
33
- uint256 _nonce = _token.nonces(_owner);
34
+ uint256 _nonce = _token.nonces(_user);
34
35
 
35
36
  assertEq(_nonce, 0);
36
37
  }
@@ -16,7 +16,7 @@ contract TestSymbol_Local is JBERC20Setup {
16
16
  function test_WhenASymbolIsSet() external {
17
17
  // it will return a non-empty string
18
18
 
19
- _erc20.initialize("NANAPUS", "NANA", _owner);
19
+ _erc20.initialize("NANAPUS", "NANA", _tokens);
20
20
 
21
21
  string memory _setSymbol = _token.symbol();
22
22
  assertEq(_setSymbol, "NANA");
@@ -453,7 +453,7 @@ contract TestPreviewCashOutFor_Local is JBTerminalStoreSetup {
453
453
  mockExpect(
454
454
  address(_dataHook),
455
455
  abi.encodeCall(IJBRulesetDataHook.beforeCashOutRecordedWith, (_context)),
456
- abi.encode(0, 10e18, _totalSupply, _spec)
456
+ abi.encode(0, 10e18, _totalSupply, 3e18, _spec)
457
457
  );
458
458
 
459
459
  (, uint256 reclaimAmount, uint256 cashOutTaxRate, JBCashOutHookSpecification[] memory hookSpecifications) = _store.previewCashOutFrom({
@@ -417,7 +417,7 @@ contract TestRecordCashOutsFor_Local is JBTerminalStoreSetup {
417
417
  mockExpect(
418
418
  address(_dataHook),
419
419
  abi.encodeCall(IJBRulesetDataHook.beforeCashOutRecordedWith, (_context)),
420
- abi.encode(0, 1e18, _totalSupply, _spec)
420
+ abi.encode(0, 1e18, _totalSupply, 3e18, _spec)
421
421
  );
422
422
 
423
423
  uint256 balanceBefore = _store.balanceOf(address(this), _projectId, _accountingContexts.token);
@@ -521,7 +521,7 @@ contract TestRecordCashOutsFor_Local is JBTerminalStoreSetup {
521
521
  mockExpect(
522
522
  address(_dataHook),
523
523
  abi.encodeCall(IJBRulesetDataHook.beforeCashOutRecordedWith, (_context)),
524
- abi.encode(0, 1e18, _totalSupply, _spec)
524
+ abi.encode(0, 1e18, _totalSupply, 3e18, _spec)
525
525
  );
526
526
 
527
527
  (, uint256 reclaimed,,) = _store.recordCashOutFor({
@@ -576,7 +576,7 @@ contract TestRecordCashOutsFor_Local is JBTerminalStoreSetup {
576
576
  mockExpect(
577
577
  address(_dataHook),
578
578
  abi.encodeCall(IJBRulesetDataHook.beforeCashOutRecordedWith, (_context)),
579
- abi.encode(0, 1e18, _totalSupply, _spec)
579
+ abi.encode(0, 1e18, _totalSupply, 3e18, _spec)
580
580
  );
581
581
 
582
582
  vm.expectRevert(abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_NoopHookSpecHasAmount.selector, 1));
@@ -631,7 +631,7 @@ contract TestRecordCashOutsFor_Local is JBTerminalStoreSetup {
631
631
  mockExpect(
632
632
  address(_dataHook),
633
633
  abi.encodeCall(IJBRulesetDataHook.beforeCashOutRecordedWith, (_context)),
634
- abi.encode(0, 1e18, _totalSupply, _spec)
634
+ abi.encode(0, 1e18, _totalSupply, 3e18, _spec)
635
635
  );
636
636
 
637
637
  vm.expectRevert(abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_NoopHookSpecHasAmount.selector, 1));
@@ -4,6 +4,8 @@ pragma solidity 0.8.28;
4
4
  import {JBERC20} from "../../../../src/JBERC20.sol";
5
5
  import {JBTokens} from "../../../../src/JBTokens.sol";
6
6
  import {IJBDirectory} from "../../../../src/interfaces/IJBDirectory.sol";
7
+ import {IJBPermissions} from "../../../../src/interfaces/IJBPermissions.sol";
8
+ import {IJBProjects} from "../../../../src/interfaces/IJBProjects.sol";
7
9
  import {IJBToken} from "../../../../src/interfaces/IJBToken.sol";
8
10
  import {IJBTokens} from "../../../../src/interfaces/IJBTokens.sol";
9
11
  import {JBTest} from "../../../helpers/JBTest.sol";
@@ -15,6 +17,8 @@ Tests relative to this contract will be dependent on mock calls/emits and stdSto
15
17
  contract JBTokensSetup is JBTest {
16
18
  // Mocks
17
19
  IJBDirectory public directory = IJBDirectory(makeAddr("directory"));
20
+ IJBPermissions public permissions = IJBPermissions(makeAddr("permissions"));
21
+ IJBProjects public projects = IJBProjects(makeAddr("projects"));
18
22
  IJBToken public jbToken;
19
23
 
20
24
  // Target Contract
@@ -22,7 +26,7 @@ contract JBTokensSetup is JBTest {
22
26
 
23
27
  function tokensSetup() public virtual {
24
28
  // Instantiate the contract being tested
25
- jbToken = new JBERC20();
29
+ jbToken = new JBERC20(permissions, projects);
26
30
  _tokens = new JBTokens(directory, jbToken);
27
31
  }
28
32
  }