@bananapus/core-v6 0.0.14 → 0.0.16
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 +4 -0
- package/README.md +2 -2
- package/SKILLS.md +2 -0
- package/STYLE_GUIDE.md +150 -43
- package/foundry.toml +3 -3
- package/package.json +4 -4
- package/remappings.txt +1 -1
- package/script/Deploy.s.sol +23 -16
- package/script/DeployPeriphery.s.sol +71 -66
- package/script/helpers/CoreDeploymentLib.sol +84 -37
- package/src/JBChainlinkV3PriceFeed.sol +1 -0
- package/src/JBController.sol +19 -4
- package/src/JBERC20.sol +12 -3
- package/src/JBFundAccessLimits.sol +12 -2
- package/src/JBMultiTerminal.sol +3 -1
- package/src/JBPermissions.sol +1 -0
- package/src/JBProjects.sol +1 -1
- package/src/JBRulesets.sol +11 -0
- package/src/JBSplits.sol +5 -0
- package/src/JBTerminalStore.sol +3 -0
- package/src/JBTokens.sol +40 -4
- package/src/interfaces/IJBController.sol +6 -1
- package/src/interfaces/IJBPayoutTerminal.sol +0 -1
- package/src/interfaces/IJBPermitTerminal.sol +1 -0
- package/src/interfaces/IJBToken.sol +5 -0
- package/src/interfaces/IJBTokens.sol +13 -0
- package/src/libraries/JBMetadataResolver.sol +7 -3
- package/src/libraries/JBRulesetMetadataResolver.sol +21 -21
- package/src/structs/JBAccountingContext.sol +1 -0
- package/src/structs/JBAfterCashOutRecordedContext.sol +1 -0
- package/src/structs/JBAfterPayRecordedContext.sol +1 -0
- package/src/structs/JBBeforeCashOutRecordedContext.sol +1 -0
- package/src/structs/JBBeforePayRecordedContext.sol +1 -0
- package/src/structs/JBCashOutHookSpecification.sol +1 -0
- package/src/structs/JBCurrencyAmount.sol +1 -0
- package/src/structs/JBFee.sol +1 -0
- package/src/structs/JBFundAccessLimitGroup.sol +1 -0
- package/src/structs/JBPayHookSpecification.sol +1 -0
- package/src/structs/JBPermissionsData.sol +1 -0
- package/src/structs/JBRuleset.sol +1 -0
- package/src/structs/JBRulesetConfig.sol +1 -0
- package/src/structs/JBRulesetMetadata.sol +1 -0
- package/src/structs/JBRulesetWeightCache.sol +1 -0
- package/src/structs/JBRulesetWithMetadata.sol +1 -0
- package/src/structs/JBSingleAllowance.sol +1 -0
- package/src/structs/JBSplit.sol +1 -0
- package/src/structs/JBSplitGroup.sol +1 -0
- package/src/structs/JBSplitHookContext.sol +1 -0
- package/src/structs/JBTerminalConfig.sol +1 -0
- package/src/structs/JBTokenAmount.sol +1 -0
- package/test/ComprehensiveInvariant.t.sol +15 -5
- package/test/{AuditExploits.t.sol → CoreExploitTests.t.sol} +35 -4
- package/test/EconomicSimulation.t.sol +10 -2
- package/test/EntryPointPermutations.t.sol +18 -5
- package/test/FlashLoanAttacks.t.sol +12 -2
- package/test/PermissionEscalation.t.sol +54 -22
- package/test/RulesetTransitions.t.sol +15 -1
- package/test/SplitLoopTests.t.sol +26 -5
- package/test/TestAccessToFunds.sol +17 -2
- package/test/TestCashOut.sol +15 -2
- package/test/TestCashOutCountFor.sol +1 -2
- package/test/TestCashOutHooks.sol +47 -25
- package/test/TestCashOutTimingEdge.sol +13 -1
- package/test/TestDurationUnderflow.sol +13 -1
- package/test/TestFeeProcessingFailure.sol +17 -7
- package/test/TestFees.sol +14 -1
- package/test/TestInterfaceSupport.sol +20 -1
- package/test/TestJBERC20Inheritance.sol +11 -1
- package/test/TestLaunchProject.sol +13 -1
- package/test/TestMetaTx.sol +15 -1
- package/test/TestMetadataParserLib.sol +37 -4
- package/test/TestMigrationHeldFees.sol +17 -11
- package/test/TestMintTokensOf.sol +14 -1
- package/test/TestMultiTokenSurplus.sol +14 -1
- package/test/TestMultipleAccessLimits.sol +23 -1
- package/test/TestPayBurnRedeemFlow.sol +16 -1
- package/test/TestPayHooks.sol +33 -14
- package/test/TestPermissions.sol +20 -1
- package/test/TestPermissionsEdge.sol +5 -1
- package/test/TestPermit2Terminal.sol +36 -3
- package/test/TestRulesetQueueing.sol +24 -1
- package/test/TestRulesetQueuingStress.sol +28 -2
- package/test/TestRulesetWeightCaching.sol +5 -2
- package/test/TestSplits.sol +23 -1
- package/test/TestTerminalMigration.sol +11 -1
- package/test/TestTokenFlow.sol +18 -1
- package/test/TestWeightCacheStaleAfterRejection.sol +15 -1
- package/test/WeirdTokenTests.t.sol +18 -2
- package/test/fork/TestChainlinkPriceFeedFork.sol +254 -0
- package/test/formal/BondingCurveProperties.t.sol +8 -2
- package/test/formal/FeeProperties.t.sol +7 -1
- package/test/helpers/JBTest.sol +7 -7
- package/test/helpers/TestBaseWorkflow.sol +84 -1
- package/test/invariants/Phase3DeepInvariant.t.sol +13 -5
- package/test/invariants/RulesetsInvariant.t.sol +12 -2
- package/test/invariants/TerminalStoreInvariant.t.sol +11 -2
- package/test/invariants/TokensInvariant.t.sol +13 -2
- package/test/invariants/handlers/ComprehensiveHandler.sol +19 -1
- package/test/invariants/handlers/EconomicHandler.sol +31 -1
- package/test/invariants/handlers/Phase3Handler.sol +31 -2
- package/test/invariants/handlers/RulesetsHandler.sol +5 -1
- package/test/invariants/handlers/TerminalStoreHandler.sol +6 -1
- package/test/invariants/handlers/TokensHandler.sol +1 -2
- package/test/mock/ERC2771ForwarderMock.sol +1 -1
- package/test/mock/MockERC20.sol +1 -3
- package/test/mock/MockMaliciousBeneficiary.sol +2 -2
- package/test/mock/MockMaliciousSplitHook.sol +2 -1
- package/test/mock/MockPriceFeed.sol +1 -1
- package/test/units/static/JBChainlinkV3PriceFeed/TestPriceFeed.sol +0 -1
- package/test/units/static/JBController/JBControllerSetup.sol +10 -1
- package/test/units/static/JBController/TestBurnTokensOf.sol +8 -1
- package/test/units/static/JBController/TestClaimTokensFor.sol +4 -1
- package/test/units/static/JBController/TestDeployErc20For.sol +7 -1
- package/test/units/static/JBController/TestLaunchProjectFor.sol +21 -1
- package/test/units/static/JBController/TestLaunchRulesetsFor.sol +21 -1
- package/test/units/static/JBController/TestMigrateController.sol +10 -1
- package/test/units/static/JBController/TestMintTokensOfUnits.sol +10 -1
- package/test/units/static/JBController/TestPayReservedTokenToTerminal.sol +4 -1
- package/test/units/static/JBController/TestReceiveMigrationFrom.sol +5 -1
- package/test/units/static/JBController/TestRulesetViews.sol +7 -1
- package/test/units/static/JBController/TestSendReservedTokensToSplitsOf.sol +21 -1
- package/test/units/static/JBController/TestSetSplitGroupsOf.sol +6 -1
- package/test/units/static/JBController/TestSetTokenFor.sol +13 -1
- package/test/units/static/JBController/TestSetUriOf.sol +5 -1
- package/test/units/static/JBController/TestTransferCreditsFrom.sol +11 -1
- package/test/units/static/JBDeadline/TestDeadlineFuzz.sol +15 -4
- package/test/units/static/JBDirectory/JBDirectorySetup.sol +4 -1
- package/test/units/static/JBDirectory/TestPrimaryTerminalOf.sol +5 -1
- package/test/units/static/JBDirectory/TestSetControllerOf.sol +11 -1
- package/test/units/static/JBDirectory/TestSetControllerOfMigrationOrder.sol +7 -1
- package/test/units/static/JBDirectory/TestSetPrimaryTerminalOf.sol +11 -1
- package/test/units/static/JBDirectory/TestSetTerminalsOf.sol +10 -1
- package/test/units/static/JBERC20/JBERC20Setup.sol +2 -1
- package/test/units/static/JBERC20/SigUtils.sol +2 -0
- package/test/units/static/JBERC20/TestInitialize.sol +1 -1
- package/test/units/static/JBERC20/TestName.sol +1 -1
- package/test/units/static/JBERC20/TestNonces.sol +3 -1
- package/test/units/static/JBERC20/TestSymbol.sol +1 -1
- package/test/units/static/JBFeelessAdresses/JBFeelessSetup.sol +2 -1
- package/test/units/static/JBFeelessAdresses/TestInterfaces.sol +2 -1
- package/test/units/static/JBFeelessAdresses/TestSetFeelessAddress.sol +1 -1
- package/test/units/static/JBFees/TestFeesFuzz.sol +1 -1
- package/test/units/static/JBFixedPointNumber/TestAdjustDecimals.sol +0 -1
- package/test/units/static/JBFixedPointNumber/TestAdjustDecimalsFuzz.sol +0 -1
- package/test/units/static/JBFundAccessLimits/JBFundAccessSetup.sol +3 -1
- package/test/units/static/JBFundAccessLimits/TestFundAccessLimitsEdge.sol +4 -1
- package/test/units/static/JBFundAccessLimits/TestPayoutLimitOf.sol +4 -1
- package/test/units/static/JBFundAccessLimits/TestPayoutLimitsOf.sol +8 -1
- package/test/units/static/JBFundAccessLimits/TestSetFundAccessLimitsFor.sol +8 -1
- package/test/units/static/JBFundAccessLimits/TestSurplusAllowanceOf.sol +4 -1
- package/test/units/static/JBFundAccessLimits/TestSurplusAllowancesOf.sol +7 -1
- package/test/units/static/JBMetadataResolver/TestGetDataFor.sol +1 -1
- package/test/units/static/JBMetadataResolver/{TestMetadataResolverM20M21.sol → TestMetadataResolverEdgeCases.sol} +6 -5
- package/test/units/static/JBMetadataResolver/TestMetadataResolverFuzz.sol +2 -1
- package/test/units/static/JBMultiTerminal/JBMultiTerminalSetup.sol +12 -1
- package/test/units/static/JBMultiTerminal/TestAccountingContextsOf.sol +9 -1
- package/test/units/static/JBMultiTerminal/TestAddAccountingContextsFor.sol +18 -2
- package/test/units/static/JBMultiTerminal/TestAddToBalanceOf.sol +42 -7
- package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +30 -6
- package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +18 -2
- package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +13 -3
- package/test/units/static/JBMultiTerminal/TestMigrateBalanceOf.sol +21 -4
- package/test/units/static/JBMultiTerminal/TestPay.sol +32 -6
- package/test/units/static/JBMultiTerminal/TestProcessHeldFeesOf.sol +0 -1
- package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +15 -1
- package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +17 -1
- package/test/units/static/JBPermissions/JBPermissionsSetup.sol +2 -1
- package/test/units/static/JBPermissions/TestHasPermission.sol +1 -1
- package/test/units/static/JBPermissions/TestHasPermissions.sol +1 -1
- package/test/units/static/JBPermissions/TestSetPermissionsFor.sol +3 -1
- package/test/units/static/JBPrices/JBPricesSetup.sol +6 -1
- package/test/units/static/JBPrices/TestAddPriceFeedFor.sol +6 -1
- package/test/units/static/JBPrices/TestPricePerUnitOf.sol +4 -1
- package/test/units/static/JBPrices/TestPrices.sol +4 -1
- package/test/units/static/JBProjects/JBProjectsSetup.sol +2 -1
- package/test/units/static/JBProjects/TestCreateFor.sol +3 -1
- package/test/units/static/JBProjects/TestInitialProject.sol +2 -1
- package/test/units/static/JBProjects/TestInterfaces.sol +0 -1
- package/test/units/static/JBProjects/TestSetResolver.sol +2 -1
- package/test/units/static/JBProjects/TestTokenUri.sol +3 -1
- package/test/units/static/JBRulesetMetadataResolver/TestSetCashOutTaxRateTo.sol +9 -1
- package/test/units/static/JBRulesets/JBRulesetsSetup.sol +3 -1
- package/test/units/static/JBRulesets/TestCurrentApprovalStatusForLatestRulesetOf.sol +9 -1
- package/test/units/static/JBRulesets/TestCurrentOf.sol +10 -1
- package/test/units/static/JBRulesets/TestGetRulesetOf.sol +7 -1
- package/test/units/static/JBRulesets/TestLatestQueuedRulesetOf.sol +9 -1
- package/test/units/static/JBRulesets/TestRulesets.sol +12 -2
- package/test/units/static/JBRulesets/TestRulesetsOf.sol +1 -1
- package/test/units/static/JBRulesets/TestUpcomingRulesetOf.sol +10 -1
- package/test/units/static/JBRulesets/TestUpdateRulesetWeightCache.sol +6 -1
- package/test/units/static/JBSplits/JBSplitsSetup.sol +3 -1
- package/test/units/static/JBSplits/TestSelfManagedSplitGroups.sol +8 -1
- package/test/units/static/JBSplits/TestSetSplitGroupsOf.sol +8 -1
- package/test/units/static/JBSplits/TestSplitsLockedEdge.sol +6 -1
- package/test/units/static/JBSplits/TestSplitsOf.sol +1 -1
- package/test/units/static/JBSplits/TestSplitsPacking.sol +5 -2
- package/test/units/static/JBSurplus/TestSurplusFuzz.sol +4 -2
- package/test/units/static/JBTerminalStore/JBTerminalStoreSetup.sol +5 -1
- package/test/units/static/JBTerminalStore/TestCurrentReclaimableSurplusOf.sol +14 -1
- package/test/units/static/JBTerminalStore/TestCurrentSurplusOf.sol +14 -1
- package/test/units/static/JBTerminalStore/TestCurrentTotalSurplusOf.sol +3 -1
- package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +20 -1
- package/test/units/static/JBTerminalStore/TestRecordPaymentFrom.sol +15 -1
- package/test/units/static/JBTerminalStore/TestRecordPayoutFor.sol +13 -1
- package/test/units/static/JBTerminalStore/TestRecordTerminalMigration.sol +8 -1
- package/test/units/static/JBTerminalStore/TestRecordUsedAllowanceOf.sol +16 -1
- package/test/units/static/JBTerminalStore/TestUint224Overflow.sol +15 -1
- package/test/units/static/JBTokens/JBTokensSetup.sol +5 -1
- package/test/units/static/JBTokens/TestBurnFrom.sol +4 -1
- package/test/units/static/JBTokens/TestClaimTokensFor.sol +4 -1
- package/test/units/static/JBTokens/TestDeployERC20ForUnits.sol +4 -1
- package/test/units/static/JBTokens/TestMintFor.sol +4 -1
- package/test/units/static/JBTokens/TestSetTokenFor.sol +4 -1
- package/test/units/static/JBTokens/TestTotalBalanceOf.sol +1 -1
- package/test/units/static/JBTokens/TestTotalSupplyOf.sol +1 -1
- package/test/units/static/JBTokens/TestTransferCreditsFrom.sol +3 -1
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.26;
|
|
3
|
+
|
|
4
|
+
import {Test} from "forge-std/Test.sol";
|
|
5
|
+
|
|
6
|
+
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
|
|
7
|
+
|
|
8
|
+
import {JBChainlinkV3PriceFeed} from "../../src/JBChainlinkV3PriceFeed.sol";
|
|
9
|
+
import {JBPrices} from "../../src/JBPrices.sol";
|
|
10
|
+
import {IJBDirectory} from "../../src/interfaces/IJBDirectory.sol";
|
|
11
|
+
import {IJBPermissions} from "../../src/interfaces/IJBPermissions.sol";
|
|
12
|
+
import {IJBPriceFeed} from "../../src/interfaces/IJBPriceFeed.sol";
|
|
13
|
+
import {IJBProjects} from "../../src/interfaces/IJBProjects.sol";
|
|
14
|
+
|
|
15
|
+
/// @notice Fork tests for JBChainlinkV3PriceFeed and JBPrices against live Chainlink oracles on Ethereum mainnet.
|
|
16
|
+
contract TestChainlinkPriceFeedFork is Test {
|
|
17
|
+
// Chainlink feed addresses (Ethereum mainnet).
|
|
18
|
+
address constant ETH_USD_FEED = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419;
|
|
19
|
+
address constant BTC_USD_FEED = 0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c;
|
|
20
|
+
|
|
21
|
+
// Staleness threshold (1 hour).
|
|
22
|
+
uint256 constant THRESHOLD = 3600;
|
|
23
|
+
|
|
24
|
+
// Currency identifiers (arbitrary nonzero values for JBPrices mapping keys).
|
|
25
|
+
uint256 constant CURRENCY_ETH = 1;
|
|
26
|
+
uint256 constant CURRENCY_USD = 2;
|
|
27
|
+
uint256 constant CURRENCY_BTC = 3;
|
|
28
|
+
|
|
29
|
+
// Pinned block for reproducibility.
|
|
30
|
+
uint256 constant FORK_BLOCK = 22_000_000;
|
|
31
|
+
|
|
32
|
+
JBChainlinkV3PriceFeed ethUsdPriceFeed;
|
|
33
|
+
JBChainlinkV3PriceFeed btcUsdPriceFeed;
|
|
34
|
+
JBPrices prices;
|
|
35
|
+
address owner;
|
|
36
|
+
|
|
37
|
+
function setUp() public {
|
|
38
|
+
vm.createSelectFork("ethereum", FORK_BLOCK);
|
|
39
|
+
|
|
40
|
+
owner = makeAddr("owner");
|
|
41
|
+
|
|
42
|
+
// Deploy price feeds against real Chainlink aggregators.
|
|
43
|
+
ethUsdPriceFeed = new JBChainlinkV3PriceFeed(AggregatorV3Interface(ETH_USD_FEED), THRESHOLD);
|
|
44
|
+
btcUsdPriceFeed = new JBChainlinkV3PriceFeed(AggregatorV3Interface(BTC_USD_FEED), THRESHOLD);
|
|
45
|
+
|
|
46
|
+
// Deploy JBPrices with mock directory/permissions/projects (only price resolution is tested).
|
|
47
|
+
prices = new JBPrices(
|
|
48
|
+
IJBDirectory(makeAddr("directory")),
|
|
49
|
+
IJBPermissions(makeAddr("permissions")),
|
|
50
|
+
IJBProjects(makeAddr("projects")),
|
|
51
|
+
owner,
|
|
52
|
+
address(0)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ------------------------------------------------------------------
|
|
57
|
+
// Live feed sanity checks
|
|
58
|
+
// ------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
/// @notice Verify currentUnitPrice returns a sane ETH/USD price at the pinned block.
|
|
61
|
+
function test_currentUnitPrice_liveEthUsd() public view {
|
|
62
|
+
uint256 price18 = ethUsdPriceFeed.currentUnitPrice(18);
|
|
63
|
+
|
|
64
|
+
// ETH price should be between $500 and $50,000.
|
|
65
|
+
assertGt(price18, 500e18, "ETH price too low");
|
|
66
|
+
assertLt(price18, 50_000e18, "ETH price too high");
|
|
67
|
+
|
|
68
|
+
// Cross-check against raw latestRoundData.
|
|
69
|
+
(, int256 rawPrice,,,) = AggregatorV3Interface(ETH_USD_FEED).latestRoundData();
|
|
70
|
+
uint256 feedDecimals = AggregatorV3Interface(ETH_USD_FEED).decimals();
|
|
71
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
72
|
+
uint256 expected18 = uint256(rawPrice) * 10 ** (18 - feedDecimals);
|
|
73
|
+
assertEq(price18, expected18, "Price mismatch vs raw feed");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ------------------------------------------------------------------
|
|
77
|
+
// Decimal scaling
|
|
78
|
+
// ------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
/// @notice Verify correct scaling across 6, 8, 18, and 27 decimals.
|
|
81
|
+
function test_currentUnitPrice_differentDecimals() public view {
|
|
82
|
+
uint256 price6 = ethUsdPriceFeed.currentUnitPrice(6);
|
|
83
|
+
uint256 price8 = ethUsdPriceFeed.currentUnitPrice(8);
|
|
84
|
+
uint256 price18 = ethUsdPriceFeed.currentUnitPrice(18);
|
|
85
|
+
uint256 price27 = ethUsdPriceFeed.currentUnitPrice(27);
|
|
86
|
+
|
|
87
|
+
// Raw feed is 8 decimals — price8 should match it exactly.
|
|
88
|
+
(, int256 rawPrice,,,) = AggregatorV3Interface(ETH_USD_FEED).latestRoundData();
|
|
89
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
90
|
+
assertEq(price8, uint256(rawPrice), "8-decimal mismatch");
|
|
91
|
+
|
|
92
|
+
// 6 decimals = raw / 100 (truncated).
|
|
93
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
94
|
+
assertEq(price6, uint256(rawPrice) / 1e2, "6-decimal mismatch");
|
|
95
|
+
|
|
96
|
+
// 18 decimals = raw * 1e10.
|
|
97
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
98
|
+
assertEq(price18, uint256(rawPrice) * 1e10, "18-decimal mismatch");
|
|
99
|
+
|
|
100
|
+
// 27 decimals = raw * 1e19.
|
|
101
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
102
|
+
assertEq(price27, uint256(rawPrice) * 1e19, "27-decimal mismatch");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ------------------------------------------------------------------
|
|
106
|
+
// JBPrices integration — direct feed
|
|
107
|
+
// ------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
/// @notice Register ETH/USD feed and resolve via pricePerUnitOf.
|
|
110
|
+
function test_pricePerUnitOf_directFeed() public {
|
|
111
|
+
vm.prank(owner);
|
|
112
|
+
prices.addPriceFeedFor(0, CURRENCY_USD, CURRENCY_ETH, IJBPriceFeed(address(ethUsdPriceFeed)));
|
|
113
|
+
|
|
114
|
+
uint256 priceFromPrices = prices.pricePerUnitOf(0, CURRENCY_USD, CURRENCY_ETH, 18);
|
|
115
|
+
uint256 priceFromFeed = ethUsdPriceFeed.currentUnitPrice(18);
|
|
116
|
+
|
|
117
|
+
assertEq(priceFromPrices, priceFromFeed, "Direct feed mismatch");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ------------------------------------------------------------------
|
|
121
|
+
// JBPrices integration — inverse feed
|
|
122
|
+
// ------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
/// @notice Register ETH/USD feed but query USD/ETH — verify inverse calculation.
|
|
125
|
+
function test_pricePerUnitOf_inverseFeed() public {
|
|
126
|
+
vm.prank(owner);
|
|
127
|
+
prices.addPriceFeedFor(0, CURRENCY_USD, CURRENCY_ETH, IJBPriceFeed(address(ethUsdPriceFeed)));
|
|
128
|
+
|
|
129
|
+
uint256 inversePrice = prices.pricePerUnitOf(0, CURRENCY_ETH, CURRENCY_USD, 18);
|
|
130
|
+
|
|
131
|
+
// inverse = (1e18 * 1e18) / ethUsdPrice. For ETH ~$2000, inverse ~0.0005e18 = 5e14.
|
|
132
|
+
// Sane range: $500–$50k → inverse 2e13–2e15.
|
|
133
|
+
assertGt(inversePrice, 2e13, "Inverse price too low");
|
|
134
|
+
assertLt(inversePrice, 2e15, "Inverse price too high");
|
|
135
|
+
|
|
136
|
+
// Verify round-trip: price * inverse ≈ 1e18 (within mulDiv rounding).
|
|
137
|
+
uint256 directPrice = ethUsdPriceFeed.currentUnitPrice(18);
|
|
138
|
+
uint256 product = (directPrice * inversePrice) / 1e18;
|
|
139
|
+
// mulDiv truncation can cause up to ~1e4 wei error at these magnitudes.
|
|
140
|
+
assertApproxEqAbs(product, 1e18, 1e4, "Round-trip mismatch");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ------------------------------------------------------------------
|
|
144
|
+
// JBPrices — same currency
|
|
145
|
+
// ------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
/// @notice pricePerUnitOf(X, X, 18) should return 1e18 without any feed.
|
|
148
|
+
function test_pricePerUnitOf_sameCurrency() public view {
|
|
149
|
+
uint256 price = prices.pricePerUnitOf(0, CURRENCY_ETH, CURRENCY_ETH, 18);
|
|
150
|
+
assertEq(price, 1e18, "Same currency should be 1e18");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ------------------------------------------------------------------
|
|
154
|
+
// JBPrices — default fallback
|
|
155
|
+
// ------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
/// @notice Feed registered at projectId=0 should be used when querying projectId=99.
|
|
158
|
+
function test_pricePerUnitOf_defaultFallback() public {
|
|
159
|
+
vm.prank(owner);
|
|
160
|
+
prices.addPriceFeedFor(0, CURRENCY_USD, CURRENCY_ETH, IJBPriceFeed(address(ethUsdPriceFeed)));
|
|
161
|
+
|
|
162
|
+
uint256 defaultPrice = prices.pricePerUnitOf(0, CURRENCY_USD, CURRENCY_ETH, 18);
|
|
163
|
+
uint256 fallbackPrice = prices.pricePerUnitOf(99, CURRENCY_USD, CURRENCY_ETH, 18);
|
|
164
|
+
|
|
165
|
+
assertEq(fallbackPrice, defaultPrice, "Fallback should match default");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ------------------------------------------------------------------
|
|
169
|
+
// Stale price revert
|
|
170
|
+
// ------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
/// @notice Warping past the threshold should cause a StalePrice revert.
|
|
173
|
+
function test_stalePriceReverts() public {
|
|
174
|
+
// Snapshot updatedAt from the feed at this block.
|
|
175
|
+
(,,, uint256 updatedAt,) = AggregatorV3Interface(ETH_USD_FEED).latestRoundData();
|
|
176
|
+
|
|
177
|
+
// Warp far enough that the feed's updatedAt + threshold < block.timestamp.
|
|
178
|
+
uint256 warpTo = block.timestamp + 7200;
|
|
179
|
+
vm.warp(warpTo);
|
|
180
|
+
|
|
181
|
+
vm.expectRevert(
|
|
182
|
+
abi.encodeWithSelector(
|
|
183
|
+
JBChainlinkV3PriceFeed.JBChainlinkV3PriceFeed_StalePrice.selector, warpTo, THRESHOLD, updatedAt
|
|
184
|
+
)
|
|
185
|
+
);
|
|
186
|
+
ethUsdPriceFeed.currentUnitPrice(18);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ------------------------------------------------------------------
|
|
190
|
+
// Feed immutability
|
|
191
|
+
// ------------------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
/// @notice Registering the same currency pair twice should revert.
|
|
194
|
+
function test_feedImmutability() public {
|
|
195
|
+
vm.prank(owner);
|
|
196
|
+
prices.addPriceFeedFor(0, CURRENCY_USD, CURRENCY_ETH, IJBPriceFeed(address(ethUsdPriceFeed)));
|
|
197
|
+
|
|
198
|
+
vm.prank(owner);
|
|
199
|
+
vm.expectRevert(
|
|
200
|
+
abi.encodeWithSelector(
|
|
201
|
+
JBPrices.JBPrices_PriceFeedAlreadyExists.selector, IJBPriceFeed(address(ethUsdPriceFeed))
|
|
202
|
+
)
|
|
203
|
+
);
|
|
204
|
+
prices.addPriceFeedFor(0, CURRENCY_USD, CURRENCY_ETH, IJBPriceFeed(address(btcUsdPriceFeed)));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ------------------------------------------------------------------
|
|
208
|
+
// Multiple feeds
|
|
209
|
+
// ------------------------------------------------------------------
|
|
210
|
+
|
|
211
|
+
/// @notice Register both ETH/USD and BTC/USD and verify independent resolution.
|
|
212
|
+
function test_multipleFeeds_ethUsd_btcUsd() public {
|
|
213
|
+
vm.startPrank(owner);
|
|
214
|
+
prices.addPriceFeedFor(0, CURRENCY_USD, CURRENCY_ETH, IJBPriceFeed(address(ethUsdPriceFeed)));
|
|
215
|
+
prices.addPriceFeedFor(0, CURRENCY_USD, CURRENCY_BTC, IJBPriceFeed(address(btcUsdPriceFeed)));
|
|
216
|
+
vm.stopPrank();
|
|
217
|
+
|
|
218
|
+
uint256 ethUsd = prices.pricePerUnitOf(0, CURRENCY_USD, CURRENCY_ETH, 18);
|
|
219
|
+
uint256 btcUsd = prices.pricePerUnitOf(0, CURRENCY_USD, CURRENCY_BTC, 18);
|
|
220
|
+
|
|
221
|
+
// ETH/USD: $500–$50,000.
|
|
222
|
+
assertGt(ethUsd, 500e18, "ETH/USD too low");
|
|
223
|
+
assertLt(ethUsd, 50_000e18, "ETH/USD too high");
|
|
224
|
+
|
|
225
|
+
// BTC/USD: $10,000–$500,000.
|
|
226
|
+
assertGt(btcUsd, 10_000e18, "BTC/USD too low");
|
|
227
|
+
assertLt(btcUsd, 500_000e18, "BTC/USD too high");
|
|
228
|
+
|
|
229
|
+
// BTC should be more expensive than ETH.
|
|
230
|
+
assertGt(btcUsd, ethUsd, "BTC should cost more than ETH");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ------------------------------------------------------------------
|
|
234
|
+
// Cross-price derivation
|
|
235
|
+
// ------------------------------------------------------------------
|
|
236
|
+
|
|
237
|
+
/// @notice Derive ETH/BTC price from ETH/USD and BTC/USD feeds.
|
|
238
|
+
function test_crossPriceDerived() public {
|
|
239
|
+
vm.startPrank(owner);
|
|
240
|
+
prices.addPriceFeedFor(0, CURRENCY_USD, CURRENCY_ETH, IJBPriceFeed(address(ethUsdPriceFeed)));
|
|
241
|
+
prices.addPriceFeedFor(0, CURRENCY_USD, CURRENCY_BTC, IJBPriceFeed(address(btcUsdPriceFeed)));
|
|
242
|
+
vm.stopPrank();
|
|
243
|
+
|
|
244
|
+
uint256 ethUsd = prices.pricePerUnitOf(0, CURRENCY_USD, CURRENCY_ETH, 18);
|
|
245
|
+
uint256 btcUsd = prices.pricePerUnitOf(0, CURRENCY_USD, CURRENCY_BTC, 18);
|
|
246
|
+
|
|
247
|
+
// ETH/BTC = ethUsd / btcUsd (how many BTC per 1 ETH).
|
|
248
|
+
uint256 ethPerBtc = (ethUsd * 1e18) / btcUsd;
|
|
249
|
+
|
|
250
|
+
// Should be between 0.01 and 0.1 (at 18 decimals: 1e16–1e17).
|
|
251
|
+
assertGt(ethPerBtc, 1e16, "ETH/BTC ratio too low");
|
|
252
|
+
assertLt(ethPerBtc, 1e17, "ETH/BTC ratio too high");
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity ^0.8.17;
|
|
3
3
|
|
|
4
|
-
import "forge-std/Test.sol";
|
|
5
|
-
import {mulDiv} from "@prb/math/src/Common.sol";
|
|
4
|
+
import {Test} from "forge-std/Test.sol";
|
|
6
5
|
import {JBCashOuts} from "../../src/libraries/JBCashOuts.sol";
|
|
7
6
|
import {JBFees} from "../../src/libraries/JBFees.sol";
|
|
8
7
|
import {JBConstants} from "../../src/libraries/JBConstants.sol";
|
|
@@ -22,6 +21,7 @@ contract BondingCurveProperties is Test {
|
|
|
22
21
|
// Property 1: Boundedness — cashOutFrom never exceeds surplus
|
|
23
22
|
// =========================================================================
|
|
24
23
|
/// @notice cashOutFrom(S, c, T, r) <= S for all valid inputs.
|
|
24
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
25
25
|
function check_cashOut_boundedness(
|
|
26
26
|
uint256 surplus,
|
|
27
27
|
uint256 cashOutCount,
|
|
@@ -63,6 +63,7 @@ contract BondingCurveProperties is Test {
|
|
|
63
63
|
// Property 2: Monotonicity — more tokens → more reclaim
|
|
64
64
|
// =========================================================================
|
|
65
65
|
/// @notice cashOutFrom(S, c1, T, r) <= cashOutFrom(S, c2, T, r) when c1 <= c2.
|
|
66
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
66
67
|
function check_cashOut_monotonicity(
|
|
67
68
|
uint256 surplus,
|
|
68
69
|
uint256 c1,
|
|
@@ -113,6 +114,7 @@ contract BondingCurveProperties is Test {
|
|
|
113
114
|
// Property 3: Full redemption — when c >= T, result is S (full surplus)
|
|
114
115
|
// =========================================================================
|
|
115
116
|
/// @notice When cashOutCount >= totalSupply, the full surplus is returned.
|
|
117
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
116
118
|
function check_cashOut_fullRedemption(uint256 surplus, uint256 totalSupply, uint256 cashOutTaxRate) public pure {
|
|
117
119
|
vm.assume(surplus > 0 && surplus <= type(uint128).max);
|
|
118
120
|
vm.assume(totalSupply > 0 && totalSupply <= type(uint128).max);
|
|
@@ -137,6 +139,7 @@ contract BondingCurveProperties is Test {
|
|
|
137
139
|
// Property 4: Max tax → zero reclaim
|
|
138
140
|
// =========================================================================
|
|
139
141
|
/// @notice When cashOutTaxRate == MAX_CASH_OUT_TAX_RATE, result is 0.
|
|
142
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
140
143
|
function check_cashOut_maxTaxIsZero(uint256 surplus, uint256 cashOutCount, uint256 totalSupply) public pure {
|
|
141
144
|
vm.assume(surplus > 0 && surplus <= type(uint128).max);
|
|
142
145
|
vm.assume(totalSupply > 0 && totalSupply <= type(uint128).max);
|
|
@@ -161,6 +164,7 @@ contract BondingCurveProperties is Test {
|
|
|
161
164
|
/// @notice Splitting a cash out into two parts should never yield more than a single cash out.
|
|
162
165
|
/// cashOutFrom(S, a, T, r) + cashOutFrom(S', b, T', r) <= cashOutFrom(S, a+b, T, r)
|
|
163
166
|
/// where S' = S - cashOutFrom(S, a, T, r) and T' = T - a
|
|
167
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
164
168
|
function check_cashOut_noArbitrage(
|
|
165
169
|
uint256 surplus,
|
|
166
170
|
uint256 a,
|
|
@@ -245,6 +249,7 @@ contract BondingCurveProperties is Test {
|
|
|
245
249
|
/// @notice The forward and reverse fee functions should be consistent:
|
|
246
250
|
/// amount - feeAmountFrom(amount, fee) + feeAmountResultingIn(net, fee) >= feeAmountFrom(amount, fee)
|
|
247
251
|
/// where net = amount - feeAmountFrom(amount, fee)
|
|
252
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
248
253
|
function check_fee_roundTrip(uint256 amount, uint256 feePercent) public pure {
|
|
249
254
|
vm.assume(amount > 0 && amount <= type(uint128).max);
|
|
250
255
|
vm.assume(feePercent > 0 && feePercent < MAX_FEE);
|
|
@@ -276,6 +281,7 @@ contract BondingCurveProperties is Test {
|
|
|
276
281
|
// Property 7: Metadata packing round-trip
|
|
277
282
|
// =========================================================================
|
|
278
283
|
/// @notice packRulesetMetadata(m) → expandMetadata should return the original metadata.
|
|
284
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
279
285
|
function check_metadataPacking_roundTrip(
|
|
280
286
|
uint16 reservedPercent,
|
|
281
287
|
uint16 cashOutTaxRate,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity ^0.8.17;
|
|
3
3
|
|
|
4
|
-
import "forge-std/Test.sol";
|
|
4
|
+
import {Test} from "forge-std/Test.sol";
|
|
5
5
|
import {JBFees} from "../../src/libraries/JBFees.sol";
|
|
6
6
|
import {JBConstants} from "../../src/libraries/JBConstants.sol";
|
|
7
7
|
|
|
@@ -17,6 +17,7 @@ contract FeeProperties is Test {
|
|
|
17
17
|
// =========================================================================
|
|
18
18
|
/// @notice feeAmountFrom(a+b, fee) vs feeAmountFrom(a, fee) + feeAmountFrom(b, fee)
|
|
19
19
|
/// differ by at most 1 wei due to mulDiv rounding.
|
|
20
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
20
21
|
function check_fee_additivity(uint256 a, uint256 b, uint256 feePercent) public pure {
|
|
21
22
|
vm.assume(a > 0 && b > 0);
|
|
22
23
|
vm.assume(a <= type(uint128).max && b <= type(uint128).max);
|
|
@@ -53,6 +54,7 @@ contract FeeProperties is Test {
|
|
|
53
54
|
/// @notice feeAmountResultingIn(netAmount, fee) >= feeAmountFrom(netAmount + feeAmountResultingIn(netAmount, fee),
|
|
54
55
|
// fee) / The reverse fee is always >= the forward fee on the gross amount, ensuring
|
|
55
56
|
/// the protocol never undercharges when returning held fees.
|
|
57
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
56
58
|
function check_fee_returnConsistency(uint256 netAmount, uint256 feePercent) public pure {
|
|
57
59
|
vm.assume(netAmount > 0 && netAmount <= type(uint128).max);
|
|
58
60
|
vm.assume(feePercent > 0 && feePercent < MAX_FEE);
|
|
@@ -91,6 +93,7 @@ contract FeeProperties is Test {
|
|
|
91
93
|
/// The overshoot is bounded by MAX_FEE / (MAX_FEE - feePercent): the rounding
|
|
92
94
|
/// error in fee1 (at most 1 wei of net) gets amplified by the fee ratio when
|
|
93
95
|
/// computing the reverse fee, plus 1 for the reverse mulDiv rounding itself.
|
|
96
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
94
97
|
function check_fee_roundTrip(uint256 amount, uint256 feePercent) public pure {
|
|
95
98
|
vm.assume(amount > 0 && amount <= type(uint128).max);
|
|
96
99
|
vm.assume(feePercent > 0 && feePercent < MAX_FEE);
|
|
@@ -133,6 +136,7 @@ contract FeeProperties is Test {
|
|
|
133
136
|
// Property 4: Partial return monotonicity
|
|
134
137
|
// =========================================================================
|
|
135
138
|
/// @notice feeAmountResultingIn(a, fee) <= feeAmountResultingIn(b, fee) when a <= b.
|
|
139
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
136
140
|
function check_fee_partialReturnMonotonicity(uint256 a, uint256 b, uint256 feePercent) public pure {
|
|
137
141
|
vm.assume(a > 0 && b > 0);
|
|
138
142
|
vm.assume(a <= type(uint128).max && b <= type(uint128).max);
|
|
@@ -163,6 +167,7 @@ contract FeeProperties is Test {
|
|
|
163
167
|
/// fee = feeAmountFrom(heldFeeAmount, feePercent) <= heldFeeAmount.
|
|
164
168
|
/// This guarantees leftover = heldFeeAmount - fee never underflows, and
|
|
165
169
|
/// leftover + fee == heldFeeAmount (exact decomposition).
|
|
170
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
166
171
|
function check_fee_subtractionSafety(uint256 heldFeeAmount, uint256 feePercent) public pure {
|
|
167
172
|
vm.assume(heldFeeAmount > 0 && heldFeeAmount <= type(uint128).max);
|
|
168
173
|
vm.assume(feePercent > 0 && feePercent < MAX_FEE);
|
|
@@ -195,6 +200,7 @@ contract FeeProperties is Test {
|
|
|
195
200
|
/// @notice After N splits each paying fee, total fee error vs single-payment fee
|
|
196
201
|
/// is bounded by N wei. For N=10: sum(feeAmountFrom(amount/N, fee), N times)
|
|
197
202
|
/// vs feeAmountFrom(amount, fee) differ by at most N.
|
|
203
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
198
204
|
function check_fee_multiSplitAccumulation(uint256 amount, uint256 feePercent) public pure {
|
|
199
205
|
vm.assume(amount >= 10); // Must be divisible into 10 parts
|
|
200
206
|
vm.assume(amount <= type(uint128).max);
|
package/test/helpers/JBTest.sol
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity 0.8.26;
|
|
3
3
|
|
|
4
|
-
import {JBRuleset} from "src/structs/JBRuleset.sol";
|
|
5
|
-
import {JBRulesetMetadata} from "src/structs/JBRulesetMetadata.sol";
|
|
6
|
-
import {JBRulesetMetadataResolver} from "src/libraries/JBRulesetMetadataResolver.sol";
|
|
7
|
-
import {JBConstants} from "src/libraries/JBConstants.sol";
|
|
8
|
-
import {IJBRulesetApprovalHook} from "src/interfaces/IJBRulesetApprovalHook.sol";
|
|
9
|
-
import "lib/forge-std/src/Test.sol";
|
|
4
|
+
import {JBRuleset} from "../../src/structs/JBRuleset.sol";
|
|
5
|
+
import {JBRulesetMetadata} from "../../src/structs/JBRulesetMetadata.sol";
|
|
6
|
+
import {JBRulesetMetadataResolver} from "../../src/libraries/JBRulesetMetadataResolver.sol";
|
|
7
|
+
import {JBConstants} from "../../src/libraries/JBConstants.sol";
|
|
8
|
+
import {IJBRulesetApprovalHook} from "../../src/interfaces/IJBRulesetApprovalHook.sol";
|
|
9
|
+
import {Test} from "lib/forge-std/src/Test.sol";
|
|
10
10
|
|
|
11
11
|
contract JBTest is Test {
|
|
12
12
|
using JBRulesetMetadataResolver for JBRulesetMetadata;
|
|
@@ -65,7 +65,7 @@ contract JBTest is Test {
|
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
function generateEmptyMetadata() public
|
|
68
|
+
function generateEmptyMetadata() public pure returns (JBRulesetMetadata memory) {
|
|
69
69
|
return JBRulesetMetadata({
|
|
70
70
|
reservedPercent: 0,
|
|
71
71
|
cashOutTaxRate: 0,
|
|
@@ -1,18 +1,32 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity ^0.8.6;
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// forge-lint: disable-next-line(unused-import)
|
|
5
|
+
import {Test} from "forge-std/Test.sol";
|
|
6
|
+
// forge-lint: disable-next-line(unused-import)
|
|
7
|
+
import {StdStorage, stdStorage} from "forge-std/StdStorage.sol";
|
|
5
8
|
|
|
9
|
+
// forge-lint: disable-next-line(unused-import)
|
|
6
10
|
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
11
|
+
// forge-lint: disable-next-line(unused-import)
|
|
7
12
|
import {IERC721Metadata} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
|
|
13
|
+
// forge-lint: disable-next-line(unused-import)
|
|
8
14
|
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
|
15
|
+
// forge-lint: disable-next-line(unused-import)
|
|
9
16
|
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
|
|
17
|
+
// forge-lint: disable-next-line(unused-import)
|
|
10
18
|
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
|
|
19
|
+
// forge-lint: disable-next-line(unused-import)
|
|
11
20
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
21
|
+
// forge-lint: disable-next-line(unused-import)
|
|
12
22
|
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
23
|
+
// forge-lint: disable-next-line(unused-import)
|
|
13
24
|
import {ERC165, IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
|
|
25
|
+
// forge-lint: disable-next-line(unused-import)
|
|
14
26
|
import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
|
|
27
|
+
// forge-lint: disable-next-line(unused-import)
|
|
15
28
|
import {JBControlled} from "../../src/abstract/JBControlled.sol";
|
|
29
|
+
// forge-lint: disable-next-line(unused-import)
|
|
16
30
|
import {JBPermissioned} from "../../src/abstract/JBPermissioned.sol";
|
|
17
31
|
import {JBController} from "../../src/JBController.sol";
|
|
18
32
|
import {JBDirectory} from "../../src/JBDirectory.sol";
|
|
@@ -26,70 +40,132 @@ import {JBProjects} from "../../src/JBProjects.sol";
|
|
|
26
40
|
import {JBSplits} from "../../src/JBSplits.sol";
|
|
27
41
|
import {JBERC20} from "../../src/JBERC20.sol";
|
|
28
42
|
import {JBTokens} from "../../src/JBTokens.sol";
|
|
43
|
+
// forge-lint: disable-next-line(unused-import)
|
|
29
44
|
import {JBDeadline} from "../../src/JBDeadline.sol";
|
|
45
|
+
// forge-lint: disable-next-line(unused-import)
|
|
30
46
|
import {JBApprovalStatus} from "../../src/enums/JBApprovalStatus.sol";
|
|
31
47
|
import {JBMultiTerminal} from "../../src/JBMultiTerminal.sol";
|
|
48
|
+
// forge-lint: disable-next-line(unused-import)
|
|
32
49
|
import {JBAccountingContext} from "../../src/structs/JBAccountingContext.sol";
|
|
50
|
+
// forge-lint: disable-next-line(unused-import)
|
|
33
51
|
import {JBCurrencyAmount} from "../../src/structs/JBCurrencyAmount.sol";
|
|
52
|
+
// forge-lint: disable-next-line(unused-import)
|
|
34
53
|
import {JBAfterPayRecordedContext} from "../../src/structs/JBAfterPayRecordedContext.sol";
|
|
54
|
+
// forge-lint: disable-next-line(unused-import)
|
|
35
55
|
import {JBAfterCashOutRecordedContext} from "../../src/structs/JBAfterCashOutRecordedContext.sol";
|
|
56
|
+
// forge-lint: disable-next-line(unused-import)
|
|
36
57
|
import {JBFee} from "../../src/structs/JBFee.sol";
|
|
58
|
+
// forge-lint: disable-next-line(unused-import)
|
|
37
59
|
import {JBFees} from "../../src/libraries/JBFees.sol";
|
|
60
|
+
// forge-lint: disable-next-line(unused-import)
|
|
38
61
|
import {JBMetadataResolver} from "../../src/libraries/JBMetadataResolver.sol";
|
|
62
|
+
// forge-lint: disable-next-line(unused-import)
|
|
39
63
|
import {JBCashOuts} from "../../src/libraries/JBCashOuts.sol";
|
|
64
|
+
// forge-lint: disable-next-line(unused-import)
|
|
40
65
|
import {JBFundAccessLimitGroup} from "../../src/structs/JBFundAccessLimitGroup.sol";
|
|
66
|
+
// forge-lint: disable-next-line(unused-import)
|
|
41
67
|
import {JBRuleset} from "../../src/structs/JBRuleset.sol";
|
|
68
|
+
// forge-lint: disable-next-line(unused-import)
|
|
42
69
|
import {JBRulesetWithMetadata} from "../../src/structs/JBRulesetWithMetadata.sol";
|
|
70
|
+
// forge-lint: disable-next-line(unused-import)
|
|
43
71
|
import {JBRulesetMetadata} from "../../src/structs/JBRulesetMetadata.sol";
|
|
72
|
+
// forge-lint: disable-next-line(unused-import)
|
|
44
73
|
import {JBRulesetConfig} from "../../src/structs/JBRulesetConfig.sol";
|
|
74
|
+
// forge-lint: disable-next-line(unused-import)
|
|
45
75
|
import {JBSplitGroup} from "../../src/structs/JBSplitGroup.sol";
|
|
76
|
+
// forge-lint: disable-next-line(unused-import)
|
|
46
77
|
import {JBPermissionsData} from "../../src/structs/JBPermissionsData.sol";
|
|
78
|
+
// forge-lint: disable-next-line(unused-import)
|
|
47
79
|
import {JBBeforePayRecordedContext} from "../../src/structs/JBBeforePayRecordedContext.sol";
|
|
80
|
+
// forge-lint: disable-next-line(unused-import)
|
|
48
81
|
import {JBBeforeCashOutRecordedContext} from "../../src/structs/JBBeforeCashOutRecordedContext.sol";
|
|
82
|
+
// forge-lint: disable-next-line(unused-import)
|
|
49
83
|
import {JBSplit} from "../../src/structs/JBSplit.sol";
|
|
84
|
+
// forge-lint: disable-next-line(unused-import)
|
|
50
85
|
import {JBTerminalConfig} from "../../src/structs/JBTerminalConfig.sol";
|
|
86
|
+
// forge-lint: disable-next-line(unused-import)
|
|
51
87
|
import {JBPayHookSpecification} from "../../src/structs/JBPayHookSpecification.sol";
|
|
88
|
+
// forge-lint: disable-next-line(unused-import)
|
|
52
89
|
import {JBCashOutHookSpecification} from "../../src/structs/JBCashOutHookSpecification.sol";
|
|
90
|
+
// forge-lint: disable-next-line(unused-import)
|
|
53
91
|
import {JBTokenAmount} from "../../src/structs/JBTokenAmount.sol";
|
|
92
|
+
// forge-lint: disable-next-line(unused-import)
|
|
54
93
|
import {JBSplitHookContext} from "../../src/structs/JBSplitHookContext.sol";
|
|
94
|
+
// forge-lint: disable-next-line(unused-import)
|
|
55
95
|
import {IJBToken} from "../../src/interfaces/IJBToken.sol";
|
|
96
|
+
// forge-lint: disable-next-line(unused-import)
|
|
56
97
|
import {JBSingleAllowance} from "../../src/structs/JBSingleAllowance.sol";
|
|
98
|
+
// forge-lint: disable-next-line(unused-import)
|
|
57
99
|
import {IJBController} from "../../src/interfaces/IJBController.sol";
|
|
100
|
+
// forge-lint: disable-next-line(unused-import)
|
|
58
101
|
import {IJBFeelessAddresses} from "../../src/interfaces/IJBFeelessAddresses.sol";
|
|
102
|
+
// forge-lint: disable-next-line(unused-import)
|
|
59
103
|
import {IJBFundAccessLimits} from "../../src/interfaces/IJBFundAccessLimits.sol";
|
|
104
|
+
// forge-lint: disable-next-line(unused-import)
|
|
60
105
|
import {IJBMigratable} from "../../src/interfaces/IJBMigratable.sol";
|
|
106
|
+
// forge-lint: disable-next-line(unused-import)
|
|
61
107
|
import {IJBPermissions} from "../../src/interfaces/IJBPermissions.sol";
|
|
108
|
+
// forge-lint: disable-next-line(unused-import)
|
|
62
109
|
import {IJBDirectoryAccessControl} from "../../src/interfaces/IJBDirectoryAccessControl.sol";
|
|
110
|
+
// forge-lint: disable-next-line(unused-import)
|
|
63
111
|
import {IJBTerminalStore} from "../../src/interfaces/IJBTerminalStore.sol";
|
|
112
|
+
// forge-lint: disable-next-line(unused-import)
|
|
64
113
|
import {IJBProjects} from "../../src/interfaces/IJBProjects.sol";
|
|
114
|
+
// forge-lint: disable-next-line(unused-import)
|
|
65
115
|
import {IJBRulesetApprovalHook} from "../../src/interfaces/IJBRulesetApprovalHook.sol";
|
|
116
|
+
// forge-lint: disable-next-line(unused-import)
|
|
66
117
|
import {IJBDirectory} from "../../src/interfaces/IJBDirectory.sol";
|
|
118
|
+
// forge-lint: disable-next-line(unused-import)
|
|
67
119
|
import {IJBRulesets} from "../../src/interfaces/IJBRulesets.sol";
|
|
120
|
+
// forge-lint: disable-next-line(unused-import)
|
|
68
121
|
import {IJBSplits} from "../../src/interfaces/IJBSplits.sol";
|
|
122
|
+
// forge-lint: disable-next-line(unused-import)
|
|
69
123
|
import {IJBTokenUriResolver} from "../../src/interfaces/IJBTokenUriResolver.sol";
|
|
124
|
+
// forge-lint: disable-next-line(unused-import)
|
|
70
125
|
import {IJBTokens} from "../../src/interfaces/IJBTokens.sol";
|
|
126
|
+
// forge-lint: disable-next-line(unused-import)
|
|
71
127
|
import {IJBSplitHook} from "../../src/interfaces/IJBSplitHook.sol";
|
|
128
|
+
// forge-lint: disable-next-line(unused-import)
|
|
72
129
|
import {IJBPayHook} from "../../src/interfaces/IJBPayHook.sol";
|
|
130
|
+
// forge-lint: disable-next-line(unused-import)
|
|
73
131
|
import {IJBRulesetDataHook} from "../../src/interfaces/IJBRulesetDataHook.sol";
|
|
132
|
+
// forge-lint: disable-next-line(unused-import)
|
|
74
133
|
import {IJBCashOutHook} from "../../src/interfaces/IJBCashOutHook.sol";
|
|
134
|
+
// forge-lint: disable-next-line(unused-import)
|
|
75
135
|
import {IJBRulesetDataHook} from "../../src/interfaces/IJBRulesetDataHook.sol";
|
|
136
|
+
// forge-lint: disable-next-line(unused-import)
|
|
76
137
|
import {IJBMultiTerminal} from "../../src/interfaces/IJBMultiTerminal.sol";
|
|
138
|
+
// forge-lint: disable-next-line(unused-import)
|
|
77
139
|
import {IJBCashOutTerminal} from "../../src/interfaces/IJBCashOutTerminal.sol";
|
|
140
|
+
// forge-lint: disable-next-line(unused-import)
|
|
78
141
|
import {IJBPayoutTerminal} from "../../src/interfaces/IJBPayoutTerminal.sol";
|
|
142
|
+
// forge-lint: disable-next-line(unused-import)
|
|
79
143
|
import {IJBPermitTerminal} from "../../src/interfaces/IJBPermitTerminal.sol";
|
|
144
|
+
// forge-lint: disable-next-line(unused-import)
|
|
80
145
|
import {IJBFeeTerminal} from "../../src/interfaces/IJBFeeTerminal.sol";
|
|
146
|
+
// forge-lint: disable-next-line(unused-import)
|
|
81
147
|
import {IJBTerminal} from "../../src/interfaces/IJBTerminal.sol";
|
|
148
|
+
// forge-lint: disable-next-line(unused-import)
|
|
82
149
|
import {IJBPriceFeed} from "../../src/interfaces/IJBPriceFeed.sol";
|
|
150
|
+
// forge-lint: disable-next-line(unused-import)
|
|
83
151
|
import {IJBPermissioned} from "../../src/interfaces/IJBPermissioned.sol";
|
|
152
|
+
// forge-lint: disable-next-line(unused-import)
|
|
84
153
|
import {IJBProjectUriRegistry} from "../../src/interfaces/IJBProjectUriRegistry.sol";
|
|
154
|
+
// forge-lint: disable-next-line(unused-import)
|
|
85
155
|
import {IJBRulesetApprovalHook} from "../../src/interfaces/IJBRulesetApprovalHook.sol";
|
|
156
|
+
// forge-lint: disable-next-line(unused-import)
|
|
86
157
|
import {IJBPrices} from "../../src/interfaces/IJBPrices.sol";
|
|
87
158
|
|
|
159
|
+
// forge-lint: disable-next-line(unused-import)
|
|
88
160
|
import {JBConstants} from "../../src/libraries/JBConstants.sol";
|
|
161
|
+
// forge-lint: disable-next-line(unused-import)
|
|
89
162
|
import {JBCurrencyIds} from "../../src/libraries/JBCurrencyIds.sol";
|
|
163
|
+
// forge-lint: disable-next-line(unused-import)
|
|
90
164
|
import {JBRulesetMetadataResolver} from "../../src/libraries/JBRulesetMetadataResolver.sol";
|
|
165
|
+
// forge-lint: disable-next-line(unused-import)
|
|
91
166
|
import {JBSplitGroupIds} from "../../src/libraries/JBSplitGroupIds.sol";
|
|
92
167
|
|
|
168
|
+
// forge-lint: disable-next-line(unused-import)
|
|
93
169
|
import {IPermit2, IAllowanceTransfer} from "@uniswap/permit2/src/interfaces/IPermit2.sol";
|
|
94
170
|
import {DeployPermit2} from "@uniswap/permit2/test/utils/DeployPermit2.sol";
|
|
95
171
|
|
|
@@ -98,7 +174,9 @@ import {JBTest} from "./JBTest.sol";
|
|
|
98
174
|
|
|
99
175
|
import {MockERC20} from "./../mock/MockERC20.sol";
|
|
100
176
|
|
|
177
|
+
// forge-lint: disable-next-line(unused-import)
|
|
101
178
|
import {mulDiv} from "@prb/math/src/Common.sol";
|
|
179
|
+
// forge-lint: disable-next-line(unused-import)
|
|
102
180
|
import {mul as UD60x18mul, wrap as UD60x18wrap, unwrap as UD60x18unwrap} from "@prb/math/src/UD60x18.sol";
|
|
103
181
|
|
|
104
182
|
// Base contract for Juicebox system tests.
|
|
@@ -294,14 +372,19 @@ contract TestBaseWorkflow is JBTest, DeployPermit2 {
|
|
|
294
372
|
if (_nonce == 0x00) {
|
|
295
373
|
data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), _origin, bytes1(0x80));
|
|
296
374
|
} else if (_nonce <= 0x7f) {
|
|
375
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
297
376
|
data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), _origin, uint8(_nonce));
|
|
298
377
|
} else if (_nonce <= 0xff) {
|
|
378
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
299
379
|
data = abi.encodePacked(bytes1(0xd7), bytes1(0x94), _origin, bytes1(0x81), uint8(_nonce));
|
|
300
380
|
} else if (_nonce <= 0xffff) {
|
|
381
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
301
382
|
data = abi.encodePacked(bytes1(0xd8), bytes1(0x94), _origin, bytes1(0x82), uint16(_nonce));
|
|
302
383
|
} else if (_nonce <= 0xffffff) {
|
|
384
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
303
385
|
data = abi.encodePacked(bytes1(0xd9), bytes1(0x94), _origin, bytes1(0x83), uint24(_nonce));
|
|
304
386
|
} else {
|
|
387
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
305
388
|
data = abi.encodePacked(bytes1(0xda), bytes1(0x94), _origin, bytes1(0x84), uint32(_nonce));
|
|
306
389
|
}
|
|
307
390
|
bytes32 hash = keccak256(data);
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity ^0.8.6;
|
|
3
3
|
|
|
4
|
-
import "forge-std/StdInvariant.sol";
|
|
5
|
-
import
|
|
4
|
+
import {StdInvariant} from "forge-std/StdInvariant.sol";
|
|
5
|
+
import {TestBaseWorkflow} from "../helpers/TestBaseWorkflow.sol";
|
|
6
|
+
import {IJBRulesetApprovalHook} from "../../src/interfaces/IJBRulesetApprovalHook.sol";
|
|
7
|
+
import {IJBSplitHook} from "../../src/interfaces/IJBSplitHook.sol";
|
|
8
|
+
import {JBRulesetMetadataResolver} from "../../src/libraries/JBRulesetMetadataResolver.sol";
|
|
9
|
+
import {JBCurrencyAmount} from "../../src/structs/JBCurrencyAmount.sol";
|
|
10
|
+
import {JBFundAccessLimitGroup} from "../../src/structs/JBFundAccessLimitGroup.sol";
|
|
11
|
+
import {JBRuleset} from "../../src/structs/JBRuleset.sol";
|
|
12
|
+
import {JBRulesetConfig} from "../../src/structs/JBRulesetConfig.sol";
|
|
13
|
+
import {JBRulesetMetadata} from "../../src/structs/JBRulesetMetadata.sol";
|
|
14
|
+
import {JBSplit} from "../../src/structs/JBSplit.sol";
|
|
15
|
+
import {JBSplitGroup} from "../../src/structs/JBSplitGroup.sol";
|
|
16
|
+
import {JBTerminalConfig} from "../../src/structs/JBTerminalConfig.sol";
|
|
6
17
|
import {Phase3Handler} from "./handlers/Phase3Handler.sol";
|
|
7
18
|
import {JBAccountingContext} from "../../src/structs/JBAccountingContext.sol";
|
|
8
19
|
import {JBConstants} from "../../src/libraries/JBConstants.sol";
|
|
9
|
-
import {JBFee} from "../../src/structs/JBFee.sol";
|
|
10
|
-
import {JBSplitGroupIds} from "../../src/libraries/JBSplitGroupIds.sol";
|
|
11
20
|
|
|
12
21
|
/// @title Phase3DeepInvariant
|
|
13
22
|
/// @notice Multi-project deep invariant tests with strict equality checks.
|
|
@@ -362,7 +371,6 @@ contract Phase3DeepInvariant_Local is StdInvariant, TestBaseWorkflow {
|
|
|
362
371
|
/// @notice After addToBalance(shouldReturn=true), returned fees must not exceed held fees.
|
|
363
372
|
function invariant_P3_6_heldFeeReturnBounded() public view {
|
|
364
373
|
uint256 returned = handler.ghost_totalReturnedFees(project2);
|
|
365
|
-
uint256 heldTotal = handler.ghost_totalHeldFeeAmounts(project2);
|
|
366
374
|
|
|
367
375
|
// Returned fees should never exceed what was held
|
|
368
376
|
// (heldTotal may be 0 if we never tracked, so only check when both nonzero)
|