@bananapus/core-v6 0.0.1
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/LICENSE +21 -0
- package/README.md +112 -0
- package/SKILLS.md +151 -0
- package/docs/book.css +13 -0
- package/docs/book.toml +12 -0
- package/docs/solidity.min.js +74 -0
- package/docs/src/README.md +703 -0
- package/docs/src/SUMMARY.md +94 -0
- package/docs/src/src/JBChainlinkV3PriceFeed.sol/contract.JBChainlinkV3PriceFeed.md +83 -0
- package/docs/src/src/JBChainlinkV3SequencerPriceFeed.sol/contract.JBChainlinkV3SequencerPriceFeed.md +88 -0
- package/docs/src/src/JBController.sol/contract.JBController.md +1121 -0
- package/docs/src/src/JBDeadline.sol/contract.JBDeadline.md +84 -0
- package/docs/src/src/JBDirectory.sol/contract.JBDirectory.md +294 -0
- package/docs/src/src/JBERC20.sol/contract.JBERC20.md +190 -0
- package/docs/src/src/JBFeelessAddresses.sol/contract.JBFeelessAddresses.md +80 -0
- package/docs/src/src/JBFundAccessLimits.sol/contract.JBFundAccessLimits.md +253 -0
- package/docs/src/src/JBMultiTerminal.sol/contract.JBMultiTerminal.md +1472 -0
- package/docs/src/src/JBPermissions.sol/contract.JBPermissions.md +199 -0
- package/docs/src/src/JBPrices.sol/contract.JBPrices.md +154 -0
- package/docs/src/src/JBProjects.sol/contract.JBProjects.md +131 -0
- package/docs/src/src/JBRulesets.sol/contract.JBRulesets.md +677 -0
- package/docs/src/src/JBSplits.sol/contract.JBSplits.md +237 -0
- package/docs/src/src/JBTerminalStore.sol/contract.JBTerminalStore.md +591 -0
- package/docs/src/src/JBTokens.sol/contract.JBTokens.md +353 -0
- package/docs/src/src/README.md +25 -0
- package/docs/src/src/abstract/JBControlled.sol/abstract.JBControlled.md +64 -0
- package/docs/src/src/abstract/JBPermissioned.sol/abstract.JBPermissioned.md +84 -0
- package/docs/src/src/abstract/README.md +5 -0
- package/docs/src/src/enums/JBApprovalStatus.sol/enum.JBApprovalStatus.md +17 -0
- package/docs/src/src/enums/README.md +4 -0
- package/docs/src/src/interfaces/IJBCashOutHook.sol/interface.IJBCashOutHook.md +29 -0
- package/docs/src/src/interfaces/IJBCashOutTerminal.sol/interface.IJBCashOutTerminal.md +57 -0
- package/docs/src/src/interfaces/IJBControlled.sol/interface.IJBControlled.md +12 -0
- package/docs/src/src/interfaces/IJBController.sol/interface.IJBController.md +334 -0
- package/docs/src/src/interfaces/IJBDirectory.sol/interface.IJBDirectory.md +108 -0
- package/docs/src/src/interfaces/IJBDirectoryAccessControl.sol/interface.IJBDirectoryAccessControl.md +19 -0
- package/docs/src/src/interfaces/IJBFeeTerminal.sol/interface.IJBFeeTerminal.md +91 -0
- package/docs/src/src/interfaces/IJBFeelessAddresses.sol/interface.IJBFeelessAddresses.md +26 -0
- package/docs/src/src/interfaces/IJBFundAccessLimits.sol/interface.IJBFundAccessLimits.md +88 -0
- package/docs/src/src/interfaces/IJBMigratable.sol/interface.IJBMigratable.md +29 -0
- package/docs/src/src/interfaces/IJBMultiTerminal.sol/interface.IJBMultiTerminal.md +50 -0
- package/docs/src/src/interfaces/IJBPayHook.sol/interface.IJBPayHook.md +28 -0
- package/docs/src/src/interfaces/IJBPayoutTerminal.sol/interface.IJBPayoutTerminal.md +105 -0
- package/docs/src/src/interfaces/IJBPermissioned.sol/interface.IJBPermissioned.md +12 -0
- package/docs/src/src/interfaces/IJBPermissions.sol/interface.IJBPermissions.md +74 -0
- package/docs/src/src/interfaces/IJBPermitTerminal.sol/interface.IJBPermitTerminal.md +15 -0
- package/docs/src/src/interfaces/IJBPriceFeed.sol/interface.IJBPriceFeed.md +12 -0
- package/docs/src/src/interfaces/IJBPrices.sol/interface.IJBPrices.md +74 -0
- package/docs/src/src/interfaces/IJBProjectUriRegistry.sol/interface.IJBProjectUriRegistry.md +19 -0
- package/docs/src/src/interfaces/IJBProjects.sol/interface.IJBProjects.md +49 -0
- package/docs/src/src/interfaces/IJBRulesetApprovalHook.sol/interface.IJBRulesetApprovalHook.md +35 -0
- package/docs/src/src/interfaces/IJBRulesetDataHook.sol/interface.IJBRulesetDataHook.md +97 -0
- package/docs/src/src/interfaces/IJBRulesets.sol/interface.IJBRulesets.md +165 -0
- package/docs/src/src/interfaces/IJBSplitHook.sol/interface.IJBSplitHook.md +31 -0
- package/docs/src/src/interfaces/IJBSplits.sol/interface.IJBSplits.md +35 -0
- package/docs/src/src/interfaces/IJBTerminal.sol/interface.IJBTerminal.md +141 -0
- package/docs/src/src/interfaces/IJBTerminalStore.sol/interface.IJBTerminalStore.md +198 -0
- package/docs/src/src/interfaces/IJBToken.sol/interface.IJBToken.md +54 -0
- package/docs/src/src/interfaces/IJBTokenUriResolver.sol/interface.IJBTokenUriResolver.md +12 -0
- package/docs/src/src/interfaces/IJBTokens.sol/interface.IJBTokens.md +151 -0
- package/docs/src/src/interfaces/README.md +33 -0
- package/docs/src/src/libraries/JBCashOuts.sol/library.JBCashOuts.md +40 -0
- package/docs/src/src/libraries/JBConstants.sol/library.JBConstants.md +52 -0
- package/docs/src/src/libraries/JBCurrencyIds.sol/library.JBCurrencyIds.md +19 -0
- package/docs/src/src/libraries/JBFees.sol/library.JBFees.md +52 -0
- package/docs/src/src/libraries/JBFixedPointNumber.sol/library.JBFixedPointNumber.md +12 -0
- package/docs/src/src/libraries/JBMetadataResolver.sol/library.JBMetadataResolver.md +242 -0
- package/docs/src/src/libraries/JBRulesetMetadataResolver.sol/library.JBRulesetMetadataResolver.md +180 -0
- package/docs/src/src/libraries/JBSplitGroupIds.sol/library.JBSplitGroupIds.md +14 -0
- package/docs/src/src/libraries/JBSurplus.sol/library.JBSurplus.md +44 -0
- package/docs/src/src/libraries/README.md +12 -0
- package/docs/src/src/periphery/JBDeadline1Day.sol/contract.JBDeadline1Day.md +15 -0
- package/docs/src/src/periphery/JBDeadline3Days.sol/contract.JBDeadline3Days.md +15 -0
- package/docs/src/src/periphery/JBDeadline3Hours.sol/contract.JBDeadline3Hours.md +15 -0
- package/docs/src/src/periphery/JBDeadline7Days.sol/contract.JBDeadline7Days.md +15 -0
- package/docs/src/src/periphery/JBMatchingPriceFeed.sol/contract.JBMatchingPriceFeed.md +22 -0
- package/docs/src/src/periphery/README.md +8 -0
- package/docs/src/src/structs/JBAccountingContext.sol/struct.JBAccountingContext.md +20 -0
- package/docs/src/src/structs/JBAfterCashOutRecordedContext.sol/struct.JBAfterCashOutRecordedContext.md +43 -0
- package/docs/src/src/structs/JBAfterPayRecordedContext.sol/struct.JBAfterPayRecordedContext.md +42 -0
- package/docs/src/src/structs/JBBeforeCashOutRecordedContext.sol/struct.JBBeforeCashOutRecordedContext.md +45 -0
- package/docs/src/src/structs/JBBeforePayRecordedContext.sol/struct.JBBeforePayRecordedContext.md +41 -0
- package/docs/src/src/structs/JBCashOutHookSpecification.sol/struct.JBCashOutHookSpecification.md +22 -0
- package/docs/src/src/structs/JBCurrencyAmount.sol/struct.JBCurrencyAmount.md +17 -0
- package/docs/src/src/structs/JBFee.sol/struct.JBFee.md +20 -0
- package/docs/src/src/structs/JBFundAccessLimitGroup.sol/struct.JBFundAccessLimitGroup.md +39 -0
- package/docs/src/src/structs/JBPayHookSpecification.sol/struct.JBPayHookSpecification.md +22 -0
- package/docs/src/src/structs/JBPermissionsData.sol/struct.JBPermissionsData.md +21 -0
- package/docs/src/src/structs/JBRuleset.sol/struct.JBRuleset.md +55 -0
- package/docs/src/src/structs/JBRulesetConfig.sol/struct.JBRulesetConfig.md +51 -0
- package/docs/src/src/structs/JBRulesetMetadata.sol/struct.JBRulesetMetadata.md +79 -0
- package/docs/src/src/structs/JBRulesetWeightCache.sol/struct.JBRulesetWeightCache.md +16 -0
- package/docs/src/src/structs/JBRulesetWithMetadata.sol/struct.JBRulesetWithMetadata.md +16 -0
- package/docs/src/src/structs/JBSingleAllowance.sol/struct.JBSingleAllowance.md +26 -0
- package/docs/src/src/structs/JBSplit.sol/struct.JBSplit.md +49 -0
- package/docs/src/src/structs/JBSplitGroup.sol/struct.JBSplitGroup.md +17 -0
- package/docs/src/src/structs/JBSplitHookContext.sol/struct.JBSplitHookContext.md +29 -0
- package/docs/src/src/structs/JBTerminalConfig.sol/struct.JBTerminalConfig.md +16 -0
- package/docs/src/src/structs/JBTokenAmount.sol/struct.JBTokenAmount.md +23 -0
- package/docs/src/src/structs/README.md +25 -0
- package/foundry.lock +11 -0
- package/foundry.toml +41 -0
- package/package.json +38 -0
- package/remappings.txt +1 -0
- package/script/Deploy.s.sol +111 -0
- package/script/DeployPeriphery.s.sol +287 -0
- package/script/helpers/CoreDeploymentLib.sol +121 -0
- package/slither-ci.config.json +10 -0
- package/sphinx.lock +507 -0
- package/src/JBChainlinkV3PriceFeed.sol +77 -0
- package/src/JBChainlinkV3SequencerPriceFeed.sol +75 -0
- package/src/JBController.sol +1186 -0
- package/src/JBDeadline.sol +73 -0
- package/src/JBDirectory.sol +343 -0
- package/src/JBERC20.sol +131 -0
- package/src/JBFeelessAddresses.sol +54 -0
- package/src/JBFundAccessLimits.sol +308 -0
- package/src/JBMultiTerminal.sol +2024 -0
- package/src/JBPermissions.sol +252 -0
- package/src/JBPrices.sol +227 -0
- package/src/JBProjects.sol +126 -0
- package/src/JBRulesets.sol +1093 -0
- package/src/JBSplits.sol +324 -0
- package/src/JBTerminalStore.sol +908 -0
- package/src/JBTokens.sol +376 -0
- package/src/abstract/JBControlled.sol +48 -0
- package/src/abstract/JBPermissioned.sol +77 -0
- package/src/enums/JBApprovalStatus.sol +12 -0
- package/src/interfaces/IJBCashOutHook.sol +15 -0
- package/src/interfaces/IJBCashOutTerminal.sol +51 -0
- package/src/interfaces/IJBControlled.sol +10 -0
- package/src/interfaces/IJBController.sol +280 -0
- package/src/interfaces/IJBDirectory.sol +69 -0
- package/src/interfaces/IJBDirectoryAccessControl.sol +15 -0
- package/src/interfaces/IJBFeeTerminal.sol +61 -0
- package/src/interfaces/IJBFeelessAddresses.sol +17 -0
- package/src/interfaces/IJBFundAccessLimits.sol +94 -0
- package/src/interfaces/IJBMigratable.sol +24 -0
- package/src/interfaces/IJBMultiTerminal.sol +36 -0
- package/src/interfaces/IJBPayHook.sol +14 -0
- package/src/interfaces/IJBPayoutTerminal.sol +92 -0
- package/src/interfaces/IJBPermissioned.sol +10 -0
- package/src/interfaces/IJBPermissions.sol +71 -0
- package/src/interfaces/IJBPermitTerminal.sol +14 -0
- package/src/interfaces/IJBPriceFeed.sol +10 -0
- package/src/interfaces/IJBPrices.sol +65 -0
- package/src/interfaces/IJBProjectUriRegistry.sol +15 -0
- package/src/interfaces/IJBProjects.sol +27 -0
- package/src/interfaces/IJBRulesetApprovalHook.sol +21 -0
- package/src/interfaces/IJBRulesetDataHook.sol +56 -0
- package/src/interfaces/IJBRulesets.sol +151 -0
- package/src/interfaces/IJBSplitHook.sol +16 -0
- package/src/interfaces/IJBSplits.sol +28 -0
- package/src/interfaces/IJBTerminal.sol +120 -0
- package/src/interfaces/IJBTerminalStore.sol +225 -0
- package/src/interfaces/IJBToken.sol +39 -0
- package/src/interfaces/IJBTokenUriResolver.sol +10 -0
- package/src/interfaces/IJBTokens.sol +113 -0
- package/src/libraries/JBCashOuts.sol +120 -0
- package/src/libraries/JBConstants.sol +14 -0
- package/src/libraries/JBCurrencyIds.sol +7 -0
- package/src/libraries/JBFees.sol +28 -0
- package/src/libraries/JBFixedPointNumber.sol +12 -0
- package/src/libraries/JBMetadataResolver.sol +306 -0
- package/src/libraries/JBRulesetMetadataResolver.sol +160 -0
- package/src/libraries/JBSplitGroupIds.sol +7 -0
- package/src/libraries/JBSurplus.sol +40 -0
- package/src/periphery/JBDeadline1Day.sol +8 -0
- package/src/periphery/JBDeadline3Days.sol +8 -0
- package/src/periphery/JBDeadline3Hours.sol +8 -0
- package/src/periphery/JBDeadline7Days.sol +8 -0
- package/src/periphery/JBMatchingPriceFeed.sol +13 -0
- package/src/structs/JBAccountingContext.sol +12 -0
- package/src/structs/JBAfterCashOutRecordedContext.sol +30 -0
- package/src/structs/JBAfterPayRecordedContext.sol +29 -0
- package/src/structs/JBBeforeCashOutRecordedContext.sol +31 -0
- package/src/structs/JBBeforePayRecordedContext.sol +28 -0
- package/src/structs/JBCashOutHookSpecification.sol +15 -0
- package/src/structs/JBCurrencyAmount.sol +10 -0
- package/src/structs/JBFee.sol +12 -0
- package/src/structs/JBFundAccessLimitGroup.sol +28 -0
- package/src/structs/JBPayHookSpecification.sol +15 -0
- package/src/structs/JBPermissionsData.sol +13 -0
- package/src/structs/JBRuleset.sol +42 -0
- package/src/structs/JBRulesetConfig.sol +43 -0
- package/src/structs/JBRulesetMetadata.sol +56 -0
- package/src/structs/JBRulesetWeightCache.sol +9 -0
- package/src/structs/JBRulesetWithMetadata.sol +12 -0
- package/src/structs/JBSingleAllowance.sol +16 -0
- package/src/structs/JBSplit.sol +37 -0
- package/src/structs/JBSplitGroup.sol +12 -0
- package/src/structs/JBSplitHookContext.sol +20 -0
- package/src/structs/JBTerminalConfig.sol +12 -0
- package/src/structs/JBTokenAmount.sol +14 -0
- package/test/AuditExploits.t.sol +2710 -0
- package/test/ComprehensiveInvariant.t.sol +298 -0
- package/test/EconomicSimulation.t.sol +340 -0
- package/test/EntryPointPermutations.t.sol +671 -0
- package/test/FlashLoanAttacks.t.sol +792 -0
- package/test/PermissionEscalation.t.sol +679 -0
- package/test/RulesetTransitions.t.sol +699 -0
- package/test/SplitLoopTests.t.sol +731 -0
- package/test/TestAccessToFunds.sol +2644 -0
- package/test/TestCashOut.sol +185 -0
- package/test/TestCashOutCountFor.sol +272 -0
- package/test/TestCashOutHooks.sol +317 -0
- package/test/TestCashOutTimingEdge.sol +229 -0
- package/test/TestDurationUnderflow.sol +220 -0
- package/test/TestFeeProcessingFailure.sol +208 -0
- package/test/TestFees.sol +604 -0
- package/test/TestInterfaceSupport.sol +62 -0
- package/test/TestJBERC20Inheritance.sol +91 -0
- package/test/TestLaunchProject.sol +176 -0
- package/test/TestMetaTx.sol +203 -0
- package/test/TestMetadataParserLib.sol +438 -0
- package/test/TestMigrationHeldFees.sol +249 -0
- package/test/TestMintTokensOf.sol +172 -0
- package/test/TestMultiTokenSurplus.sol +206 -0
- package/test/TestMultipleAccessLimits.sol +642 -0
- package/test/TestPayBurnRedeemFlow.sol +180 -0
- package/test/TestPayHooks.sol +190 -0
- package/test/TestPermissions.sol +305 -0
- package/test/TestPermissionsEdge.sol +286 -0
- package/test/TestPermit2Terminal.sol +339 -0
- package/test/TestRulesetQueueing.sol +1001 -0
- package/test/TestRulesetQueuingStress.sol +778 -0
- package/test/TestRulesetWeightCaching.sol +177 -0
- package/test/TestSplits.sol +369 -0
- package/test/TestTerminalMigration.sol +167 -0
- package/test/TestTokenFlow.sol +174 -0
- package/test/WeirdTokenTests.t.sol +764 -0
- package/test/formal/BondingCurveProperties.t.sol +411 -0
- package/test/formal/FeeProperties.t.sol +246 -0
- package/test/helpers/JBTest.sol +129 -0
- package/test/helpers/MetadataResolverHelper.sol +116 -0
- package/test/helpers/TestBaseWorkflow.sol +317 -0
- package/test/invariants/Phase3DeepInvariant.t.sol +404 -0
- package/test/invariants/RulesetsInvariant.t.sol +115 -0
- package/test/invariants/TerminalStoreInvariant.t.sol +220 -0
- package/test/invariants/TokensInvariant.t.sol +184 -0
- package/test/invariants/handlers/ComprehensiveHandler.sol +285 -0
- package/test/invariants/handlers/EconomicHandler.sol +347 -0
- package/test/invariants/handlers/Phase3Handler.sol +414 -0
- package/test/invariants/handlers/RulesetsHandler.sol +111 -0
- package/test/invariants/handlers/TerminalStoreHandler.sol +146 -0
- package/test/invariants/handlers/TokensHandler.sol +127 -0
- package/test/mock/ERC2771ForwarderMock.sol +37 -0
- package/test/mock/MockERC20.sol +18 -0
- package/test/mock/MockMaliciousBeneficiary.sol +67 -0
- package/test/mock/MockMaliciousSplitHook.sol +42 -0
- package/test/mock/MockPriceFeed.sol +20 -0
- package/test/trees/JBController/burnTokensOf.tree +9 -0
- package/test/trees/JBController/claimTokensFor.tree +5 -0
- package/test/trees/JBController/deployERC20For.tree +5 -0
- package/test/trees/JBController/getRulesetOf.tree +5 -0
- package/test/trees/JBController/launchProjectFor.tree +12 -0
- package/test/trees/JBController/launchRulesetsFor.tree +8 -0
- package/test/trees/JBController/migrateController.tree +12 -0
- package/test/trees/JBController/mintTokensOf.tree +12 -0
- package/test/trees/JBController/payReservedTokenToTerminal.tree +8 -0
- package/test/trees/JBController/receiveMigrationFrom.tree +4 -0
- package/test/trees/JBController/sendReservedTokensToSplitsOf.tree +12 -0
- package/test/trees/JBController/setMetadataOf.tree +5 -0
- package/test/trees/JBController/setSplitGroupsOf.tree +5 -0
- package/test/trees/JBController/setTokenFor.tree +5 -0
- package/test/trees/JBController/transferCreditsFrom.tree +8 -0
- package/test/trees/JBDirectory/primaryTerminalOf.tree +8 -0
- package/test/trees/JBDirectory/setControllerOf.tree +11 -0
- package/test/trees/JBDirectory/setPrimaryTerminalOf.tree +15 -0
- package/test/trees/JBDirectory/setTerminalsOf.tree +11 -0
- package/test/trees/JBERC20/initialize.tree +7 -0
- package/test/trees/JBERC20/name.tree +5 -0
- package/test/trees/JBERC20/nonces.tree +5 -0
- package/test/trees/JBERC20/symbol.tree +5 -0
- package/test/trees/JBFeelessAddresses/setFeelessAddress.tree +5 -0
- package/test/trees/JBFeelessAddresses/supportsInterface.tree +5 -0
- package/test/trees/JBFundAccessLimits/payoutLimitOf.tree +5 -0
- package/test/trees/JBFundAccessLimits/payoutLimitsOf.tree +8 -0
- package/test/trees/JBFundAccessLimits/setFundAccessLimitsFor.tree +18 -0
- package/test/trees/JBFundAccessLimits/surplusAllowanceOf.tree +5 -0
- package/test/trees/JBFundAccessLimits/surplusAllowancesOf.tree +8 -0
- package/test/trees/JBMetadataResolver/getDataFor.tree +8 -0
- package/test/trees/JBMultiTerminal/accountingContextsOf.tree +5 -0
- package/test/trees/JBMultiTerminal/addAccountingContextsFor.tree +10 -0
- package/test/trees/JBMultiTerminal/addToBalanceOf.tree +23 -0
- package/test/trees/JBMultiTerminal/cashOutTokensOf.tree +23 -0
- package/test/trees/JBMultiTerminal/executePayout.tree +32 -0
- package/test/trees/JBMultiTerminal/executeProcessFee.tree +14 -0
- package/test/trees/JBMultiTerminal/migrateBalanceOf.tree +12 -0
- package/test/trees/JBMultiTerminal/pay.tree +23 -0
- package/test/trees/JBMultiTerminal/processHeldFeesOf.tree +8 -0
- package/test/trees/JBMultiTerminal/sendPayoutsOf.tree +34 -0
- package/test/trees/JBMultiTerminal/useAllowanceOf.tree +16 -0
- package/test/trees/JBPermissions/hasPermission.tree +8 -0
- package/test/trees/JBPermissions/hasPermissions.tree +8 -0
- package/test/trees/JBPermissions/setPermissionsFor.tree +5 -0
- package/test/trees/JBPrices/addPriceFeedFor.tree +14 -0
- package/test/trees/JBPrices/pricePerUnitOf.tree +11 -0
- package/test/trees/JBProjects/createFor.tree +11 -0
- package/test/trees/JBProjects/setTokenUriResolver.tree +5 -0
- package/test/trees/JBProjects/supportsInterface.tree +9 -0
- package/test/trees/JBProjects/tokenURI.tree +5 -0
- package/test/trees/JBRulesets/currentApprovalStatusForLatestRulesetOf.tree +8 -0
- package/test/trees/JBRulesets/currentOf.tree +12 -0
- package/test/trees/JBRulesets/getRulesetOf.tree +5 -0
- package/test/trees/JBRulesets/latestQueuedRulesetOf.tree +10 -0
- package/test/trees/JBRulesets/rulesetsOf.tree +11 -0
- package/test/trees/JBRulesets/upcomingRulesetOf.tree +20 -0
- package/test/trees/JBRulesets/updateRulesetWeightCache.tree +5 -0
- package/test/trees/JBSplits/setSplitGroupsOf.tree +17 -0
- package/test/trees/JBSplits/splitsOf.tree +5 -0
- package/test/trees/JBTerminalStore/currentReclaimableSurplusOf.tree +16 -0
- package/test/trees/JBTerminalStore/currentSurplusOf.tree +25 -0
- package/test/trees/JBTerminalStore/currentTotalSurplusOf.tree +5 -0
- package/test/trees/JBTerminalStore/recordCashOutsFor.tree +16 -0
- package/test/trees/JBTerminalStore/recordPaymentFrom.tree +14 -0
- package/test/trees/JBTerminalStore/recordPayoutFor.tree +10 -0
- package/test/trees/JBTerminalStore/recordTerminalMigration.tree +5 -0
- package/test/trees/JBTerminalStore/recordUsedAllowanceOf.tree +10 -0
- package/test/trees/JBTokens/burnFrom.tree +10 -0
- package/test/trees/JBTokens/claimTokensFor.tree +10 -0
- package/test/trees/JBTokens/deployERC20For.tree +12 -0
- package/test/trees/JBTokens/mintFor.tree +10 -0
- package/test/trees/JBTokens/setTokenFor.tree +11 -0
- package/test/trees/JBTokens/totalBalanceOf.tree +5 -0
- package/test/trees/JBTokens/totalSupplyOf.tree +5 -0
- package/test/trees/JBTokens/transferCreditsFrom.tree +8 -0
- package/test/trees/mintTokensOf.tree +12 -0
- package/test/units/static/JBChainlinkV3PriceFeed/TestPriceFeed.sol +220 -0
- package/test/units/static/JBController/JBControllerSetup.sol +40 -0
- package/test/units/static/JBController/TestBurnTokensOf.sol +107 -0
- package/test/units/static/JBController/TestClaimTokensFor.sol +60 -0
- package/test/units/static/JBController/TestDeployErc20For.sol +80 -0
- package/test/units/static/JBController/TestLaunchProjectFor.sol +282 -0
- package/test/units/static/JBController/TestLaunchRulesetsFor.sol +322 -0
- package/test/units/static/JBController/TestMigrateController.sol +148 -0
- package/test/units/static/JBController/TestMintTokensOfUnits.sol +102 -0
- package/test/units/static/JBController/TestPayReservedTokenToTerminal.sol +71 -0
- package/test/units/static/JBController/TestReceiveMigrationFrom.sol +95 -0
- package/test/units/static/JBController/TestRulesetViews.sol +219 -0
- package/test/units/static/JBController/TestSendReservedTokensToSplitsOf.sol +595 -0
- package/test/units/static/JBController/TestSetSplitGroupsOf.sol +63 -0
- package/test/units/static/JBController/TestSetTokenFor.sol +227 -0
- package/test/units/static/JBController/TestSetUriOf.sol +53 -0
- package/test/units/static/JBController/TestTransferCreditsFrom.sol +159 -0
- package/test/units/static/JBDeadline/TestDeadlineFuzz.sol +194 -0
- package/test/units/static/JBDirectory/JBDirectorySetup.sol +22 -0
- package/test/units/static/JBDirectory/TestPrimaryTerminalOf.sol +122 -0
- package/test/units/static/JBDirectory/TestSetControllerOf.sol +173 -0
- package/test/units/static/JBDirectory/TestSetControllerOfMigrationOrder.sol +98 -0
- package/test/units/static/JBDirectory/TestSetPrimaryTerminalOf.sol +169 -0
- package/test/units/static/JBDirectory/TestSetTerminalsOf.sol +128 -0
- package/test/units/static/JBERC20/JBERC20Setup.sol +20 -0
- package/test/units/static/JBERC20/SigUtils.sol +34 -0
- package/test/units/static/JBERC20/TestInitialize.sol +54 -0
- package/test/units/static/JBERC20/TestName.sol +30 -0
- package/test/units/static/JBERC20/TestNonces.sol +59 -0
- package/test/units/static/JBERC20/TestSymbol.sol +31 -0
- package/test/units/static/JBFeelessAdresses/JBFeelessSetup.sol +20 -0
- package/test/units/static/JBFeelessAdresses/TestInterfaces.sol +29 -0
- package/test/units/static/JBFeelessAdresses/TestSetFeelessAddress.sol +35 -0
- package/test/units/static/JBFees/TestFeesFuzz.sol +78 -0
- package/test/units/static/JBFixedPointNumber/TestAdjustDecimals.sol +16 -0
- package/test/units/static/JBFixedPointNumber/TestAdjustDecimalsFuzz.sol +71 -0
- package/test/units/static/JBFundAccessLimits/JBFundAccessSetup.sol +21 -0
- package/test/units/static/JBFundAccessLimits/TestFundAccessLimitsEdge.sol +159 -0
- package/test/units/static/JBFundAccessLimits/TestPayoutLimitOf.sol +56 -0
- package/test/units/static/JBFundAccessLimits/TestPayoutLimitsOf.sol +94 -0
- package/test/units/static/JBFundAccessLimits/TestSetFundAccessLimitsFor.sol +182 -0
- package/test/units/static/JBFundAccessLimits/TestSurplusAllowanceOf.sol +61 -0
- package/test/units/static/JBFundAccessLimits/TestSurplusAllowancesOf.sol +96 -0
- package/test/units/static/JBMetadataResolver/TestGetDataFor.sol +89 -0
- package/test/units/static/JBMetadataResolver/TestMetadataResolverFuzz.sol +227 -0
- package/test/units/static/JBMetadataResolver/TestMetadataResolverM20M21.sol +245 -0
- package/test/units/static/JBMultiTerminal/JBMultiTerminalSetup.sol +39 -0
- package/test/units/static/JBMultiTerminal/TestAccountingContextsOf.sol +65 -0
- package/test/units/static/JBMultiTerminal/TestAddAccountingContextsFor.sol +313 -0
- package/test/units/static/JBMultiTerminal/TestAddToBalanceOf.sol +432 -0
- package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +478 -0
- package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +577 -0
- package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +176 -0
- package/test/units/static/JBMultiTerminal/TestMigrateBalanceOf.sol +190 -0
- package/test/units/static/JBMultiTerminal/TestPay.sol +514 -0
- package/test/units/static/JBMultiTerminal/TestProcessHeldFeesOf.sol +29 -0
- package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +243 -0
- package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +310 -0
- package/test/units/static/JBPermissions/JBPermissionsSetup.sol +18 -0
- package/test/units/static/JBPermissions/TestHasPermission.sol +50 -0
- package/test/units/static/JBPermissions/TestHasPermissions.sol +93 -0
- package/test/units/static/JBPermissions/TestSetPermissionsFor.sol +62 -0
- package/test/units/static/JBPrices/JBPricesSetup.sol +26 -0
- package/test/units/static/JBPrices/TestAddPriceFeedFor.sol +102 -0
- package/test/units/static/JBPrices/TestPricePerUnitOf.sol +129 -0
- package/test/units/static/JBPrices/TestPrices.sol +262 -0
- package/test/units/static/JBProjects/JBProjectsSetup.sol +20 -0
- package/test/units/static/JBProjects/TestCreateFor.sol +69 -0
- package/test/units/static/JBProjects/TestInitialProject.sol +19 -0
- package/test/units/static/JBProjects/TestInterfaces.sol +27 -0
- package/test/units/static/JBProjects/TestSetResolver.sol +36 -0
- package/test/units/static/JBProjects/TestTokenUri.sol +38 -0
- package/test/units/static/JBRulesetMetadataResolver/TestSetCashOutTaxRateTo.sol +99 -0
- package/test/units/static/JBRulesets/JBRulesetsSetup.sol +21 -0
- package/test/units/static/JBRulesets/TestCurrentApprovalStatusForLatestRulesetOf.sol +257 -0
- package/test/units/static/JBRulesets/TestCurrentOf.sol +231 -0
- package/test/units/static/JBRulesets/TestGetRulesetOf.sol +94 -0
- package/test/units/static/JBRulesets/TestLatestQueuedRulesetOf.sol +252 -0
- package/test/units/static/JBRulesets/TestRulesets.sol +617 -0
- package/test/units/static/JBRulesets/TestRulesetsOf.sol +37 -0
- package/test/units/static/JBRulesets/TestUpcomingRulesetOf.sol +526 -0
- package/test/units/static/JBRulesets/TestUpdateRulesetWeightCache.sol +91 -0
- package/test/units/static/JBSplits/JBSplitsSetup.sol +23 -0
- package/test/units/static/JBSplits/TestSelfManagedSplitGroups.sol +502 -0
- package/test/units/static/JBSplits/TestSetSplitGroupsOf.sol +370 -0
- package/test/units/static/JBSplits/TestSplitsLockedEdge.sol +262 -0
- package/test/units/static/JBSplits/TestSplitsOf.sol +24 -0
- package/test/units/static/JBSplits/TestSplitsPacking.sol +33 -0
- package/test/units/static/JBSurplus/TestSurplusFuzz.sol +125 -0
- package/test/units/static/JBTerminalStore/JBTerminalStoreSetup.sol +23 -0
- package/test/units/static/JBTerminalStore/TestCurrentReclaimableSurplusOf.sol +434 -0
- package/test/units/static/JBTerminalStore/TestCurrentSurplusOf.sol +428 -0
- package/test/units/static/JBTerminalStore/TestCurrentTotalSurplusOf.sol +65 -0
- package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +479 -0
- package/test/units/static/JBTerminalStore/TestRecordPaymentFrom.sol +508 -0
- package/test/units/static/JBTerminalStore/TestRecordPayoutFor.sol +257 -0
- package/test/units/static/JBTerminalStore/TestRecordTerminalMigration.sol +131 -0
- package/test/units/static/JBTerminalStore/TestRecordUsedAllowanceOf.sol +390 -0
- package/test/units/static/JBTerminalStore/TestUint224Overflow.sol +187 -0
- package/test/units/static/JBTokens/JBTokensSetup.sol +23 -0
- package/test/units/static/JBTokens/TestBurnFrom.sol +104 -0
- package/test/units/static/JBTokens/TestClaimTokensFor.sol +107 -0
- package/test/units/static/JBTokens/TestDeployERC20ForUnits.sol +89 -0
- package/test/units/static/JBTokens/TestMintFor.sol +97 -0
- package/test/units/static/JBTokens/TestSetTokenFor.sol +95 -0
- package/test/units/static/JBTokens/TestTotalBalanceOf.sol +65 -0
- package/test/units/static/JBTokens/TestTotalSupplyOf.sol +56 -0
- package/test/units/static/JBTokens/TestTransferCreditsFrom.sol +54 -0
|
@@ -0,0 +1,2644 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity >=0.8.6;
|
|
3
|
+
|
|
4
|
+
import /* {*} from */ "./helpers/TestBaseWorkflow.sol";
|
|
5
|
+
import "@openzeppelin/contracts/utils/Address.sol";
|
|
6
|
+
import {MockPriceFeed} from "./mock/MockPriceFeed.sol";
|
|
7
|
+
import {MaliciousAllowanceBeneficiary, MaliciousPayoutBeneficiary} from "./mock/MockMaliciousBeneficiary.sol";
|
|
8
|
+
|
|
9
|
+
/// Funds can be accessed in three ways:
|
|
10
|
+
/// 1. project owners set a payout limit to prioritize spending to pre-determined destinations. funds being removed from
|
|
11
|
+
/// the protocol incurs fees unless the recipients are feeless addresses.
|
|
12
|
+
/// 2. project owners set a surplus allowance to allow spending funds from the project's surplus balance in the terminal
|
|
13
|
+
/// (i.e. the balance in excess of their payout limit). incurs fees unless the caller is a feeless address.
|
|
14
|
+
/// 3. token holders can cash out tokens to access surplus funds. incurs fees if the cash out tax rate != 100%, unless
|
|
15
|
+
/// the
|
|
16
|
+
/// beneficiary is a feeless address.
|
|
17
|
+
/// Each of these only incurs protocol fees if the `_FEE_PROJECT_ID` (project with ID #1) accepts the token being
|
|
18
|
+
/// accessed.
|
|
19
|
+
contract TestAccessToFunds_Local is TestBaseWorkflow {
|
|
20
|
+
uint256 private constant _FEE_PROJECT_ID = 1;
|
|
21
|
+
uint8 private constant _WEIGHT_DECIMALS = 18; // FIXED
|
|
22
|
+
uint8 private constant _NATIVE_DECIMALS = 18; // FIXED
|
|
23
|
+
uint8 private constant _PRICE_FEED_DECIMALS = 10;
|
|
24
|
+
uint256 private constant _USD_PRICE_PER_NATIVE = 2000 * 10 ** _PRICE_FEED_DECIMALS; // 2000 USDC == 1 native token
|
|
25
|
+
|
|
26
|
+
IJBController private _controller;
|
|
27
|
+
IJBPrices private _prices;
|
|
28
|
+
IJBMultiTerminal private _terminal;
|
|
29
|
+
IJBMultiTerminal private _terminal2;
|
|
30
|
+
IJBTokens private _tokens;
|
|
31
|
+
address private _projectOwner;
|
|
32
|
+
address private _beneficiary;
|
|
33
|
+
MockERC20 private _usdcToken;
|
|
34
|
+
uint256 private _projectId;
|
|
35
|
+
|
|
36
|
+
uint112 private _weight;
|
|
37
|
+
JBRulesetMetadata private _metadata;
|
|
38
|
+
|
|
39
|
+
function setUp() public override {
|
|
40
|
+
super.setUp();
|
|
41
|
+
|
|
42
|
+
_projectOwner = multisig();
|
|
43
|
+
_beneficiary = beneficiary();
|
|
44
|
+
_usdcToken = usdcToken();
|
|
45
|
+
_tokens = jbTokens();
|
|
46
|
+
_controller = jbController();
|
|
47
|
+
_prices = jbPrices();
|
|
48
|
+
_terminal = jbMultiTerminal();
|
|
49
|
+
_terminal2 = jbMultiTerminal2();
|
|
50
|
+
_weight = uint112(1000 * 10 ** _WEIGHT_DECIMALS);
|
|
51
|
+
|
|
52
|
+
_metadata = JBRulesetMetadata({
|
|
53
|
+
reservedPercent: JBConstants.MAX_RESERVED_PERCENT / 2, //50%
|
|
54
|
+
cashOutTaxRate: JBConstants.MAX_CASH_OUT_TAX_RATE / 2, //50%
|
|
55
|
+
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
56
|
+
pausePay: false,
|
|
57
|
+
pauseCreditTransfers: false,
|
|
58
|
+
allowOwnerMinting: false,
|
|
59
|
+
allowSetCustomToken: false,
|
|
60
|
+
allowTerminalMigration: false,
|
|
61
|
+
allowSetTerminals: false,
|
|
62
|
+
ownerMustSendPayouts: false,
|
|
63
|
+
allowSetController: false,
|
|
64
|
+
allowAddAccountingContext: true,
|
|
65
|
+
allowAddPriceFeed: true,
|
|
66
|
+
holdFees: false,
|
|
67
|
+
useTotalSurplusForCashOuts: true,
|
|
68
|
+
useDataHookForPay: false,
|
|
69
|
+
useDataHookForCashOut: false,
|
|
70
|
+
dataHook: address(0),
|
|
71
|
+
metadata: 0
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Tests that basic payout limit and surplus allowance limits work as intended.
|
|
76
|
+
|
|
77
|
+
function testNativeAllowance() public {
|
|
78
|
+
// Hardcode values to use.
|
|
79
|
+
uint224 _nativeCurrencyPayoutLimit = uint224(10 * 10 ** _NATIVE_DECIMALS);
|
|
80
|
+
uint224 _nativeCurrencySurplusAllowance = uint224(5 * 10 ** _NATIVE_DECIMALS);
|
|
81
|
+
|
|
82
|
+
// Package up the limits for the given terminal.
|
|
83
|
+
JBFundAccessLimitGroup[] memory _fundAccessLimitGroup = new JBFundAccessLimitGroup[](1);
|
|
84
|
+
{
|
|
85
|
+
// Specify a payout limit.
|
|
86
|
+
JBCurrencyAmount[] memory _payoutLimits = new JBCurrencyAmount[](1);
|
|
87
|
+
_payoutLimits[0] = JBCurrencyAmount({
|
|
88
|
+
amount: _nativeCurrencyPayoutLimit, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Specify a surplus allowance.
|
|
92
|
+
JBCurrencyAmount[] memory _surplusAllowances = new JBCurrencyAmount[](1);
|
|
93
|
+
_surplusAllowances[0] = JBCurrencyAmount({
|
|
94
|
+
amount: _nativeCurrencySurplusAllowance, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
_fundAccessLimitGroup[0] = JBFundAccessLimitGroup({
|
|
98
|
+
terminal: address(_terminal),
|
|
99
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
100
|
+
payoutLimits: _payoutLimits,
|
|
101
|
+
surplusAllowances: _surplusAllowances
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
{
|
|
106
|
+
// Package up the ruleset configuration.
|
|
107
|
+
JBRulesetConfig[] memory _rulesetConfigurations = new JBRulesetConfig[](1);
|
|
108
|
+
_rulesetConfigurations[0].mustStartAtOrAfter = 0;
|
|
109
|
+
_rulesetConfigurations[0].duration = 0;
|
|
110
|
+
_rulesetConfigurations[0].weight = _weight;
|
|
111
|
+
_rulesetConfigurations[0].weightCutPercent = 0;
|
|
112
|
+
_rulesetConfigurations[0].approvalHook = IJBRulesetApprovalHook(address(0));
|
|
113
|
+
_rulesetConfigurations[0].metadata = _metadata;
|
|
114
|
+
_rulesetConfigurations[0].splitGroups = new JBSplitGroup[](0);
|
|
115
|
+
_rulesetConfigurations[0].fundAccessLimitGroups = _fundAccessLimitGroup;
|
|
116
|
+
|
|
117
|
+
JBTerminalConfig[] memory _terminalConfigurations = new JBTerminalConfig[](1);
|
|
118
|
+
JBAccountingContext[] memory _tokensToAccept = new JBAccountingContext[](1);
|
|
119
|
+
_tokensToAccept[0] = JBAccountingContext({
|
|
120
|
+
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
_terminalConfigurations[0] =
|
|
124
|
+
JBTerminalConfig({terminal: _terminal, accountingContextsToAccept: _tokensToAccept});
|
|
125
|
+
|
|
126
|
+
// Create a first project to collect fees.
|
|
127
|
+
_controller.launchProjectFor({
|
|
128
|
+
owner: address(420), // Random.
|
|
129
|
+
projectUri: "whatever",
|
|
130
|
+
rulesetConfigurations: _rulesetConfigurations,
|
|
131
|
+
terminalConfigurations: _terminalConfigurations, // Set terminals to receive fees.
|
|
132
|
+
memo: ""
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Create the project to test.
|
|
136
|
+
_projectId = _controller.launchProjectFor({
|
|
137
|
+
owner: _projectOwner,
|
|
138
|
+
projectUri: "myIPFSHash",
|
|
139
|
+
rulesetConfigurations: _rulesetConfigurations,
|
|
140
|
+
terminalConfigurations: _terminalConfigurations,
|
|
141
|
+
memo: ""
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Get a reference to the amount being paid.
|
|
146
|
+
// The amount being paid is the payout limit plus two times the surplus allowance.
|
|
147
|
+
uint256 _nativePayAmount = _nativeCurrencyPayoutLimit + (2 * _nativeCurrencySurplusAllowance);
|
|
148
|
+
|
|
149
|
+
// Pay the project such that the `_beneficiary` receives project tokens.
|
|
150
|
+
_terminal.pay{value: _nativePayAmount}({
|
|
151
|
+
projectId: _projectId,
|
|
152
|
+
amount: _nativePayAmount,
|
|
153
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
154
|
+
beneficiary: _beneficiary,
|
|
155
|
+
minReturnedTokens: 0,
|
|
156
|
+
memo: "",
|
|
157
|
+
metadata: new bytes(0)
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Make sure the beneficiary got the expected number of tokens.
|
|
161
|
+
uint256 _beneficiaryTokenBalance = mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
|
|
162
|
+
* _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT;
|
|
163
|
+
assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), _beneficiaryTokenBalance);
|
|
164
|
+
|
|
165
|
+
// Make sure the terminal holds the full native token balance.
|
|
166
|
+
assertEq(
|
|
167
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN), _nativePayAmount
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// Use the full surplus allowance.
|
|
171
|
+
vm.prank(_projectOwner);
|
|
172
|
+
_terminal.useAllowanceOf({
|
|
173
|
+
projectId: _projectId,
|
|
174
|
+
amount: _nativeCurrencySurplusAllowance,
|
|
175
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
176
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
177
|
+
minTokensPaidOut: 0,
|
|
178
|
+
beneficiary: payable(_beneficiary),
|
|
179
|
+
feeBeneficiary: payable(_projectOwner),
|
|
180
|
+
memo: "MEMO"
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Make sure the beneficiary received the funds and that they are no longer in the terminal.
|
|
184
|
+
uint256 _beneficiaryNativeBalance = _nativeCurrencySurplusAllowance
|
|
185
|
+
- mulDiv(_nativeCurrencySurplusAllowance, _terminal.FEE(), JBConstants.MAX_FEE);
|
|
186
|
+
assertEq(_beneficiary.balance, _beneficiaryNativeBalance);
|
|
187
|
+
assertEq(
|
|
188
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
|
|
189
|
+
_nativePayAmount - _nativeCurrencySurplusAllowance
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
// Make sure the fee was paid correctly.
|
|
193
|
+
assertEq(
|
|
194
|
+
jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
|
|
195
|
+
_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance
|
|
196
|
+
);
|
|
197
|
+
assertEq(address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance);
|
|
198
|
+
|
|
199
|
+
// Make sure the project owner got the expected number of tokens.
|
|
200
|
+
assertEq(
|
|
201
|
+
_tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID),
|
|
202
|
+
mulDiv(_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance, _weight, 10 ** _NATIVE_DECIMALS)
|
|
203
|
+
* _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Pay out native tokens up to the payout limit. Since `splits[]` is empty, everything goes to project owner.
|
|
207
|
+
_terminal.sendPayoutsOf({
|
|
208
|
+
projectId: _projectId,
|
|
209
|
+
amount: _nativeCurrencyPayoutLimit,
|
|
210
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
211
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
212
|
+
minTokensPaidOut: 0
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Make sure the project owner received the funds which were paid out.
|
|
216
|
+
uint256 _projectOwnerNativeBalance =
|
|
217
|
+
_nativeCurrencyPayoutLimit - _nativeCurrencyPayoutLimit * _terminal.FEE() / JBConstants.MAX_FEE;
|
|
218
|
+
|
|
219
|
+
// Make sure the project owner received the full amount.
|
|
220
|
+
assertEq(_projectOwner.balance, _projectOwnerNativeBalance);
|
|
221
|
+
|
|
222
|
+
// Make sure the fee was paid correctly.
|
|
223
|
+
assertEq(
|
|
224
|
+
jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
|
|
225
|
+
(_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance)
|
|
226
|
+
+ (_nativeCurrencyPayoutLimit - _projectOwnerNativeBalance)
|
|
227
|
+
);
|
|
228
|
+
assertEq(address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance);
|
|
229
|
+
|
|
230
|
+
// Make sure the project owner got the expected number of tokens.
|
|
231
|
+
assertEq(
|
|
232
|
+
_tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID),
|
|
233
|
+
mulDiv(
|
|
234
|
+
(_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance)
|
|
235
|
+
+ (_nativeCurrencyPayoutLimit - _projectOwnerNativeBalance),
|
|
236
|
+
_weight,
|
|
237
|
+
10 ** _NATIVE_DECIMALS
|
|
238
|
+
) * _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
// Cash out native tokens from the surplus using all of the `_beneficiary`'s tokens.
|
|
242
|
+
vm.prank(_beneficiary);
|
|
243
|
+
_terminal.cashOutTokensOf({
|
|
244
|
+
holder: _beneficiary,
|
|
245
|
+
projectId: _projectId,
|
|
246
|
+
cashOutCount: _beneficiaryTokenBalance,
|
|
247
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
248
|
+
minTokensReclaimed: 0,
|
|
249
|
+
beneficiary: payable(_beneficiary),
|
|
250
|
+
metadata: new bytes(0)
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Make sure the beneficiary doesn't have any project tokens left.
|
|
254
|
+
assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), 0);
|
|
255
|
+
|
|
256
|
+
// Get the expected amount of native tokens reclaimed by the cash out.
|
|
257
|
+
uint256 _nativeReclaimAmount = mulDiv(
|
|
258
|
+
mulDiv(
|
|
259
|
+
_nativePayAmount - _nativeCurrencySurplusAllowance - _nativeCurrencyPayoutLimit,
|
|
260
|
+
_beneficiaryTokenBalance,
|
|
261
|
+
mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
|
|
262
|
+
),
|
|
263
|
+
_metadata.cashOutTaxRate
|
|
264
|
+
+ mulDiv(
|
|
265
|
+
_beneficiaryTokenBalance,
|
|
266
|
+
JBConstants.MAX_CASH_OUT_TAX_RATE - _metadata.cashOutTaxRate,
|
|
267
|
+
mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
|
|
268
|
+
),
|
|
269
|
+
JBConstants.MAX_CASH_OUT_TAX_RATE
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
// Calculate the fee from the cash out.
|
|
273
|
+
uint256 _feeAmount = _nativeReclaimAmount * _terminal.FEE() / JBConstants.MAX_FEE;
|
|
274
|
+
assertEq(_beneficiary.balance, _beneficiaryNativeBalance + _nativeReclaimAmount - _feeAmount);
|
|
275
|
+
|
|
276
|
+
// Make sure the fee was paid correctly.
|
|
277
|
+
assertEq(
|
|
278
|
+
jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
|
|
279
|
+
(_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance)
|
|
280
|
+
+ (_nativeCurrencyPayoutLimit - _projectOwnerNativeBalance) + _feeAmount
|
|
281
|
+
);
|
|
282
|
+
assertEq(
|
|
283
|
+
address(_terminal).balance,
|
|
284
|
+
_nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
|
|
285
|
+
- (_nativeReclaimAmount - _feeAmount)
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// Make sure the project owner got the expected number of the fee project's tokens by paying the fee.
|
|
289
|
+
assertEq(
|
|
290
|
+
_tokens.totalBalanceOf(_beneficiary, _FEE_PROJECT_ID),
|
|
291
|
+
mulDiv(_feeAmount, _weight, 10 ** _NATIVE_DECIMALS) * _metadata.reservedPercent
|
|
292
|
+
/ JBConstants.MAX_RESERVED_PERCENT
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function testFuzzNativeAllowance(
|
|
297
|
+
uint224 _nativeCurrencySurplusAllowance,
|
|
298
|
+
uint224 _nativeCurrencyPayoutLimit,
|
|
299
|
+
uint256 _nativePayAmount
|
|
300
|
+
)
|
|
301
|
+
public
|
|
302
|
+
{
|
|
303
|
+
// Make sure the amount of native tokens to pay is bounded.
|
|
304
|
+
_nativePayAmount = bound(_nativePayAmount, 0, 1_000_000 * 10 ** _NATIVE_DECIMALS);
|
|
305
|
+
|
|
306
|
+
// Make sure the values don't overflow the registry.
|
|
307
|
+
unchecked {
|
|
308
|
+
vm.assume(
|
|
309
|
+
_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit >= _nativeCurrencySurplusAllowance
|
|
310
|
+
&& _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit >= _nativeCurrencyPayoutLimit
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Package up the limits for the given terminal.
|
|
315
|
+
JBFundAccessLimitGroup[] memory _fundAccessLimitGroup = new JBFundAccessLimitGroup[](1);
|
|
316
|
+
{
|
|
317
|
+
// Specify a payout limit.
|
|
318
|
+
JBCurrencyAmount[] memory _payoutLimits = new JBCurrencyAmount[](1);
|
|
319
|
+
_payoutLimits[0] = JBCurrencyAmount({
|
|
320
|
+
amount: _nativeCurrencyPayoutLimit, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Specify a surplus allowance.
|
|
324
|
+
JBCurrencyAmount[] memory _surplusAllowances = new JBCurrencyAmount[](1);
|
|
325
|
+
_surplusAllowances[0] = JBCurrencyAmount({
|
|
326
|
+
amount: _nativeCurrencySurplusAllowance, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
_fundAccessLimitGroup[0] = JBFundAccessLimitGroup({
|
|
330
|
+
terminal: address(_terminal),
|
|
331
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
332
|
+
payoutLimits: _payoutLimits,
|
|
333
|
+
surplusAllowances: _surplusAllowances
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
{
|
|
338
|
+
// Package up the ruleset configuration.
|
|
339
|
+
JBRulesetConfig[] memory _rulesetConfigurations = new JBRulesetConfig[](1);
|
|
340
|
+
_rulesetConfigurations[0].mustStartAtOrAfter = 0;
|
|
341
|
+
_rulesetConfigurations[0].duration = 0;
|
|
342
|
+
_rulesetConfigurations[0].weight = _weight;
|
|
343
|
+
_rulesetConfigurations[0].weightCutPercent = 0;
|
|
344
|
+
_rulesetConfigurations[0].approvalHook = IJBRulesetApprovalHook(address(0));
|
|
345
|
+
_rulesetConfigurations[0].metadata = _metadata;
|
|
346
|
+
_rulesetConfigurations[0].splitGroups = new JBSplitGroup[](0);
|
|
347
|
+
_rulesetConfigurations[0].fundAccessLimitGroups = _fundAccessLimitGroup;
|
|
348
|
+
|
|
349
|
+
JBTerminalConfig[] memory _terminalConfigurations = new JBTerminalConfig[](1);
|
|
350
|
+
JBAccountingContext[] memory _tokensToAccept = new JBAccountingContext[](1);
|
|
351
|
+
_tokensToAccept[0] = JBAccountingContext({
|
|
352
|
+
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
353
|
+
});
|
|
354
|
+
_terminalConfigurations[0] =
|
|
355
|
+
JBTerminalConfig({terminal: _terminal, accountingContextsToAccept: _tokensToAccept});
|
|
356
|
+
|
|
357
|
+
// Create a project to collect fees.
|
|
358
|
+
_controller.launchProjectFor({
|
|
359
|
+
owner: address(420), // Random.
|
|
360
|
+
projectUri: "whatever",
|
|
361
|
+
rulesetConfigurations: _rulesetConfigurations, // Use the same ruleset configurations.
|
|
362
|
+
terminalConfigurations: _terminalConfigurations, // set the terminals where fees will be received
|
|
363
|
+
memo: ""
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// Create the project to test.
|
|
367
|
+
_projectId = _controller.launchProjectFor({
|
|
368
|
+
owner: _projectOwner,
|
|
369
|
+
projectUri: "myIPFSHash",
|
|
370
|
+
rulesetConfigurations: _rulesetConfigurations,
|
|
371
|
+
terminalConfigurations: _terminalConfigurations,
|
|
372
|
+
memo: ""
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Make a payment to the test project to give it a starting balance. Send the tokens to the `_beneficiary`.
|
|
377
|
+
_terminal.pay{value: _nativePayAmount}({
|
|
378
|
+
projectId: _projectId,
|
|
379
|
+
amount: _nativePayAmount,
|
|
380
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
381
|
+
beneficiary: _beneficiary,
|
|
382
|
+
minReturnedTokens: 0,
|
|
383
|
+
memo: "",
|
|
384
|
+
metadata: new bytes(0)
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Make sure the beneficiary got the expected number of tokens.
|
|
388
|
+
uint256 _beneficiaryTokenBalance = mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
|
|
389
|
+
* _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT;
|
|
390
|
+
assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), _beneficiaryTokenBalance);
|
|
391
|
+
|
|
392
|
+
// Make sure the terminal holds the full native token balance.
|
|
393
|
+
assertEq(
|
|
394
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN), _nativePayAmount
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
// Revert if there's no surplus allowance.
|
|
398
|
+
if (_nativeCurrencySurplusAllowance == 0) {
|
|
399
|
+
vm.expectRevert(
|
|
400
|
+
abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_InadequateControllerAllowance.selector, 0, 0)
|
|
401
|
+
);
|
|
402
|
+
// Revert if there's no surplus, or if too much is being withdrawn.
|
|
403
|
+
} else if (_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit > _nativePayAmount) {
|
|
404
|
+
vm.expectRevert(
|
|
405
|
+
abi.encodeWithSelector(
|
|
406
|
+
JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
|
|
407
|
+
_nativeCurrencySurplusAllowance,
|
|
408
|
+
_nativeCurrencyPayoutLimit > _nativePayAmount ? 0 : _nativePayAmount - _nativeCurrencyPayoutLimit
|
|
409
|
+
)
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Use the full surplus allowance.
|
|
414
|
+
vm.prank(_projectOwner);
|
|
415
|
+
_terminal.useAllowanceOf({
|
|
416
|
+
projectId: _projectId,
|
|
417
|
+
amount: _nativeCurrencySurplusAllowance,
|
|
418
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
419
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
420
|
+
minTokensPaidOut: 0,
|
|
421
|
+
beneficiary: payable(_beneficiary),
|
|
422
|
+
feeBeneficiary: payable(_projectOwner),
|
|
423
|
+
memo: "MEMO"
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Keep a reference to the beneficiary's balance.
|
|
427
|
+
uint256 _beneficiaryNativeBalance;
|
|
428
|
+
|
|
429
|
+
// Check the collected balance if one is expected.
|
|
430
|
+
if (_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit <= _nativePayAmount) {
|
|
431
|
+
// Make sure the beneficiary received the funds and that they are no longer in the terminal.
|
|
432
|
+
_beneficiaryNativeBalance = _nativeCurrencySurplusAllowance
|
|
433
|
+
- mulDiv(_nativeCurrencySurplusAllowance, _terminal.FEE(), JBConstants.MAX_FEE);
|
|
434
|
+
assertEq(_beneficiary.balance, _beneficiaryNativeBalance);
|
|
435
|
+
assertEq(
|
|
436
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
|
|
437
|
+
_nativePayAmount - _nativeCurrencySurplusAllowance
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
// Make sure the fee was paid correctly.
|
|
441
|
+
assertEq(
|
|
442
|
+
jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
|
|
443
|
+
_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance
|
|
444
|
+
);
|
|
445
|
+
assertEq(address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance);
|
|
446
|
+
|
|
447
|
+
// Make sure the beneficiary got the expected number of tokens.
|
|
448
|
+
assertEq(
|
|
449
|
+
_tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID),
|
|
450
|
+
mulDiv(_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance, _weight, 10 ** _NATIVE_DECIMALS)
|
|
451
|
+
* _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT
|
|
452
|
+
);
|
|
453
|
+
} else {
|
|
454
|
+
// Set the native token surplus allowance to 0 if it wasn't used.
|
|
455
|
+
_nativeCurrencySurplusAllowance = 0;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Revert if the payout limit is greater than the balance.
|
|
459
|
+
if (_nativeCurrencyPayoutLimit > _nativePayAmount) {
|
|
460
|
+
vm.expectRevert(
|
|
461
|
+
abi.encodeWithSelector(
|
|
462
|
+
JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
|
|
463
|
+
_nativeCurrencyPayoutLimit,
|
|
464
|
+
_nativePayAmount
|
|
465
|
+
)
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
// Revert if there's no payout limit.
|
|
469
|
+
} else if (_nativeCurrencyPayoutLimit == 0) {
|
|
470
|
+
vm.expectRevert(
|
|
471
|
+
abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_InadequateControllerPayoutLimit.selector, 0, 0)
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Pay out native tokens up to the payout limit. Since `splits[]` is empty, everything goes to project owner.
|
|
476
|
+
_terminal.sendPayoutsOf({
|
|
477
|
+
projectId: _projectId,
|
|
478
|
+
amount: _nativeCurrencyPayoutLimit,
|
|
479
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
480
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
481
|
+
minTokensPaidOut: 0
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
uint256 _projectOwnerNativeBalance;
|
|
485
|
+
|
|
486
|
+
// Check the payout if one is expected.
|
|
487
|
+
if (_nativeCurrencyPayoutLimit <= _nativePayAmount && _nativeCurrencyPayoutLimit != 0) {
|
|
488
|
+
// Make sure the project owner received the payout.
|
|
489
|
+
_projectOwnerNativeBalance =
|
|
490
|
+
_nativeCurrencyPayoutLimit - _nativeCurrencyPayoutLimit * _terminal.FEE() / JBConstants.MAX_FEE;
|
|
491
|
+
assertEq(_projectOwner.balance, _projectOwnerNativeBalance);
|
|
492
|
+
assertEq(
|
|
493
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
|
|
494
|
+
_nativePayAmount - _nativeCurrencySurplusAllowance - _nativeCurrencyPayoutLimit
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
// Make sure the fee was paid correctly.
|
|
498
|
+
assertEq(
|
|
499
|
+
jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
|
|
500
|
+
(_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance)
|
|
501
|
+
+ (_nativeCurrencyPayoutLimit - _projectOwnerNativeBalance)
|
|
502
|
+
);
|
|
503
|
+
assertEq(
|
|
504
|
+
address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
|
|
505
|
+
);
|
|
506
|
+
|
|
507
|
+
// Make sure the project owner got the expected number of the fee project's tokens.
|
|
508
|
+
assertEq(
|
|
509
|
+
_tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID),
|
|
510
|
+
mulDiv(
|
|
511
|
+
(_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance)
|
|
512
|
+
+ (_nativeCurrencyPayoutLimit - _projectOwnerNativeBalance),
|
|
513
|
+
_weight,
|
|
514
|
+
10 ** _NATIVE_DECIMALS
|
|
515
|
+
) * _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Reclaim native tokens from the surplus by cashing out all of the `_beneficiary`'s tokens.
|
|
520
|
+
vm.prank(_beneficiary);
|
|
521
|
+
_terminal.cashOutTokensOf({
|
|
522
|
+
holder: _beneficiary,
|
|
523
|
+
projectId: _projectId,
|
|
524
|
+
cashOutCount: _beneficiaryTokenBalance,
|
|
525
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
526
|
+
minTokensReclaimed: 0,
|
|
527
|
+
beneficiary: payable(_beneficiary),
|
|
528
|
+
metadata: new bytes(0)
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
// Make sure the beneficiary doesn't have tokens left.
|
|
532
|
+
assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), 0);
|
|
533
|
+
|
|
534
|
+
// Check for a new beneficiary balance if one is expected.
|
|
535
|
+
if (_nativePayAmount > _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit) {
|
|
536
|
+
// Get the expected amount reclaimed.
|
|
537
|
+
uint256 _nativeReclaimAmount = mulDiv(
|
|
538
|
+
mulDiv(
|
|
539
|
+
_nativePayAmount - _nativeCurrencySurplusAllowance - _nativeCurrencyPayoutLimit,
|
|
540
|
+
_beneficiaryTokenBalance,
|
|
541
|
+
mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
|
|
542
|
+
),
|
|
543
|
+
_metadata.cashOutTaxRate
|
|
544
|
+
+ mulDiv(
|
|
545
|
+
_beneficiaryTokenBalance,
|
|
546
|
+
JBConstants.MAX_CASH_OUT_TAX_RATE - _metadata.cashOutTaxRate,
|
|
547
|
+
mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
|
|
548
|
+
),
|
|
549
|
+
JBConstants.MAX_CASH_OUT_TAX_RATE
|
|
550
|
+
);
|
|
551
|
+
// Calculate the fee from the cash out.
|
|
552
|
+
uint256 _feeAmount = _nativeReclaimAmount * _terminal.FEE() / JBConstants.MAX_FEE;
|
|
553
|
+
assertEq(_beneficiary.balance, _beneficiaryNativeBalance + _nativeReclaimAmount - _feeAmount);
|
|
554
|
+
|
|
555
|
+
// Make sure the fee was paid correctly.
|
|
556
|
+
assertEq(
|
|
557
|
+
jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
|
|
558
|
+
(_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance)
|
|
559
|
+
+ (_nativeCurrencyPayoutLimit - _projectOwnerNativeBalance) + _feeAmount
|
|
560
|
+
);
|
|
561
|
+
assertEq(
|
|
562
|
+
address(_terminal).balance,
|
|
563
|
+
_nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
|
|
564
|
+
- (_nativeReclaimAmount - _feeAmount)
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
// Make sure the project owner got the expected number of tokens from the fee.
|
|
568
|
+
assertEq(
|
|
569
|
+
_tokens.totalBalanceOf(_beneficiary, _FEE_PROJECT_ID),
|
|
570
|
+
mulDiv(_feeAmount, _weight, 10 ** _NATIVE_DECIMALS) * _metadata.reservedPercent
|
|
571
|
+
/ JBConstants.MAX_RESERVED_PERCENT
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function testFuzzNativeAllowanceWithRevertingFeeProject(
|
|
577
|
+
uint224 _nativeCurrencySurplusAllowance,
|
|
578
|
+
uint224 _nativeCurrencyPayoutLimit,
|
|
579
|
+
uint256 _nativePayAmount,
|
|
580
|
+
bool _feeProjectAcceptsToken
|
|
581
|
+
)
|
|
582
|
+
public
|
|
583
|
+
{
|
|
584
|
+
// Make sure the amount of native tokens to pay is bounded.
|
|
585
|
+
_nativePayAmount = bound(_nativePayAmount, 0, 1_000_000 * 10 ** _NATIVE_DECIMALS);
|
|
586
|
+
|
|
587
|
+
// Make sure the values don't overflow the registry.
|
|
588
|
+
unchecked {
|
|
589
|
+
vm.assume(
|
|
590
|
+
_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit >= _nativeCurrencySurplusAllowance
|
|
591
|
+
&& _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit >= _nativeCurrencyPayoutLimit
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Package up the limits for the given terminal.
|
|
596
|
+
JBFundAccessLimitGroup[] memory _fundAccessLimitGroup = new JBFundAccessLimitGroup[](1);
|
|
597
|
+
{
|
|
598
|
+
// Specify a payout limit.
|
|
599
|
+
JBCurrencyAmount[] memory _payoutLimits = new JBCurrencyAmount[](1);
|
|
600
|
+
_payoutLimits[0] = JBCurrencyAmount({
|
|
601
|
+
amount: _nativeCurrencyPayoutLimit, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
// Specify a surplus allowance.
|
|
605
|
+
JBCurrencyAmount[] memory _surplusAllowances = new JBCurrencyAmount[](1);
|
|
606
|
+
_surplusAllowances[0] = JBCurrencyAmount({
|
|
607
|
+
amount: _nativeCurrencySurplusAllowance, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
_fundAccessLimitGroup[0] = JBFundAccessLimitGroup({
|
|
611
|
+
terminal: address(_terminal),
|
|
612
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
613
|
+
payoutLimits: _payoutLimits,
|
|
614
|
+
surplusAllowances: _surplusAllowances
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
{
|
|
619
|
+
JBTerminalConfig[] memory _terminalConfigurations = new JBTerminalConfig[](1);
|
|
620
|
+
JBAccountingContext[] memory _tokensToAccept = new JBAccountingContext[](1);
|
|
621
|
+
_tokensToAccept[0] = JBAccountingContext({
|
|
622
|
+
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
623
|
+
});
|
|
624
|
+
_terminalConfigurations[0] =
|
|
625
|
+
JBTerminalConfig({terminal: _terminal, accountingContextsToAccept: _tokensToAccept});
|
|
626
|
+
|
|
627
|
+
// Create a first project to collect fees.
|
|
628
|
+
_controller.launchProjectFor({
|
|
629
|
+
owner: address(420), // Random.
|
|
630
|
+
projectUri: "whatever",
|
|
631
|
+
rulesetConfigurations: new JBRulesetConfig[](0), // No ruleset config will force revert when paid.
|
|
632
|
+
// Set the fee collecting terminal's native token accounting context if the test calls for doing so.
|
|
633
|
+
terminalConfigurations: _feeProjectAcceptsToken ? _terminalConfigurations : new JBTerminalConfig[](0), // Set
|
|
634
|
+
// terminals to receive fees.
|
|
635
|
+
memo: ""
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
// Package up the ruleset configuration.
|
|
639
|
+
JBRulesetConfig[] memory _rulesetConfigurations = new JBRulesetConfig[](1);
|
|
640
|
+
_rulesetConfigurations[0].mustStartAtOrAfter = 0;
|
|
641
|
+
_rulesetConfigurations[0].duration = 0;
|
|
642
|
+
_rulesetConfigurations[0].weight = _weight;
|
|
643
|
+
_rulesetConfigurations[0].weightCutPercent = 0;
|
|
644
|
+
_rulesetConfigurations[0].approvalHook = IJBRulesetApprovalHook(address(0));
|
|
645
|
+
_rulesetConfigurations[0].metadata = _metadata;
|
|
646
|
+
_rulesetConfigurations[0].splitGroups = new JBSplitGroup[](0);
|
|
647
|
+
_rulesetConfigurations[0].fundAccessLimitGroups = _fundAccessLimitGroup;
|
|
648
|
+
|
|
649
|
+
// Create the project to test.
|
|
650
|
+
_projectId = _controller.launchProjectFor({
|
|
651
|
+
owner: _projectOwner,
|
|
652
|
+
projectUri: "myIPFSHash",
|
|
653
|
+
rulesetConfigurations: _rulesetConfigurations,
|
|
654
|
+
terminalConfigurations: _terminalConfigurations,
|
|
655
|
+
memo: ""
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Make a payment to the project to give it a starting balance. Send the tokens to the `_beneficiary`.
|
|
660
|
+
_terminal.pay{value: _nativePayAmount}({
|
|
661
|
+
projectId: _projectId,
|
|
662
|
+
amount: _nativePayAmount,
|
|
663
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
664
|
+
beneficiary: _beneficiary,
|
|
665
|
+
minReturnedTokens: 0,
|
|
666
|
+
memo: "",
|
|
667
|
+
metadata: new bytes(0)
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
// Make sure the beneficiary got the expected number of tokens.
|
|
671
|
+
uint256 _beneficiaryTokenBalance = mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
|
|
672
|
+
* _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT;
|
|
673
|
+
assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), _beneficiaryTokenBalance);
|
|
674
|
+
|
|
675
|
+
// Make sure the terminal holds the full native token balance.
|
|
676
|
+
assertEq(
|
|
677
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN), _nativePayAmount
|
|
678
|
+
);
|
|
679
|
+
|
|
680
|
+
// Revert if there's no surplus allowance.
|
|
681
|
+
if (_nativeCurrencySurplusAllowance == 0) {
|
|
682
|
+
vm.expectRevert(
|
|
683
|
+
abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_InadequateControllerAllowance.selector, 0, 0)
|
|
684
|
+
);
|
|
685
|
+
// Revert if there's no surplus, or if too much is being withdrawn.
|
|
686
|
+
} else if (_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit > _nativePayAmount) {
|
|
687
|
+
vm.expectRevert(
|
|
688
|
+
abi.encodeWithSelector(
|
|
689
|
+
JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
|
|
690
|
+
_nativeCurrencySurplusAllowance,
|
|
691
|
+
_nativeCurrencyPayoutLimit > _nativePayAmount ? 0 : _nativePayAmount - _nativeCurrencyPayoutLimit
|
|
692
|
+
)
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// Use the full surplus allowance.
|
|
697
|
+
vm.prank(_projectOwner);
|
|
698
|
+
_terminal.useAllowanceOf({
|
|
699
|
+
projectId: _projectId,
|
|
700
|
+
amount: _nativeCurrencySurplusAllowance,
|
|
701
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
702
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
703
|
+
minTokensPaidOut: 0,
|
|
704
|
+
beneficiary: payable(_beneficiary),
|
|
705
|
+
feeBeneficiary: payable(_projectOwner),
|
|
706
|
+
memo: "MEMO"
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
// Keep a reference to the beneficiary's balance.
|
|
710
|
+
uint256 _beneficiaryNativeBalance;
|
|
711
|
+
|
|
712
|
+
// Check the collected balance if one is expected.
|
|
713
|
+
if (_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit <= _nativePayAmount) {
|
|
714
|
+
// Make sure the beneficiary received the funds and that they are no longer in the terminal.
|
|
715
|
+
_beneficiaryNativeBalance = _nativeCurrencySurplusAllowance
|
|
716
|
+
- mulDiv(_nativeCurrencySurplusAllowance, _terminal.FEE(), JBConstants.MAX_FEE);
|
|
717
|
+
assertEq(_beneficiary.balance, _beneficiaryNativeBalance);
|
|
718
|
+
// Make sure the fee stays in the terminal.
|
|
719
|
+
assertEq(
|
|
720
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
|
|
721
|
+
_nativePayAmount - _beneficiaryNativeBalance
|
|
722
|
+
);
|
|
723
|
+
|
|
724
|
+
// Make sure the fee was not taken.
|
|
725
|
+
assertEq(jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN), 0);
|
|
726
|
+
assertEq(address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance);
|
|
727
|
+
|
|
728
|
+
// Make sure the beneficiary got no tokens.
|
|
729
|
+
assertEq(_tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID), 0);
|
|
730
|
+
} else {
|
|
731
|
+
// Set the native token's surplus allowance to 0 if it wasn't used.
|
|
732
|
+
_nativeCurrencySurplusAllowance = 0;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Revert if the payout limit is greater than the balance.
|
|
736
|
+
if (_nativeCurrencyPayoutLimit > _nativePayAmount) {
|
|
737
|
+
vm.expectRevert(
|
|
738
|
+
abi.encodeWithSelector(
|
|
739
|
+
JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
|
|
740
|
+
_nativeCurrencyPayoutLimit,
|
|
741
|
+
_nativePayAmount
|
|
742
|
+
)
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
// Revert if there's no payout limit.
|
|
746
|
+
} else if (_nativeCurrencyPayoutLimit == 0) {
|
|
747
|
+
vm.expectRevert(
|
|
748
|
+
abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_InadequateControllerPayoutLimit.selector, 0, 0)
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Pay out native tokens up to the payout limit. Since `splits[]` is empty, everything goes to project owner.
|
|
753
|
+
_terminal.sendPayoutsOf({
|
|
754
|
+
projectId: _projectId,
|
|
755
|
+
amount: _nativeCurrencyPayoutLimit,
|
|
756
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
757
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
758
|
+
minTokensPaidOut: 0
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
uint256 _projectOwnerNativeBalance;
|
|
762
|
+
|
|
763
|
+
// Check the received payout if one is expected.
|
|
764
|
+
if (_nativeCurrencyPayoutLimit <= _nativePayAmount && _nativeCurrencyPayoutLimit != 0) {
|
|
765
|
+
// Make sure the project owner received the funds that were paid out.
|
|
766
|
+
_projectOwnerNativeBalance =
|
|
767
|
+
_nativeCurrencyPayoutLimit - _nativeCurrencyPayoutLimit * _terminal.FEE() / JBConstants.MAX_FEE;
|
|
768
|
+
assertEq(_projectOwner.balance, _projectOwnerNativeBalance);
|
|
769
|
+
// Make sure the fee stays in the terminal.
|
|
770
|
+
assertEq(
|
|
771
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
|
|
772
|
+
_nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
|
|
773
|
+
);
|
|
774
|
+
|
|
775
|
+
// Make sure the fee was paid correctly.
|
|
776
|
+
assertEq(jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN), 0);
|
|
777
|
+
assertEq(
|
|
778
|
+
address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
|
|
779
|
+
);
|
|
780
|
+
|
|
781
|
+
// Make sure the project owner got the expected number of tokens.
|
|
782
|
+
assertEq(_tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID), 0);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Reclaim native tokens from the surplus by cashing out all of the `_beneficiary`'s tokens.
|
|
786
|
+
vm.prank(_beneficiary);
|
|
787
|
+
_terminal.cashOutTokensOf({
|
|
788
|
+
holder: _beneficiary,
|
|
789
|
+
projectId: _projectId,
|
|
790
|
+
cashOutCount: _beneficiaryTokenBalance,
|
|
791
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
792
|
+
minTokensReclaimed: 0,
|
|
793
|
+
beneficiary: payable(_beneficiary),
|
|
794
|
+
metadata: new bytes(0)
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
// Make sure the beneficiary doesn't have tokens left.
|
|
798
|
+
assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), 0);
|
|
799
|
+
|
|
800
|
+
// Check for a new beneficiary balance if one is expected.
|
|
801
|
+
if (_nativePayAmount > _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit) {
|
|
802
|
+
// Get the expected amount reclaimed.
|
|
803
|
+
uint256 _nativeReclaimAmount = mulDiv(
|
|
804
|
+
mulDiv(
|
|
805
|
+
_nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance,
|
|
806
|
+
_beneficiaryTokenBalance,
|
|
807
|
+
mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
|
|
808
|
+
),
|
|
809
|
+
_metadata.cashOutTaxRate
|
|
810
|
+
+ mulDiv(
|
|
811
|
+
_beneficiaryTokenBalance,
|
|
812
|
+
JBConstants.MAX_CASH_OUT_TAX_RATE - _metadata.cashOutTaxRate,
|
|
813
|
+
mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
|
|
814
|
+
),
|
|
815
|
+
JBConstants.MAX_CASH_OUT_TAX_RATE
|
|
816
|
+
);
|
|
817
|
+
|
|
818
|
+
// Calculate the fee from the cash out.
|
|
819
|
+
uint256 _feeAmount = _nativeReclaimAmount * _terminal.FEE() / JBConstants.MAX_FEE;
|
|
820
|
+
assertEq(_beneficiary.balance, _beneficiaryNativeBalance + _nativeReclaimAmount - _feeAmount);
|
|
821
|
+
// Make sure the fee stays in the terminal.
|
|
822
|
+
assertEq(
|
|
823
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
|
|
824
|
+
_nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
|
|
825
|
+
- (_nativeReclaimAmount - _feeAmount)
|
|
826
|
+
);
|
|
827
|
+
|
|
828
|
+
// Make sure the fee was paid correctly.
|
|
829
|
+
assertEq(jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN), 0);
|
|
830
|
+
assertEq(
|
|
831
|
+
address(_terminal).balance,
|
|
832
|
+
_nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
|
|
833
|
+
- (_nativeReclaimAmount - _feeAmount)
|
|
834
|
+
);
|
|
835
|
+
|
|
836
|
+
// Make sure the project owner got the expected number of tokens from the fee.
|
|
837
|
+
assertEq(_tokens.totalBalanceOf(_beneficiary, _FEE_PROJECT_ID), 0);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function testFuzzNativeTokenAllowanceForTheFeeProject(
|
|
842
|
+
uint224 _nativeCurrencySurplusAllowance,
|
|
843
|
+
uint224 _nativeCurrencyPayoutLimit,
|
|
844
|
+
uint256 _nativePayAmount
|
|
845
|
+
)
|
|
846
|
+
public
|
|
847
|
+
{
|
|
848
|
+
// Make sure the amount of native tokens to pay is bounded.
|
|
849
|
+
_nativePayAmount = bound(_nativePayAmount, 0, 1_000_000 * 10 ** _NATIVE_DECIMALS);
|
|
850
|
+
|
|
851
|
+
// Make sure the values don't overflow the registry.
|
|
852
|
+
unchecked {
|
|
853
|
+
vm.assume(
|
|
854
|
+
_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit >= _nativeCurrencySurplusAllowance
|
|
855
|
+
&& _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit >= _nativeCurrencyPayoutLimit
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// Package up the limits for the given terminal.
|
|
860
|
+
JBFundAccessLimitGroup[] memory _fundAccessLimitGroup = new JBFundAccessLimitGroup[](1);
|
|
861
|
+
{
|
|
862
|
+
// Specify a payout limit.
|
|
863
|
+
JBCurrencyAmount[] memory _payoutLimits = new JBCurrencyAmount[](1);
|
|
864
|
+
_payoutLimits[0] = JBCurrencyAmount({
|
|
865
|
+
amount: _nativeCurrencyPayoutLimit, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
// Specify a surplus allowance.
|
|
869
|
+
JBCurrencyAmount[] memory _surplusAllowances = new JBCurrencyAmount[](1);
|
|
870
|
+
_surplusAllowances[0] = JBCurrencyAmount({
|
|
871
|
+
amount: _nativeCurrencySurplusAllowance, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
_fundAccessLimitGroup[0] = JBFundAccessLimitGroup({
|
|
875
|
+
terminal: address(_terminal),
|
|
876
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
877
|
+
payoutLimits: _payoutLimits,
|
|
878
|
+
surplusAllowances: _surplusAllowances
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
{
|
|
883
|
+
// Package up the ruleset configuration.
|
|
884
|
+
JBRulesetConfig[] memory _rulesetConfigurations = new JBRulesetConfig[](1);
|
|
885
|
+
_rulesetConfigurations[0].mustStartAtOrAfter = 0;
|
|
886
|
+
_rulesetConfigurations[0].duration = 0;
|
|
887
|
+
_rulesetConfigurations[0].weight = _weight;
|
|
888
|
+
_rulesetConfigurations[0].weightCutPercent = 0;
|
|
889
|
+
_rulesetConfigurations[0].approvalHook = IJBRulesetApprovalHook(address(0));
|
|
890
|
+
_rulesetConfigurations[0].metadata = _metadata;
|
|
891
|
+
_rulesetConfigurations[0].splitGroups = new JBSplitGroup[](0);
|
|
892
|
+
_rulesetConfigurations[0].fundAccessLimitGroups = _fundAccessLimitGroup;
|
|
893
|
+
|
|
894
|
+
JBTerminalConfig[] memory _terminalConfigurations = new JBTerminalConfig[](1);
|
|
895
|
+
JBAccountingContext[] memory _tokensToAccept = new JBAccountingContext[](1);
|
|
896
|
+
_tokensToAccept[0] = JBAccountingContext({
|
|
897
|
+
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
898
|
+
});
|
|
899
|
+
_terminalConfigurations[0] =
|
|
900
|
+
JBTerminalConfig({terminal: _terminal, accountingContextsToAccept: _tokensToAccept});
|
|
901
|
+
|
|
902
|
+
// Create the project to test.
|
|
903
|
+
_projectId = _controller.launchProjectFor({
|
|
904
|
+
owner: _projectOwner,
|
|
905
|
+
projectUri: "myIPFSHash",
|
|
906
|
+
rulesetConfigurations: _rulesetConfigurations,
|
|
907
|
+
terminalConfigurations: _terminalConfigurations,
|
|
908
|
+
memo: ""
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Make a payment to the project to give it a starting balance. Send the tokens to the `_beneficiary`.
|
|
913
|
+
_terminal.pay{value: _nativePayAmount}({
|
|
914
|
+
projectId: _projectId,
|
|
915
|
+
amount: _nativePayAmount,
|
|
916
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
917
|
+
beneficiary: _beneficiary,
|
|
918
|
+
minReturnedTokens: 0,
|
|
919
|
+
memo: "",
|
|
920
|
+
metadata: new bytes(0)
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
// Make sure the beneficiary got the expected number of tokens.
|
|
924
|
+
uint256 _beneficiaryTokenBalance = mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
|
|
925
|
+
* _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT;
|
|
926
|
+
assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), _beneficiaryTokenBalance);
|
|
927
|
+
|
|
928
|
+
// Make sure the terminal holds the full native token balance.
|
|
929
|
+
assertEq(
|
|
930
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN), _nativePayAmount
|
|
931
|
+
);
|
|
932
|
+
|
|
933
|
+
// Revert if there's no surplus allowance.
|
|
934
|
+
if (_nativeCurrencySurplusAllowance == 0) {
|
|
935
|
+
vm.expectRevert(
|
|
936
|
+
abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_InadequateControllerAllowance.selector, 0, 0)
|
|
937
|
+
);
|
|
938
|
+
// Revert if there's no surplus, or if too much is being withdrawn.
|
|
939
|
+
} else if (_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit > _nativePayAmount) {
|
|
940
|
+
vm.expectRevert(
|
|
941
|
+
abi.encodeWithSelector(
|
|
942
|
+
JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
|
|
943
|
+
_nativeCurrencySurplusAllowance,
|
|
944
|
+
_nativeCurrencyPayoutLimit > _nativePayAmount ? 0 : _nativePayAmount - _nativeCurrencyPayoutLimit
|
|
945
|
+
)
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// Use the full surplus allowance.
|
|
950
|
+
vm.prank(_projectOwner);
|
|
951
|
+
_terminal.useAllowanceOf({
|
|
952
|
+
projectId: _projectId,
|
|
953
|
+
amount: _nativeCurrencySurplusAllowance,
|
|
954
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
955
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
956
|
+
minTokensPaidOut: 0,
|
|
957
|
+
beneficiary: payable(_beneficiary),
|
|
958
|
+
feeBeneficiary: payable(_projectOwner),
|
|
959
|
+
memo: "MEMO"
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
// Keep a reference to the beneficiary's balance.
|
|
963
|
+
uint256 _beneficiaryNativeBalance;
|
|
964
|
+
|
|
965
|
+
// Check the collected balance if one is expected.
|
|
966
|
+
if (_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit <= _nativePayAmount) {
|
|
967
|
+
// Make sure the beneficiary received the funds and that they are no longer in the terminal.
|
|
968
|
+
_beneficiaryNativeBalance = _nativeCurrencySurplusAllowance
|
|
969
|
+
- mulDiv(_nativeCurrencySurplusAllowance, _terminal.FEE(), JBConstants.MAX_FEE);
|
|
970
|
+
assertEq(_beneficiary.balance, _beneficiaryNativeBalance);
|
|
971
|
+
assertEq(
|
|
972
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
|
|
973
|
+
_nativePayAmount - _beneficiaryNativeBalance
|
|
974
|
+
);
|
|
975
|
+
assertEq(address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance);
|
|
976
|
+
|
|
977
|
+
// Make sure the beneficiary got the expected number of tokens.
|
|
978
|
+
assertEq(
|
|
979
|
+
_tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID),
|
|
980
|
+
mulDiv(_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance, _weight, 10 ** _NATIVE_DECIMALS)
|
|
981
|
+
* _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT
|
|
982
|
+
);
|
|
983
|
+
} else {
|
|
984
|
+
// Set the native token surplus allowance to 0 if it wasn't used.
|
|
985
|
+
_nativeCurrencySurplusAllowance = 0;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Revert if the payout limit is greater than the balance.
|
|
989
|
+
if (_nativeCurrencyPayoutLimit > _nativePayAmount) {
|
|
990
|
+
vm.expectRevert(
|
|
991
|
+
abi.encodeWithSelector(
|
|
992
|
+
JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
|
|
993
|
+
_nativeCurrencyPayoutLimit,
|
|
994
|
+
_nativePayAmount
|
|
995
|
+
)
|
|
996
|
+
);
|
|
997
|
+
|
|
998
|
+
// Revert if there's no payout limit.
|
|
999
|
+
} else if (_nativeCurrencyPayoutLimit == 0) {
|
|
1000
|
+
vm.expectRevert(
|
|
1001
|
+
abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_InadequateControllerPayoutLimit.selector, 0, 0)
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// Pay out native tokens up to the payout limit. Since `splits[]` is empty, everything goes to project owner.
|
|
1006
|
+
_terminal.sendPayoutsOf({
|
|
1007
|
+
projectId: _projectId,
|
|
1008
|
+
amount: _nativeCurrencyPayoutLimit,
|
|
1009
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
1010
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1011
|
+
minTokensPaidOut: 0
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
uint256 _projectOwnerNativeBalance;
|
|
1015
|
+
|
|
1016
|
+
// Check the received payout if one is expected.
|
|
1017
|
+
if (_nativeCurrencyPayoutLimit <= _nativePayAmount && _nativeCurrencyPayoutLimit != 0) {
|
|
1018
|
+
// Make sure the project owner received the funds that were paid out.
|
|
1019
|
+
_projectOwnerNativeBalance =
|
|
1020
|
+
_nativeCurrencyPayoutLimit - _nativeCurrencyPayoutLimit * _terminal.FEE() / JBConstants.MAX_FEE;
|
|
1021
|
+
assertEq(_projectOwner.balance, _projectOwnerNativeBalance);
|
|
1022
|
+
assertEq(
|
|
1023
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
|
|
1024
|
+
_nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
|
|
1025
|
+
);
|
|
1026
|
+
assertEq(
|
|
1027
|
+
address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
|
|
1028
|
+
);
|
|
1029
|
+
|
|
1030
|
+
// Make sure the project owner got the expected number of tokens.
|
|
1031
|
+
assertEq(
|
|
1032
|
+
_tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID),
|
|
1033
|
+
mulDiv(
|
|
1034
|
+
(_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance)
|
|
1035
|
+
+ (_nativeCurrencyPayoutLimit - _projectOwnerNativeBalance),
|
|
1036
|
+
_weight,
|
|
1037
|
+
10 ** _NATIVE_DECIMALS
|
|
1038
|
+
) * _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// Reclaim native tokens from the surplus by cashing out all of the `_beneficiary`'s tokens.
|
|
1043
|
+
vm.prank(_beneficiary);
|
|
1044
|
+
_terminal.cashOutTokensOf({
|
|
1045
|
+
holder: _beneficiary,
|
|
1046
|
+
projectId: _projectId,
|
|
1047
|
+
cashOutCount: _beneficiaryTokenBalance,
|
|
1048
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
1049
|
+
minTokensReclaimed: 0,
|
|
1050
|
+
beneficiary: payable(_beneficiary),
|
|
1051
|
+
metadata: new bytes(0)
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
// Check for a new beneficiary balance if one is expected.
|
|
1055
|
+
if (_nativePayAmount > _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit) {
|
|
1056
|
+
// Keep a reference to the total amount paid, including from fees.
|
|
1057
|
+
uint256 _totalPaid = _nativePayAmount + (_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance)
|
|
1058
|
+
+ (_nativeCurrencyPayoutLimit - _projectOwnerNativeBalance);
|
|
1059
|
+
|
|
1060
|
+
// Get the expected amount reclaimed.
|
|
1061
|
+
uint256 _nativeReclaimAmount = mulDiv(
|
|
1062
|
+
mulDiv(
|
|
1063
|
+
_nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance,
|
|
1064
|
+
_beneficiaryTokenBalance,
|
|
1065
|
+
mulDiv(_totalPaid, _weight, 10 ** _NATIVE_DECIMALS)
|
|
1066
|
+
),
|
|
1067
|
+
_metadata.cashOutTaxRate
|
|
1068
|
+
+ mulDiv(
|
|
1069
|
+
_beneficiaryTokenBalance,
|
|
1070
|
+
JBConstants.MAX_CASH_OUT_TAX_RATE - _metadata.cashOutTaxRate,
|
|
1071
|
+
mulDiv(_totalPaid, _weight, 10 ** _NATIVE_DECIMALS)
|
|
1072
|
+
),
|
|
1073
|
+
JBConstants.MAX_CASH_OUT_TAX_RATE
|
|
1074
|
+
);
|
|
1075
|
+
// Calculate the fee from the cash out.
|
|
1076
|
+
uint256 _feeAmount = _nativeReclaimAmount * _terminal.FEE() / JBConstants.MAX_FEE;
|
|
1077
|
+
|
|
1078
|
+
// Make sure the beneficiary received tokens from the fee just paid.
|
|
1079
|
+
assertEq(
|
|
1080
|
+
_tokens.totalBalanceOf(_beneficiary, _projectId),
|
|
1081
|
+
mulDiv(_feeAmount, _weight, 10 ** _NATIVE_DECIMALS) * _metadata.reservedPercent
|
|
1082
|
+
/ JBConstants.MAX_RESERVED_PERCENT
|
|
1083
|
+
);
|
|
1084
|
+
|
|
1085
|
+
// Make sure the beneficiary received the funds.
|
|
1086
|
+
assertEq(_beneficiary.balance, _beneficiaryNativeBalance + _nativeReclaimAmount - _feeAmount);
|
|
1087
|
+
|
|
1088
|
+
assertEq(
|
|
1089
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
|
|
1090
|
+
_nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
|
|
1091
|
+
- (_nativeReclaimAmount - _feeAmount)
|
|
1092
|
+
);
|
|
1093
|
+
assertEq(
|
|
1094
|
+
address(_terminal).balance,
|
|
1095
|
+
_nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
|
|
1096
|
+
- (_nativeReclaimAmount - _feeAmount)
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
function testFuzzMultiCurrencyAllowance(
|
|
1102
|
+
uint224 _nativeCurrencySurplusAllowance,
|
|
1103
|
+
uint224 _nativeCurrencyPayoutLimit,
|
|
1104
|
+
uint256 _nativePayAmount,
|
|
1105
|
+
uint224 _usdCurrencySurplusAllowance,
|
|
1106
|
+
uint224 _usdCurrencyPayoutLimit,
|
|
1107
|
+
uint256 _usdcPayAmount
|
|
1108
|
+
)
|
|
1109
|
+
public
|
|
1110
|
+
{
|
|
1111
|
+
// Make sure the amount of native tokens to pay is bounded.
|
|
1112
|
+
_nativePayAmount = bound(_nativePayAmount, 0, 1_000_000 * 10 ** _NATIVE_DECIMALS);
|
|
1113
|
+
_usdcPayAmount = bound(_usdcPayAmount, 0, 1_000_000 * 10 ** _usdcToken.decimals());
|
|
1114
|
+
|
|
1115
|
+
// Make sure the values don't overflow the registry.
|
|
1116
|
+
unchecked {
|
|
1117
|
+
vm.assume(
|
|
1118
|
+
_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit >= _nativeCurrencySurplusAllowance
|
|
1119
|
+
&& _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit >= _nativeCurrencyPayoutLimit
|
|
1120
|
+
);
|
|
1121
|
+
vm.assume(
|
|
1122
|
+
_usdCurrencySurplusAllowance + _usdCurrencyPayoutLimit >= _usdCurrencySurplusAllowance
|
|
1123
|
+
&& _usdCurrencySurplusAllowance + _usdCurrencyPayoutLimit >= _usdCurrencyPayoutLimit
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// Ensure there is always a difference of at least 1 between the pay amount and payout limit.
|
|
1128
|
+
vm.assume(
|
|
1129
|
+
_nativeCurrencyPayoutLimit > _nativePayAmount
|
|
1130
|
+
? _nativeCurrencyPayoutLimit - _nativePayAmount > 1
|
|
1131
|
+
: _nativePayAmount - _nativeCurrencyPayoutLimit > 1
|
|
1132
|
+
);
|
|
1133
|
+
|
|
1134
|
+
{
|
|
1135
|
+
// Package up the limits for the given terminal.
|
|
1136
|
+
JBFundAccessLimitGroup[] memory _fundAccessLimitGroup = new JBFundAccessLimitGroup[](1);
|
|
1137
|
+
|
|
1138
|
+
// Specify payout limits.
|
|
1139
|
+
JBCurrencyAmount[] memory _payoutLimits = new JBCurrencyAmount[](2);
|
|
1140
|
+
_payoutLimits[0] = JBCurrencyAmount({
|
|
1141
|
+
amount: _nativeCurrencyPayoutLimit, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1142
|
+
});
|
|
1143
|
+
_payoutLimits[1] =
|
|
1144
|
+
JBCurrencyAmount({amount: _usdCurrencyPayoutLimit, currency: uint32(uint160(address(_usdcToken)))});
|
|
1145
|
+
|
|
1146
|
+
// Specify surplus allowances.
|
|
1147
|
+
JBCurrencyAmount[] memory _surplusAllowances = new JBCurrencyAmount[](2);
|
|
1148
|
+
_surplusAllowances[0] = JBCurrencyAmount({
|
|
1149
|
+
amount: _nativeCurrencySurplusAllowance, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1150
|
+
});
|
|
1151
|
+
_surplusAllowances[1] = JBCurrencyAmount({
|
|
1152
|
+
amount: _usdCurrencySurplusAllowance, currency: uint32(uint160(address(_usdcToken)))
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
_fundAccessLimitGroup[0] = JBFundAccessLimitGroup({
|
|
1156
|
+
terminal: address(_terminal),
|
|
1157
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1158
|
+
payoutLimits: _payoutLimits,
|
|
1159
|
+
surplusAllowances: _surplusAllowances
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1162
|
+
// Package up the ruleset configuration.
|
|
1163
|
+
JBRulesetConfig[] memory _rulesetConfigurations = new JBRulesetConfig[](1);
|
|
1164
|
+
_rulesetConfigurations[0].mustStartAtOrAfter = 0;
|
|
1165
|
+
_rulesetConfigurations[0].duration = 0;
|
|
1166
|
+
_rulesetConfigurations[0].weight = _weight;
|
|
1167
|
+
_rulesetConfigurations[0].weightCutPercent = 0;
|
|
1168
|
+
_rulesetConfigurations[0].approvalHook = IJBRulesetApprovalHook(address(0));
|
|
1169
|
+
_rulesetConfigurations[0].metadata = _metadata;
|
|
1170
|
+
_rulesetConfigurations[0].splitGroups = new JBSplitGroup[](0);
|
|
1171
|
+
_rulesetConfigurations[0].fundAccessLimitGroups = _fundAccessLimitGroup;
|
|
1172
|
+
|
|
1173
|
+
JBTerminalConfig[] memory _terminalConfigurations = new JBTerminalConfig[](1);
|
|
1174
|
+
JBAccountingContext[] memory _tokensToAccept = new JBAccountingContext[](2);
|
|
1175
|
+
_tokensToAccept[0] = JBAccountingContext({
|
|
1176
|
+
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1177
|
+
});
|
|
1178
|
+
_tokensToAccept[1] = JBAccountingContext({
|
|
1179
|
+
token: address(_usdcToken), decimals: 6, currency: uint32(uint160(address(_usdcToken)))
|
|
1180
|
+
});
|
|
1181
|
+
_terminalConfigurations[0] =
|
|
1182
|
+
JBTerminalConfig({terminal: _terminal, accountingContextsToAccept: _tokensToAccept});
|
|
1183
|
+
|
|
1184
|
+
// Create a first project to collect fees.
|
|
1185
|
+
_controller.launchProjectFor({
|
|
1186
|
+
owner: _projectOwner, // Random.
|
|
1187
|
+
projectUri: "whatever",
|
|
1188
|
+
rulesetConfigurations: _rulesetConfigurations, // Use the same ruleset configurations.
|
|
1189
|
+
terminalConfigurations: _terminalConfigurations, // Set terminals to receive fees.
|
|
1190
|
+
memo: ""
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
// Create the project to test.
|
|
1194
|
+
_projectId = _controller.launchProjectFor({
|
|
1195
|
+
owner: _projectOwner,
|
|
1196
|
+
projectUri: "myIPFSHash",
|
|
1197
|
+
rulesetConfigurations: _rulesetConfigurations,
|
|
1198
|
+
terminalConfigurations: _terminalConfigurations,
|
|
1199
|
+
memo: ""
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// Add a price feed to convert from native token to USD currencies.
|
|
1204
|
+
{
|
|
1205
|
+
vm.startPrank(_projectOwner);
|
|
1206
|
+
MockPriceFeed _priceFeedNativeUsd = new MockPriceFeed(_USD_PRICE_PER_NATIVE, _PRICE_FEED_DECIMALS);
|
|
1207
|
+
vm.label(address(_priceFeedNativeUsd), "Mock Price Feed Native-USDC");
|
|
1208
|
+
|
|
1209
|
+
_controller.addPriceFeed({
|
|
1210
|
+
projectId: 1,
|
|
1211
|
+
pricingCurrency: uint32(uint160(address(_usdcToken))),
|
|
1212
|
+
unitCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
1213
|
+
feed: _priceFeedNativeUsd
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
_controller.addPriceFeed({
|
|
1217
|
+
projectId: 2,
|
|
1218
|
+
pricingCurrency: uint32(uint160(address(_usdcToken))),
|
|
1219
|
+
unitCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
1220
|
+
feed: _priceFeedNativeUsd
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
vm.stopPrank();
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// Make a payment to the project to give it a starting balance. Send the tokens to the `_beneficiary`.
|
|
1227
|
+
_terminal.pay{value: _nativePayAmount}({
|
|
1228
|
+
projectId: _projectId,
|
|
1229
|
+
amount: _nativePayAmount,
|
|
1230
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1231
|
+
beneficiary: _beneficiary,
|
|
1232
|
+
minReturnedTokens: 0,
|
|
1233
|
+
memo: "",
|
|
1234
|
+
metadata: new bytes(0)
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
// Make sure the beneficiary got the expected number of tokens from the native token payment.
|
|
1238
|
+
uint256 _beneficiaryTokenBalance = _unreservedPortion(mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS));
|
|
1239
|
+
assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), _beneficiaryTokenBalance);
|
|
1240
|
+
// Mint USDC to this contract.
|
|
1241
|
+
_usdcToken.mint(address(this), _usdcPayAmount);
|
|
1242
|
+
|
|
1243
|
+
// Allow the terminal to spend the USDC.
|
|
1244
|
+
_usdcToken.approve(address(_terminal), _usdcPayAmount);
|
|
1245
|
+
|
|
1246
|
+
// Make a payment to the project to give it a starting balance. Send the tokens to the `_beneficiary`.
|
|
1247
|
+
_terminal.pay({
|
|
1248
|
+
projectId: _projectId,
|
|
1249
|
+
amount: _usdcPayAmount,
|
|
1250
|
+
token: address(_usdcToken),
|
|
1251
|
+
beneficiary: _beneficiary,
|
|
1252
|
+
minReturnedTokens: 0,
|
|
1253
|
+
memo: "",
|
|
1254
|
+
metadata: new bytes(0)
|
|
1255
|
+
});
|
|
1256
|
+
|
|
1257
|
+
// Make sure the terminal holds the full native token balance.
|
|
1258
|
+
assertEq(
|
|
1259
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN), _nativePayAmount
|
|
1260
|
+
);
|
|
1261
|
+
// Make sure the USDC is accounted for.
|
|
1262
|
+
assertEq(jbTerminalStore().balanceOf(address(_terminal), _projectId, address(_usdcToken)), _usdcPayAmount);
|
|
1263
|
+
assertEq(_usdcToken.balanceOf(address(_terminal)), _usdcPayAmount);
|
|
1264
|
+
|
|
1265
|
+
{
|
|
1266
|
+
// Convert the USD amount to a native token amount, by way of the current weight used for issuance.
|
|
1267
|
+
uint256 _usdWeightedPayAmountConvertedToNative = mulDiv(
|
|
1268
|
+
_usdcPayAmount,
|
|
1269
|
+
_weight,
|
|
1270
|
+
mulDiv(_USD_PRICE_PER_NATIVE, 10 ** _usdcToken.decimals(), 10 ** _PRICE_FEED_DECIMALS)
|
|
1271
|
+
);
|
|
1272
|
+
|
|
1273
|
+
// Make sure the beneficiary got the expected number of tokens from the USDC payment.
|
|
1274
|
+
_beneficiaryTokenBalance += _unreservedPortion(_usdWeightedPayAmountConvertedToNative);
|
|
1275
|
+
assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), _beneficiaryTokenBalance);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// Revert if there's no native token allowance.
|
|
1279
|
+
if (_nativeCurrencySurplusAllowance == 0) {
|
|
1280
|
+
vm.expectRevert(
|
|
1281
|
+
abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_InadequateControllerAllowance.selector, 0, 0)
|
|
1282
|
+
);
|
|
1283
|
+
} else if (
|
|
1284
|
+
_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit + _toNative(_usdCurrencyPayoutLimit)
|
|
1285
|
+
> _nativePayAmount
|
|
1286
|
+
) {
|
|
1287
|
+
vm.expectRevert(
|
|
1288
|
+
abi.encodeWithSelector(
|
|
1289
|
+
JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
|
|
1290
|
+
_nativeCurrencySurplusAllowance,
|
|
1291
|
+
_nativeCurrencyPayoutLimit + _toNative(_usdCurrencyPayoutLimit) > _nativePayAmount
|
|
1292
|
+
? 0
|
|
1293
|
+
: _nativePayAmount - _nativeCurrencyPayoutLimit - _toNative(_usdCurrencyPayoutLimit)
|
|
1294
|
+
)
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// Use the full native token surplus allowance.
|
|
1299
|
+
vm.prank(_projectOwner);
|
|
1300
|
+
_terminal.useAllowanceOf({
|
|
1301
|
+
projectId: _projectId,
|
|
1302
|
+
amount: _nativeCurrencySurplusAllowance,
|
|
1303
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
1304
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1305
|
+
minTokensPaidOut: 0,
|
|
1306
|
+
beneficiary: payable(_beneficiary),
|
|
1307
|
+
feeBeneficiary: payable(_projectOwner),
|
|
1308
|
+
memo: "MEMO"
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
// Keep a reference to the beneficiary's native token balance.
|
|
1312
|
+
uint256 _beneficiaryNativeBalance;
|
|
1313
|
+
|
|
1314
|
+
// Check the collected balance if one is expected.
|
|
1315
|
+
if (
|
|
1316
|
+
_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit + _toNative(_usdCurrencyPayoutLimit)
|
|
1317
|
+
<= _nativePayAmount
|
|
1318
|
+
) {
|
|
1319
|
+
// Make sure the beneficiary received the funds and that they are no longer in the terminal.
|
|
1320
|
+
_beneficiaryNativeBalance = _nativeCurrencySurplusAllowance
|
|
1321
|
+
- mulDiv(_nativeCurrencySurplusAllowance, _terminal.FEE(), JBConstants.MAX_FEE);
|
|
1322
|
+
assertEq(_beneficiary.balance, _beneficiaryNativeBalance);
|
|
1323
|
+
assertEq(
|
|
1324
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
|
|
1325
|
+
_nativePayAmount - _nativeCurrencySurplusAllowance
|
|
1326
|
+
);
|
|
1327
|
+
|
|
1328
|
+
// Make sure the fee was paid correctly.
|
|
1329
|
+
assertEq(
|
|
1330
|
+
jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
|
|
1331
|
+
_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance
|
|
1332
|
+
);
|
|
1333
|
+
assertEq(address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance);
|
|
1334
|
+
|
|
1335
|
+
// Make sure the beneficiary got the expected number of tokens.
|
|
1336
|
+
assertEq(
|
|
1337
|
+
_tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID),
|
|
1338
|
+
_unreservedPortion(
|
|
1339
|
+
mulDiv(_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance, _weight, 10 ** _NATIVE_DECIMALS)
|
|
1340
|
+
)
|
|
1341
|
+
);
|
|
1342
|
+
} else {
|
|
1343
|
+
// Set the native token surplus allowance to 0 if it wasn't used.
|
|
1344
|
+
_nativeCurrencySurplusAllowance = 0;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
// Revert if there's no native token allowance.
|
|
1348
|
+
if (_usdCurrencySurplusAllowance == 0) {
|
|
1349
|
+
vm.expectRevert(
|
|
1350
|
+
abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_InadequateControllerAllowance.selector, 0, 0)
|
|
1351
|
+
);
|
|
1352
|
+
// revert if the USD surplus allowance resolved to native tokens is greater than 0, and there is sufficient
|
|
1353
|
+
// surplus to pull from including what was already pulled from.
|
|
1354
|
+
} else if (
|
|
1355
|
+
_toNative(_usdCurrencySurplusAllowance) > 0
|
|
1356
|
+
&& _toNative(_usdCurrencySurplusAllowance + _usdCurrencyPayoutLimit) + _nativeCurrencyPayoutLimit
|
|
1357
|
+
+ _nativeCurrencySurplusAllowance > _nativePayAmount
|
|
1358
|
+
) {
|
|
1359
|
+
vm.expectRevert(
|
|
1360
|
+
abi.encodeWithSelector(
|
|
1361
|
+
JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
|
|
1362
|
+
_toNative(_usdCurrencySurplusAllowance),
|
|
1363
|
+
_toNative(_usdCurrencyPayoutLimit) + _nativeCurrencyPayoutLimit + _nativeCurrencySurplusAllowance
|
|
1364
|
+
> _nativePayAmount
|
|
1365
|
+
? 0
|
|
1366
|
+
: _nativePayAmount - _toNative(_usdCurrencyPayoutLimit) - _nativeCurrencyPayoutLimit
|
|
1367
|
+
- _nativeCurrencySurplusAllowance
|
|
1368
|
+
)
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
// Use the full native token surplus allowance.
|
|
1373
|
+
vm.prank(_projectOwner);
|
|
1374
|
+
_terminal.useAllowanceOf({
|
|
1375
|
+
projectId: _projectId,
|
|
1376
|
+
amount: _usdCurrencySurplusAllowance,
|
|
1377
|
+
currency: uint32(uint160(address(_usdcToken))),
|
|
1378
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1379
|
+
minTokensPaidOut: 0,
|
|
1380
|
+
beneficiary: payable(_beneficiary),
|
|
1381
|
+
feeBeneficiary: payable(_projectOwner),
|
|
1382
|
+
memo: "MEMO"
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
// Check the collected balance if one is expected.
|
|
1386
|
+
if (
|
|
1387
|
+
_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit
|
|
1388
|
+
+ _toNative(_usdCurrencySurplusAllowance + _usdCurrencyPayoutLimit) <= _nativePayAmount
|
|
1389
|
+
) {
|
|
1390
|
+
// Make sure the beneficiary received the funds and that they are no longer in the terminal.
|
|
1391
|
+
_beneficiaryNativeBalance += _toNative(_usdCurrencySurplusAllowance)
|
|
1392
|
+
- mulDiv(_toNative(_usdCurrencySurplusAllowance), _terminal.FEE(), JBConstants.MAX_FEE);
|
|
1393
|
+
assertEq(_beneficiary.balance, _beneficiaryNativeBalance);
|
|
1394
|
+
assertEq(
|
|
1395
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
|
|
1396
|
+
_nativePayAmount - _nativeCurrencySurplusAllowance - _toNative(_usdCurrencySurplusAllowance)
|
|
1397
|
+
);
|
|
1398
|
+
|
|
1399
|
+
// Make sure the fee was paid correctly.
|
|
1400
|
+
assertEq(
|
|
1401
|
+
jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
|
|
1402
|
+
_nativeCurrencySurplusAllowance + _toNative(_usdCurrencySurplusAllowance) - _beneficiaryNativeBalance
|
|
1403
|
+
);
|
|
1404
|
+
assertEq(address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance);
|
|
1405
|
+
|
|
1406
|
+
// Make sure the beneficiary got the expected number of tokens.
|
|
1407
|
+
assertEq(
|
|
1408
|
+
_tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID),
|
|
1409
|
+
_unreservedPortion(
|
|
1410
|
+
mulDiv(
|
|
1411
|
+
_nativeCurrencySurplusAllowance + _toNative(_usdCurrencySurplusAllowance)
|
|
1412
|
+
- _beneficiaryNativeBalance,
|
|
1413
|
+
_weight,
|
|
1414
|
+
10 ** _NATIVE_DECIMALS
|
|
1415
|
+
)
|
|
1416
|
+
)
|
|
1417
|
+
);
|
|
1418
|
+
} else {
|
|
1419
|
+
// Set the native token surplus allowance to 0 if it wasn't used.
|
|
1420
|
+
_usdCurrencySurplusAllowance = 0;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
// Payout limits
|
|
1424
|
+
{
|
|
1425
|
+
// Revert if the payout limit is greater than the balance.
|
|
1426
|
+
if (_nativeCurrencyPayoutLimit > _nativePayAmount) {
|
|
1427
|
+
vm.expectRevert(
|
|
1428
|
+
abi.encodeWithSelector(
|
|
1429
|
+
JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
|
|
1430
|
+
_nativeCurrencyPayoutLimit,
|
|
1431
|
+
_nativePayAmount
|
|
1432
|
+
)
|
|
1433
|
+
);
|
|
1434
|
+
// Revert if there's no payout limit.
|
|
1435
|
+
} else if (_nativeCurrencyPayoutLimit == 0) {
|
|
1436
|
+
vm.expectRevert(
|
|
1437
|
+
abi.encodeWithSelector(
|
|
1438
|
+
JBTerminalStore.JBTerminalStore_InadequateControllerPayoutLimit.selector, 0, 0
|
|
1439
|
+
)
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
// Pay out native tokens up to the payout limit. Since `splits[]` is empty, everything goes to project
|
|
1444
|
+
// owner.
|
|
1445
|
+
_terminal.sendPayoutsOf({
|
|
1446
|
+
projectId: _projectId,
|
|
1447
|
+
amount: _nativeCurrencyPayoutLimit,
|
|
1448
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
1449
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1450
|
+
minTokensPaidOut: 0
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
uint256 _projectOwnerNativeBalance;
|
|
1454
|
+
|
|
1455
|
+
// Check the received payout if one is expected.
|
|
1456
|
+
if (_nativeCurrencyPayoutLimit <= _nativePayAmount && _nativeCurrencyPayoutLimit != 0) {
|
|
1457
|
+
// Make sure the project owner received the funds that were paid out.
|
|
1458
|
+
_projectOwnerNativeBalance =
|
|
1459
|
+
_nativeCurrencyPayoutLimit - _nativeCurrencyPayoutLimit * _terminal.FEE() / JBConstants.MAX_FEE;
|
|
1460
|
+
assertEq(_projectOwner.balance, _projectOwnerNativeBalance);
|
|
1461
|
+
assertEq(
|
|
1462
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
|
|
1463
|
+
_nativePayAmount - _nativeCurrencySurplusAllowance - _toNative(_usdCurrencySurplusAllowance)
|
|
1464
|
+
- _nativeCurrencyPayoutLimit
|
|
1465
|
+
);
|
|
1466
|
+
|
|
1467
|
+
// Make sure the fee was paid correctly.
|
|
1468
|
+
assertEq(
|
|
1469
|
+
jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
|
|
1470
|
+
_nativeCurrencySurplusAllowance + _toNative(_usdCurrencySurplusAllowance)
|
|
1471
|
+
- _beneficiaryNativeBalance + _nativeCurrencyPayoutLimit - _projectOwnerNativeBalance
|
|
1472
|
+
);
|
|
1473
|
+
assertEq(
|
|
1474
|
+
address(_terminal).balance,
|
|
1475
|
+
_nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
|
|
1476
|
+
);
|
|
1477
|
+
|
|
1478
|
+
uint256 _fullPortion = mulDiv(
|
|
1479
|
+
_nativeCurrencySurplusAllowance + _toNative(_usdCurrencySurplusAllowance)
|
|
1480
|
+
- _beneficiaryNativeBalance + _nativeCurrencyPayoutLimit - _projectOwnerNativeBalance,
|
|
1481
|
+
_weight,
|
|
1482
|
+
10 ** _NATIVE_DECIMALS
|
|
1483
|
+
);
|
|
1484
|
+
|
|
1485
|
+
// Make sure the project owner got the expected number of tokens.
|
|
1486
|
+
assertEq(_tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID), _unreservedPortion(_fullPortion));
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
// Revert if the payout limit is greater than the balance.
|
|
1490
|
+
if (
|
|
1491
|
+
_nativeCurrencyPayoutLimit <= _nativePayAmount
|
|
1492
|
+
&& _toNative(_usdCurrencyPayoutLimit) + _nativeCurrencyPayoutLimit > _nativePayAmount
|
|
1493
|
+
) {
|
|
1494
|
+
vm.expectRevert(
|
|
1495
|
+
abi.encodeWithSelector(
|
|
1496
|
+
JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
|
|
1497
|
+
_toNative(_usdCurrencyPayoutLimit),
|
|
1498
|
+
_nativeCurrencyPayoutLimit > _nativePayAmount
|
|
1499
|
+
? _nativePayAmount
|
|
1500
|
+
: _nativePayAmount - _nativeCurrencyPayoutLimit
|
|
1501
|
+
)
|
|
1502
|
+
);
|
|
1503
|
+
} else if (
|
|
1504
|
+
_nativeCurrencyPayoutLimit > _nativePayAmount && _toNative(_usdCurrencyPayoutLimit) > _nativePayAmount
|
|
1505
|
+
) {
|
|
1506
|
+
vm.expectRevert(
|
|
1507
|
+
abi.encodeWithSelector(
|
|
1508
|
+
JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
|
|
1509
|
+
_toNative(_usdCurrencyPayoutLimit),
|
|
1510
|
+
_nativePayAmount
|
|
1511
|
+
)
|
|
1512
|
+
);
|
|
1513
|
+
// Revert if there's no payout limit.
|
|
1514
|
+
} else if (_usdCurrencyPayoutLimit == 0) {
|
|
1515
|
+
vm.expectRevert(
|
|
1516
|
+
abi.encodeWithSelector(
|
|
1517
|
+
JBTerminalStore.JBTerminalStore_InadequateControllerPayoutLimit.selector, 0, 0
|
|
1518
|
+
)
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// Pay out usdc tokens up to the payout limit. Since `splits[]` is empty, everything goes to project
|
|
1523
|
+
// owner.
|
|
1524
|
+
_terminal.sendPayoutsOf({
|
|
1525
|
+
projectId: _projectId,
|
|
1526
|
+
amount: _usdCurrencyPayoutLimit,
|
|
1527
|
+
currency: uint32(uint160(address(_usdcToken))),
|
|
1528
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1529
|
+
minTokensPaidOut: 0
|
|
1530
|
+
});
|
|
1531
|
+
|
|
1532
|
+
// Check the received payout if one is expected.
|
|
1533
|
+
if (
|
|
1534
|
+
_toNative(_usdCurrencyPayoutLimit) + _nativeCurrencyPayoutLimit <= _nativePayAmount
|
|
1535
|
+
&& _usdCurrencyPayoutLimit > 0
|
|
1536
|
+
) {
|
|
1537
|
+
// Make sure the project owner received the funds that were paid out.
|
|
1538
|
+
_projectOwnerNativeBalance += _toNative(_usdCurrencyPayoutLimit) - _toNative(_usdCurrencyPayoutLimit)
|
|
1539
|
+
* _terminal.FEE() / JBConstants.MAX_FEE;
|
|
1540
|
+
assertEq(_projectOwner.balance, _projectOwnerNativeBalance);
|
|
1541
|
+
assertEq(
|
|
1542
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
|
|
1543
|
+
_nativePayAmount - _nativeCurrencySurplusAllowance - _toNative(_usdCurrencySurplusAllowance)
|
|
1544
|
+
- _nativeCurrencyPayoutLimit - _toNative(_usdCurrencyPayoutLimit)
|
|
1545
|
+
);
|
|
1546
|
+
|
|
1547
|
+
// Make sure the fee was paid correctly.
|
|
1548
|
+
assertEq(
|
|
1549
|
+
jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
|
|
1550
|
+
(_nativeCurrencySurplusAllowance
|
|
1551
|
+
+ _toNative(_usdCurrencySurplusAllowance)
|
|
1552
|
+
- _beneficiaryNativeBalance)
|
|
1553
|
+
+ (_nativeCurrencyPayoutLimit + _toNative(_usdCurrencyPayoutLimit) - _projectOwnerNativeBalance)
|
|
1554
|
+
);
|
|
1555
|
+
assertEq(
|
|
1556
|
+
address(_terminal).balance,
|
|
1557
|
+
_nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
|
|
1558
|
+
);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
// Keep a reference to the remaining native token surplus.
|
|
1563
|
+
uint256 _nativeSurplus = _nativeCurrencyPayoutLimit + _toNative(_usdCurrencyPayoutLimit)
|
|
1564
|
+
+ _nativeCurrencySurplusAllowance + _toNative(_usdCurrencySurplusAllowance) >= _nativePayAmount
|
|
1565
|
+
? 0
|
|
1566
|
+
: _nativePayAmount - _nativeCurrencyPayoutLimit - _toNative(_usdCurrencyPayoutLimit)
|
|
1567
|
+
- _nativeCurrencySurplusAllowance - _toNative(_usdCurrencySurplusAllowance);
|
|
1568
|
+
|
|
1569
|
+
// Keep a reference to the remaining native token balance.
|
|
1570
|
+
uint256 _nativeBalance =
|
|
1571
|
+
_nativePayAmount - _nativeCurrencySurplusAllowance - _toNative(_usdCurrencySurplusAllowance);
|
|
1572
|
+
if (_nativeCurrencyPayoutLimit <= _nativePayAmount) {
|
|
1573
|
+
_nativeBalance -= _nativeCurrencyPayoutLimit;
|
|
1574
|
+
if (_toNative(_usdCurrencyPayoutLimit) + _nativeCurrencyPayoutLimit <= _nativePayAmount) {
|
|
1575
|
+
_nativeBalance -= _toNative(_usdCurrencyPayoutLimit);
|
|
1576
|
+
}
|
|
1577
|
+
} else if (_toNative(_usdCurrencyPayoutLimit) <= _nativePayAmount) {
|
|
1578
|
+
_nativeBalance -= _toNative(_usdCurrencyPayoutLimit);
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
// Make sure it's correct.
|
|
1582
|
+
assertEq(jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN), _nativeBalance);
|
|
1583
|
+
|
|
1584
|
+
// Make sure the USDC surplus is correct.
|
|
1585
|
+
assertEq(jbTerminalStore().balanceOf(address(_terminal), _projectId, address(_usdcToken)), _usdcPayAmount);
|
|
1586
|
+
|
|
1587
|
+
// Make sure the total token supply is correct.
|
|
1588
|
+
assertEq(
|
|
1589
|
+
_controller.totalTokenSupplyWithReservedTokensOf(_projectId),
|
|
1590
|
+
mulDiv(
|
|
1591
|
+
_beneficiaryTokenBalance,
|
|
1592
|
+
JBConstants.MAX_RESERVED_PERCENT,
|
|
1593
|
+
JBConstants.MAX_RESERVED_PERCENT - _metadata.reservedPercent
|
|
1594
|
+
)
|
|
1595
|
+
);
|
|
1596
|
+
|
|
1597
|
+
// Keep a reference to the amount of native tokens being reclaimed.
|
|
1598
|
+
uint256 _nativeReclaimAmount;
|
|
1599
|
+
|
|
1600
|
+
vm.startPrank(_beneficiary);
|
|
1601
|
+
|
|
1602
|
+
// If there's surplus.
|
|
1603
|
+
if (_toNative(mulDiv(_usdcPayAmount, 10 ** _NATIVE_DECIMALS, 10 ** _usdcToken.decimals())) + _nativeSurplus > 0)
|
|
1604
|
+
{
|
|
1605
|
+
// Get the expected amount reclaimed.
|
|
1606
|
+
_nativeReclaimAmount = mulDiv(
|
|
1607
|
+
mulDiv(
|
|
1608
|
+
_toNative(mulDiv(_usdcPayAmount, 10 ** _NATIVE_DECIMALS, 10 ** _usdcToken.decimals()))
|
|
1609
|
+
+ _nativeSurplus,
|
|
1610
|
+
_beneficiaryTokenBalance,
|
|
1611
|
+
mulDiv(
|
|
1612
|
+
_beneficiaryTokenBalance,
|
|
1613
|
+
JBConstants.MAX_RESERVED_PERCENT,
|
|
1614
|
+
JBConstants.MAX_RESERVED_PERCENT - _metadata.reservedPercent
|
|
1615
|
+
)
|
|
1616
|
+
),
|
|
1617
|
+
_metadata.cashOutTaxRate
|
|
1618
|
+
+ mulDiv(
|
|
1619
|
+
_beneficiaryTokenBalance,
|
|
1620
|
+
JBConstants.MAX_CASH_OUT_TAX_RATE - _metadata.cashOutTaxRate,
|
|
1621
|
+
mulDiv(
|
|
1622
|
+
_beneficiaryTokenBalance,
|
|
1623
|
+
JBConstants.MAX_RESERVED_PERCENT,
|
|
1624
|
+
JBConstants.MAX_RESERVED_PERCENT - _metadata.reservedPercent
|
|
1625
|
+
)
|
|
1626
|
+
),
|
|
1627
|
+
JBConstants.MAX_CASH_OUT_TAX_RATE
|
|
1628
|
+
);
|
|
1629
|
+
|
|
1630
|
+
// If there is more to reclaim than there are native tokens in the tank.
|
|
1631
|
+
if (_nativeReclaimAmount > _nativeSurplus) {
|
|
1632
|
+
// Keep a reference to the amount to cash out for native tokens, a proportion of available surplus in
|
|
1633
|
+
// native tokens.
|
|
1634
|
+
uint256 _tokenCountToCashOutForNative = mulDiv(
|
|
1635
|
+
_beneficiaryTokenBalance,
|
|
1636
|
+
_nativeSurplus,
|
|
1637
|
+
_nativeSurplus
|
|
1638
|
+
+ _toNative(mulDiv(_usdcPayAmount, 10 ** _NATIVE_DECIMALS, 10 ** _usdcToken.decimals()))
|
|
1639
|
+
);
|
|
1640
|
+
uint256 _tokenSupply = mulDiv(
|
|
1641
|
+
_beneficiaryTokenBalance,
|
|
1642
|
+
JBConstants.MAX_RESERVED_PERCENT,
|
|
1643
|
+
JBConstants.MAX_RESERVED_PERCENT - _metadata.reservedPercent
|
|
1644
|
+
);
|
|
1645
|
+
// Cash out native tokens from the surplus using only the `_beneficiary`'s tokens needed to clear the
|
|
1646
|
+
// native token balance.
|
|
1647
|
+
_terminal.cashOutTokensOf({
|
|
1648
|
+
holder: _beneficiary,
|
|
1649
|
+
projectId: _projectId,
|
|
1650
|
+
cashOutCount: _tokenCountToCashOutForNative,
|
|
1651
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
1652
|
+
minTokensReclaimed: 0,
|
|
1653
|
+
beneficiary: payable(_beneficiary),
|
|
1654
|
+
metadata: new bytes(0)
|
|
1655
|
+
});
|
|
1656
|
+
|
|
1657
|
+
// Cash out USDC from the surplus using only the `_beneficiary`'s tokens needed to clear the USDC
|
|
1658
|
+
// balance.
|
|
1659
|
+
_terminal.cashOutTokensOf({
|
|
1660
|
+
holder: _beneficiary,
|
|
1661
|
+
projectId: _projectId,
|
|
1662
|
+
cashOutCount: _beneficiaryTokenBalance - _tokenCountToCashOutForNative,
|
|
1663
|
+
tokenToReclaim: address(_usdcToken),
|
|
1664
|
+
minTokensReclaimed: 0,
|
|
1665
|
+
beneficiary: payable(_beneficiary),
|
|
1666
|
+
metadata: new bytes(0)
|
|
1667
|
+
});
|
|
1668
|
+
|
|
1669
|
+
_nativeReclaimAmount = mulDiv(
|
|
1670
|
+
mulDiv(
|
|
1671
|
+
_toNative(mulDiv(_usdcPayAmount, 10 ** _NATIVE_DECIMALS, 10 ** _usdcToken.decimals()))
|
|
1672
|
+
+ _nativeSurplus,
|
|
1673
|
+
_tokenCountToCashOutForNative,
|
|
1674
|
+
_tokenSupply
|
|
1675
|
+
),
|
|
1676
|
+
_metadata.cashOutTaxRate
|
|
1677
|
+
+ mulDiv(
|
|
1678
|
+
_tokenCountToCashOutForNative,
|
|
1679
|
+
JBConstants.MAX_CASH_OUT_TAX_RATE - _metadata.cashOutTaxRate,
|
|
1680
|
+
_tokenSupply
|
|
1681
|
+
),
|
|
1682
|
+
JBConstants.MAX_CASH_OUT_TAX_RATE
|
|
1683
|
+
);
|
|
1684
|
+
|
|
1685
|
+
uint256 _usdcReclaimAmount = mulDiv(
|
|
1686
|
+
mulDiv(
|
|
1687
|
+
_usdcPayAmount
|
|
1688
|
+
+ _toUsd(
|
|
1689
|
+
mulDiv(
|
|
1690
|
+
_nativeSurplus - _nativeReclaimAmount,
|
|
1691
|
+
10 ** _usdcToken.decimals(),
|
|
1692
|
+
10 ** _NATIVE_DECIMALS
|
|
1693
|
+
)
|
|
1694
|
+
),
|
|
1695
|
+
_beneficiaryTokenBalance - _tokenCountToCashOutForNative,
|
|
1696
|
+
_tokenSupply - _tokenCountToCashOutForNative
|
|
1697
|
+
),
|
|
1698
|
+
_metadata.cashOutTaxRate
|
|
1699
|
+
+ mulDiv(
|
|
1700
|
+
_beneficiaryTokenBalance - _tokenCountToCashOutForNative,
|
|
1701
|
+
JBConstants.MAX_CASH_OUT_TAX_RATE - _metadata.cashOutTaxRate,
|
|
1702
|
+
_tokenSupply - _tokenCountToCashOutForNative
|
|
1703
|
+
),
|
|
1704
|
+
JBConstants.MAX_CASH_OUT_TAX_RATE
|
|
1705
|
+
);
|
|
1706
|
+
|
|
1707
|
+
assertEq(
|
|
1708
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, address(_usdcToken)),
|
|
1709
|
+
_usdcPayAmount - _usdcReclaimAmount
|
|
1710
|
+
);
|
|
1711
|
+
|
|
1712
|
+
uint256 _usdcFeeAmount = _usdcReclaimAmount * _terminal.FEE() / JBConstants.MAX_FEE;
|
|
1713
|
+
assertEq(_usdcToken.balanceOf(_beneficiary), _usdcReclaimAmount - _usdcFeeAmount);
|
|
1714
|
+
|
|
1715
|
+
// Make sure the fee was paid correctly.
|
|
1716
|
+
assertEq(
|
|
1717
|
+
jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, address(_usdcToken)),
|
|
1718
|
+
_usdcFeeAmount
|
|
1719
|
+
);
|
|
1720
|
+
assertEq(_usdcToken.balanceOf(address(_terminal)), _usdcPayAmount - _usdcReclaimAmount + _usdcFeeAmount);
|
|
1721
|
+
} else {
|
|
1722
|
+
// Reclaim native tokens from the surplus by cashing out all of the `_beneficiary`'s tokens.
|
|
1723
|
+
_terminal.cashOutTokensOf({
|
|
1724
|
+
holder: _beneficiary,
|
|
1725
|
+
projectId: _projectId,
|
|
1726
|
+
cashOutCount: _beneficiaryTokenBalance,
|
|
1727
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
1728
|
+
minTokensReclaimed: 0,
|
|
1729
|
+
beneficiary: payable(_beneficiary),
|
|
1730
|
+
metadata: new bytes(0)
|
|
1731
|
+
});
|
|
1732
|
+
}
|
|
1733
|
+
// Burn the tokens.
|
|
1734
|
+
} else {
|
|
1735
|
+
_terminal.cashOutTokensOf({
|
|
1736
|
+
holder: _beneficiary,
|
|
1737
|
+
projectId: _projectId,
|
|
1738
|
+
cashOutCount: _beneficiaryTokenBalance,
|
|
1739
|
+
tokenToReclaim: address(_usdcToken),
|
|
1740
|
+
minTokensReclaimed: 0,
|
|
1741
|
+
beneficiary: payable(_beneficiary),
|
|
1742
|
+
metadata: new bytes(0)
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
vm.stopPrank();
|
|
1746
|
+
|
|
1747
|
+
// Make sure the balance is adjusted by the reclaim amount.
|
|
1748
|
+
assertEq(
|
|
1749
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
|
|
1750
|
+
_nativeBalance - _nativeReclaimAmount
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
// Project 2 accepts native tokens into `_terminal` and USDC into `_terminal2`.
|
|
1755
|
+
// Project 1 accepts USDC and native token fees into `_terminal`.
|
|
1756
|
+
function testFuzzMultiTerminalAllowance(
|
|
1757
|
+
uint224 _nativeCurrencySurplusAllowance,
|
|
1758
|
+
uint224 _nativeCurrencyPayoutLimit,
|
|
1759
|
+
uint256 _nativePayAmount,
|
|
1760
|
+
uint224 _usdCurrencySurplusAllowance,
|
|
1761
|
+
uint224 _usdCurrencyPayoutLimit,
|
|
1762
|
+
uint256 _usdcPayAmount
|
|
1763
|
+
)
|
|
1764
|
+
public
|
|
1765
|
+
{
|
|
1766
|
+
// Make sure the amount of native tokens to pay is bounded.
|
|
1767
|
+
_nativePayAmount = bound(_nativePayAmount, 0, 1_000_000 * 10 ** _NATIVE_DECIMALS);
|
|
1768
|
+
_usdcPayAmount = bound(_usdcPayAmount, 0, 1_000_000 * 10 ** _usdcToken.decimals());
|
|
1769
|
+
_usdCurrencyPayoutLimit = uint224(
|
|
1770
|
+
bound(_usdCurrencyPayoutLimit, 0, type(uint224).max / 10 ** (_NATIVE_DECIMALS - _usdcToken.decimals()))
|
|
1771
|
+
);
|
|
1772
|
+
|
|
1773
|
+
// Make sure the values don't overflow the registry.
|
|
1774
|
+
unchecked {
|
|
1775
|
+
vm.assume(
|
|
1776
|
+
_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit >= _nativeCurrencySurplusAllowance
|
|
1777
|
+
&& _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit >= _nativeCurrencyPayoutLimit
|
|
1778
|
+
);
|
|
1779
|
+
vm.assume(
|
|
1780
|
+
_usdCurrencySurplusAllowance + _usdCurrencyPayoutLimit >= _usdCurrencySurplusAllowance
|
|
1781
|
+
&& _usdCurrencySurplusAllowance + _usdCurrencyPayoutLimit >= _usdCurrencyPayoutLimit
|
|
1782
|
+
);
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
{
|
|
1786
|
+
// Package up the limits for the given terminal.
|
|
1787
|
+
JBFundAccessLimitGroup[] memory _fundAccessLimitGroup = new JBFundAccessLimitGroup[](2);
|
|
1788
|
+
|
|
1789
|
+
// Specify payout limits.
|
|
1790
|
+
JBCurrencyAmount[] memory _payoutLimits1 = new JBCurrencyAmount[](1);
|
|
1791
|
+
JBCurrencyAmount[] memory _payoutLimits2 = new JBCurrencyAmount[](1);
|
|
1792
|
+
_payoutLimits1[0] = JBCurrencyAmount({
|
|
1793
|
+
amount: _nativeCurrencyPayoutLimit, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1794
|
+
});
|
|
1795
|
+
_payoutLimits2[0] =
|
|
1796
|
+
JBCurrencyAmount({amount: _usdCurrencyPayoutLimit, currency: uint32(uint160(address(_usdcToken)))});
|
|
1797
|
+
|
|
1798
|
+
// Specify surplus allowances.
|
|
1799
|
+
JBCurrencyAmount[] memory _surplusAllowances1 = new JBCurrencyAmount[](1);
|
|
1800
|
+
JBCurrencyAmount[] memory _surplusAllowances2 = new JBCurrencyAmount[](1);
|
|
1801
|
+
_surplusAllowances1[0] = JBCurrencyAmount({
|
|
1802
|
+
amount: _nativeCurrencySurplusAllowance, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1803
|
+
});
|
|
1804
|
+
_surplusAllowances2[0] = JBCurrencyAmount({
|
|
1805
|
+
amount: _usdCurrencySurplusAllowance, currency: uint32(uint160(address(_usdcToken)))
|
|
1806
|
+
});
|
|
1807
|
+
|
|
1808
|
+
_fundAccessLimitGroup[0] = JBFundAccessLimitGroup({
|
|
1809
|
+
terminal: address(_terminal),
|
|
1810
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1811
|
+
payoutLimits: _payoutLimits1,
|
|
1812
|
+
surplusAllowances: _surplusAllowances1
|
|
1813
|
+
});
|
|
1814
|
+
|
|
1815
|
+
_fundAccessLimitGroup[1] = JBFundAccessLimitGroup({
|
|
1816
|
+
terminal: address(_terminal2),
|
|
1817
|
+
token: address(_usdcToken),
|
|
1818
|
+
payoutLimits: _payoutLimits2,
|
|
1819
|
+
surplusAllowances: _surplusAllowances2
|
|
1820
|
+
});
|
|
1821
|
+
|
|
1822
|
+
// Package up the ruleset configuration.
|
|
1823
|
+
JBRulesetConfig[] memory _rulesetConfigurations = new JBRulesetConfig[](1);
|
|
1824
|
+
_rulesetConfigurations[0].mustStartAtOrAfter = 0;
|
|
1825
|
+
_rulesetConfigurations[0].duration = 0;
|
|
1826
|
+
_rulesetConfigurations[0].weight = _weight;
|
|
1827
|
+
_rulesetConfigurations[0].weightCutPercent = 0;
|
|
1828
|
+
_rulesetConfigurations[0].approvalHook = IJBRulesetApprovalHook(address(0));
|
|
1829
|
+
_rulesetConfigurations[0].metadata = _metadata;
|
|
1830
|
+
_rulesetConfigurations[0].splitGroups = new JBSplitGroup[](0);
|
|
1831
|
+
_rulesetConfigurations[0].fundAccessLimitGroups = _fundAccessLimitGroup;
|
|
1832
|
+
|
|
1833
|
+
JBTerminalConfig[] memory _terminalConfigurations1 = new JBTerminalConfig[](1);
|
|
1834
|
+
JBTerminalConfig[] memory _terminalConfigurations2 = new JBTerminalConfig[](2);
|
|
1835
|
+
JBAccountingContext[] memory _tokensToAccept1 = new JBAccountingContext[](2);
|
|
1836
|
+
_tokensToAccept1[0] = JBAccountingContext({
|
|
1837
|
+
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1838
|
+
});
|
|
1839
|
+
_tokensToAccept1[1] = JBAccountingContext({
|
|
1840
|
+
token: address(_usdcToken), decimals: 6, currency: uint32(uint160(address(_usdcToken)))
|
|
1841
|
+
});
|
|
1842
|
+
|
|
1843
|
+
JBAccountingContext[] memory _tokensToAccept2 = new JBAccountingContext[](1);
|
|
1844
|
+
_tokensToAccept2[0] = JBAccountingContext({
|
|
1845
|
+
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
1846
|
+
});
|
|
1847
|
+
|
|
1848
|
+
JBAccountingContext[] memory _tokensToAccept3 = new JBAccountingContext[](1);
|
|
1849
|
+
_tokensToAccept3[0] = JBAccountingContext({
|
|
1850
|
+
token: address(_usdcToken), decimals: 6, currency: uint32(uint160(address(_usdcToken)))
|
|
1851
|
+
});
|
|
1852
|
+
|
|
1853
|
+
// Fee takes USDC and native token in same terminal.
|
|
1854
|
+
_terminalConfigurations1[0] =
|
|
1855
|
+
JBTerminalConfig({terminal: _terminal, accountingContextsToAccept: _tokensToAccept1});
|
|
1856
|
+
_terminalConfigurations2[0] =
|
|
1857
|
+
JBTerminalConfig({terminal: _terminal, accountingContextsToAccept: _tokensToAccept2});
|
|
1858
|
+
_terminalConfigurations2[1] =
|
|
1859
|
+
JBTerminalConfig({terminal: _terminal2, accountingContextsToAccept: _tokensToAccept3});
|
|
1860
|
+
|
|
1861
|
+
// Create a first project to collect fees.
|
|
1862
|
+
_controller.launchProjectFor({
|
|
1863
|
+
owner: _projectOwner, // Random.
|
|
1864
|
+
projectUri: "whatever",
|
|
1865
|
+
rulesetConfigurations: _rulesetConfigurations, // Use the same ruleset configurations.
|
|
1866
|
+
terminalConfigurations: _terminalConfigurations1, // Set terminals to receive fees.
|
|
1867
|
+
memo: ""
|
|
1868
|
+
});
|
|
1869
|
+
|
|
1870
|
+
// Create the project to test.
|
|
1871
|
+
_projectId = _controller.launchProjectFor({
|
|
1872
|
+
owner: _projectOwner,
|
|
1873
|
+
projectUri: "myIPFSHash",
|
|
1874
|
+
rulesetConfigurations: _rulesetConfigurations,
|
|
1875
|
+
terminalConfigurations: _terminalConfigurations2,
|
|
1876
|
+
memo: ""
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
// Add a price feed to convert from native token to USD currencies.
|
|
1881
|
+
{
|
|
1882
|
+
vm.startPrank(_projectOwner);
|
|
1883
|
+
MockPriceFeed _priceFeedNativeUsd = new MockPriceFeed(_USD_PRICE_PER_NATIVE, _PRICE_FEED_DECIMALS);
|
|
1884
|
+
vm.label(address(_priceFeedNativeUsd), "Mock Price Feed Native-USDC");
|
|
1885
|
+
|
|
1886
|
+
_controller.addPriceFeed({
|
|
1887
|
+
projectId: 1,
|
|
1888
|
+
pricingCurrency: uint32(uint160(address(_usdcToken))),
|
|
1889
|
+
unitCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
1890
|
+
feed: _priceFeedNativeUsd
|
|
1891
|
+
});
|
|
1892
|
+
|
|
1893
|
+
_controller.addPriceFeed({
|
|
1894
|
+
projectId: 2,
|
|
1895
|
+
pricingCurrency: uint32(uint160(address(_usdcToken))),
|
|
1896
|
+
unitCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
1897
|
+
feed: _priceFeedNativeUsd
|
|
1898
|
+
});
|
|
1899
|
+
|
|
1900
|
+
vm.stopPrank();
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
// Make a payment to the project to give it a starting balance. Send the tokens to the `_beneficiary`.
|
|
1904
|
+
_terminal.pay{value: _nativePayAmount}({
|
|
1905
|
+
projectId: _projectId,
|
|
1906
|
+
amount: _nativePayAmount,
|
|
1907
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1908
|
+
beneficiary: _beneficiary,
|
|
1909
|
+
minReturnedTokens: 0,
|
|
1910
|
+
memo: "",
|
|
1911
|
+
metadata: new bytes(0)
|
|
1912
|
+
});
|
|
1913
|
+
|
|
1914
|
+
// Make sure the beneficiary got the expected number of tokens from the native token payment.
|
|
1915
|
+
uint256 _beneficiaryTokenBalance = _unreservedPortion(mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS));
|
|
1916
|
+
assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), _beneficiaryTokenBalance);
|
|
1917
|
+
// Mint USDC to this contract.
|
|
1918
|
+
_usdcToken.mint(address(this), _usdcPayAmount);
|
|
1919
|
+
|
|
1920
|
+
// Allow the terminal to spend the USDC.
|
|
1921
|
+
_usdcToken.approve(address(_terminal2), _usdcPayAmount);
|
|
1922
|
+
|
|
1923
|
+
// Make a payment to the project to give it a starting balance. Send the tokens to the `_beneficiary`.
|
|
1924
|
+
_terminal2.pay({
|
|
1925
|
+
projectId: _projectId,
|
|
1926
|
+
amount: _usdcPayAmount,
|
|
1927
|
+
token: address(_usdcToken),
|
|
1928
|
+
beneficiary: _beneficiary,
|
|
1929
|
+
minReturnedTokens: 0,
|
|
1930
|
+
memo: "",
|
|
1931
|
+
metadata: new bytes(0)
|
|
1932
|
+
});
|
|
1933
|
+
|
|
1934
|
+
// Make sure the terminal holds the full native token balance.
|
|
1935
|
+
assertEq(
|
|
1936
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN), _nativePayAmount
|
|
1937
|
+
);
|
|
1938
|
+
// Make sure the USDC is accounted for.
|
|
1939
|
+
assertEq(jbTerminalStore().balanceOf(address(_terminal2), _projectId, address(_usdcToken)), _usdcPayAmount);
|
|
1940
|
+
assertEq(_usdcToken.balanceOf(address(_terminal2)), _usdcPayAmount);
|
|
1941
|
+
|
|
1942
|
+
{
|
|
1943
|
+
// Convert the USD amount to a native token amount, by way of the current weight used for issuance.
|
|
1944
|
+
uint256 _usdWeightedPayAmountConvertedToNative = mulDiv(
|
|
1945
|
+
_usdcPayAmount,
|
|
1946
|
+
_weight,
|
|
1947
|
+
mulDiv(_USD_PRICE_PER_NATIVE, 10 ** _usdcToken.decimals(), 10 ** _PRICE_FEED_DECIMALS)
|
|
1948
|
+
);
|
|
1949
|
+
|
|
1950
|
+
// Make sure the beneficiary got the expected number of tokens from the USDC payment.
|
|
1951
|
+
_beneficiaryTokenBalance += _unreservedPortion(_usdWeightedPayAmountConvertedToNative);
|
|
1952
|
+
assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), _beneficiaryTokenBalance);
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
// Revert if there's no native token allowance.
|
|
1956
|
+
if (_nativeCurrencySurplusAllowance == 0) {
|
|
1957
|
+
vm.expectRevert(
|
|
1958
|
+
abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_InadequateControllerAllowance.selector, 0, 0)
|
|
1959
|
+
);
|
|
1960
|
+
} else if (_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit > _nativePayAmount) {
|
|
1961
|
+
vm.expectRevert(
|
|
1962
|
+
abi.encodeWithSelector(
|
|
1963
|
+
JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
|
|
1964
|
+
_nativeCurrencySurplusAllowance,
|
|
1965
|
+
_nativeCurrencyPayoutLimit > _nativePayAmount ? 0 : _nativePayAmount - _nativeCurrencyPayoutLimit
|
|
1966
|
+
)
|
|
1967
|
+
);
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
// Use the full native token surplus allowance.
|
|
1971
|
+
vm.prank(_projectOwner);
|
|
1972
|
+
_terminal.useAllowanceOf({
|
|
1973
|
+
projectId: _projectId,
|
|
1974
|
+
amount: _nativeCurrencySurplusAllowance,
|
|
1975
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
1976
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
1977
|
+
minTokensPaidOut: 0,
|
|
1978
|
+
beneficiary: payable(_beneficiary),
|
|
1979
|
+
feeBeneficiary: payable(_projectOwner),
|
|
1980
|
+
memo: "MEMO"
|
|
1981
|
+
});
|
|
1982
|
+
|
|
1983
|
+
// Keep a reference to the beneficiary's native token balance.
|
|
1984
|
+
uint256 _beneficiaryNativeBalance;
|
|
1985
|
+
|
|
1986
|
+
// Check the collected balance if one is expected.
|
|
1987
|
+
if (_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit <= _nativePayAmount) {
|
|
1988
|
+
// Make sure the beneficiary received the funds and that they are no longer in the terminal.
|
|
1989
|
+
_beneficiaryNativeBalance = _nativeCurrencySurplusAllowance
|
|
1990
|
+
- mulDiv(_nativeCurrencySurplusAllowance, _terminal.FEE(), JBConstants.MAX_FEE);
|
|
1991
|
+
assertEq(_beneficiary.balance, _beneficiaryNativeBalance);
|
|
1992
|
+
assertEq(
|
|
1993
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
|
|
1994
|
+
_nativePayAmount - _nativeCurrencySurplusAllowance
|
|
1995
|
+
);
|
|
1996
|
+
|
|
1997
|
+
// Make sure the fee was paid correctly.
|
|
1998
|
+
assertEq(
|
|
1999
|
+
jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
|
|
2000
|
+
_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance
|
|
2001
|
+
);
|
|
2002
|
+
assertEq(address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance);
|
|
2003
|
+
|
|
2004
|
+
// Make sure the beneficiary got the expected number of tokens.
|
|
2005
|
+
assertEq(
|
|
2006
|
+
_tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID),
|
|
2007
|
+
_unreservedPortion(
|
|
2008
|
+
mulDiv(_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance, _weight, 10 ** _NATIVE_DECIMALS)
|
|
2009
|
+
)
|
|
2010
|
+
);
|
|
2011
|
+
} else {
|
|
2012
|
+
// Set the native token surplus allowance to 0 if it wasn't used.
|
|
2013
|
+
_nativeCurrencySurplusAllowance = 0;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
// Revert if there's no native token allowance.
|
|
2017
|
+
if (_usdCurrencySurplusAllowance == 0) {
|
|
2018
|
+
vm.expectRevert(
|
|
2019
|
+
abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_InadequateControllerAllowance.selector, 0, 0)
|
|
2020
|
+
);
|
|
2021
|
+
// Revert if the USD surplus allowance resolved to native tokens is greater than 0, and there is sufficient
|
|
2022
|
+
// surplus to pull from including what was already pulled from.
|
|
2023
|
+
} else if (_usdCurrencySurplusAllowance + _usdCurrencyPayoutLimit > _usdcPayAmount) {
|
|
2024
|
+
vm.expectRevert(
|
|
2025
|
+
abi.encodeWithSelector(
|
|
2026
|
+
JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
|
|
2027
|
+
_usdCurrencySurplusAllowance,
|
|
2028
|
+
_usdCurrencyPayoutLimit > _usdcPayAmount ? 0 : _usdcPayAmount - _usdCurrencyPayoutLimit
|
|
2029
|
+
)
|
|
2030
|
+
);
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
// Use the full native token surplus allowance.
|
|
2034
|
+
vm.prank(_projectOwner);
|
|
2035
|
+
_terminal2.useAllowanceOf({
|
|
2036
|
+
projectId: _projectId,
|
|
2037
|
+
amount: _usdCurrencySurplusAllowance,
|
|
2038
|
+
currency: uint32(uint160(address(_usdcToken))),
|
|
2039
|
+
token: address(_usdcToken),
|
|
2040
|
+
minTokensPaidOut: 0,
|
|
2041
|
+
beneficiary: payable(_beneficiary),
|
|
2042
|
+
feeBeneficiary: payable(_projectOwner),
|
|
2043
|
+
memo: "MEMO"
|
|
2044
|
+
});
|
|
2045
|
+
|
|
2046
|
+
// Keep a reference to the beneficiary's USDC balance.
|
|
2047
|
+
uint256 _beneficiaryUsdcBalance;
|
|
2048
|
+
|
|
2049
|
+
// Check the collected balance if one is expected.
|
|
2050
|
+
if (_usdCurrencySurplusAllowance + _usdCurrencyPayoutLimit <= _usdcPayAmount) {
|
|
2051
|
+
// Make sure the beneficiary received the funds and that they are no longer in the terminal.
|
|
2052
|
+
_beneficiaryUsdcBalance += _usdCurrencySurplusAllowance
|
|
2053
|
+
- mulDiv(_usdCurrencySurplusAllowance, _terminal.FEE(), JBConstants.MAX_FEE);
|
|
2054
|
+
assertEq(_usdcToken.balanceOf(_beneficiary), _beneficiaryUsdcBalance);
|
|
2055
|
+
assertEq(
|
|
2056
|
+
jbTerminalStore().balanceOf(address(_terminal2), _projectId, address(_usdcToken)),
|
|
2057
|
+
_usdcPayAmount - _usdCurrencySurplusAllowance
|
|
2058
|
+
);
|
|
2059
|
+
|
|
2060
|
+
// Make sure the fee was paid correctly.
|
|
2061
|
+
assertEq(
|
|
2062
|
+
jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, address(_usdcToken)),
|
|
2063
|
+
_usdCurrencySurplusAllowance - _beneficiaryUsdcBalance
|
|
2064
|
+
);
|
|
2065
|
+
assertEq(_usdcToken.balanceOf(address(_terminal2)), _usdcPayAmount - _usdCurrencySurplusAllowance);
|
|
2066
|
+
assertEq(_usdcToken.balanceOf(address(_terminal)), _usdCurrencySurplusAllowance - _beneficiaryUsdcBalance);
|
|
2067
|
+
|
|
2068
|
+
// Make sure the beneficiary got the expected number of tokens.
|
|
2069
|
+
assertEq(
|
|
2070
|
+
_tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID),
|
|
2071
|
+
_unreservedPortion(
|
|
2072
|
+
mulDiv(
|
|
2073
|
+
_nativeCurrencySurplusAllowance
|
|
2074
|
+
+ _toNative(
|
|
2075
|
+
mulDiv(
|
|
2076
|
+
_usdCurrencySurplusAllowance, 10 ** _NATIVE_DECIMALS, 10 ** _usdcToken.decimals()
|
|
2077
|
+
)
|
|
2078
|
+
) - _beneficiaryNativeBalance
|
|
2079
|
+
- _toNative(
|
|
2080
|
+
mulDiv(_beneficiaryUsdcBalance, 10 ** _NATIVE_DECIMALS, 10 ** _usdcToken.decimals())
|
|
2081
|
+
),
|
|
2082
|
+
_weight,
|
|
2083
|
+
10 ** _NATIVE_DECIMALS
|
|
2084
|
+
)
|
|
2085
|
+
)
|
|
2086
|
+
);
|
|
2087
|
+
} else {
|
|
2088
|
+
// Set the native token surplus allowance to 0 if it wasn't used.
|
|
2089
|
+
_usdCurrencySurplusAllowance = 0;
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
// Payout limits
|
|
2093
|
+
{
|
|
2094
|
+
// Revert if the payout limit is greater than the balance.
|
|
2095
|
+
if (_nativeCurrencyPayoutLimit > _nativePayAmount) {
|
|
2096
|
+
vm.expectRevert(
|
|
2097
|
+
abi.encodeWithSelector(
|
|
2098
|
+
JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
|
|
2099
|
+
_nativeCurrencyPayoutLimit,
|
|
2100
|
+
_nativePayAmount
|
|
2101
|
+
)
|
|
2102
|
+
);
|
|
2103
|
+
// Revert if there's no payout limit.
|
|
2104
|
+
} else if (_nativeCurrencyPayoutLimit == 0) {
|
|
2105
|
+
vm.expectRevert(
|
|
2106
|
+
abi.encodeWithSelector(
|
|
2107
|
+
JBTerminalStore.JBTerminalStore_InadequateControllerPayoutLimit.selector, 0, 0
|
|
2108
|
+
)
|
|
2109
|
+
);
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
// Pay out native tokens up to the payout limit. Since `splits[]` is empty, everything goes to project
|
|
2113
|
+
// owner.
|
|
2114
|
+
_terminal.sendPayoutsOf({
|
|
2115
|
+
projectId: _projectId,
|
|
2116
|
+
amount: _nativeCurrencyPayoutLimit,
|
|
2117
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
2118
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
2119
|
+
minTokensPaidOut: 0
|
|
2120
|
+
});
|
|
2121
|
+
|
|
2122
|
+
uint256 _projectOwnerNativeBalance;
|
|
2123
|
+
|
|
2124
|
+
// Check the received payout if one is expected.
|
|
2125
|
+
if (_nativeCurrencyPayoutLimit <= _nativePayAmount && _nativeCurrencyPayoutLimit != 0) {
|
|
2126
|
+
// Make sure the project owner received the funds that were paid out.
|
|
2127
|
+
_projectOwnerNativeBalance =
|
|
2128
|
+
_nativeCurrencyPayoutLimit - _nativeCurrencyPayoutLimit * _terminal.FEE() / JBConstants.MAX_FEE;
|
|
2129
|
+
assertEq(_projectOwner.balance, _projectOwnerNativeBalance);
|
|
2130
|
+
assertEq(
|
|
2131
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
|
|
2132
|
+
_nativePayAmount - _nativeCurrencySurplusAllowance - _nativeCurrencyPayoutLimit
|
|
2133
|
+
);
|
|
2134
|
+
|
|
2135
|
+
// Make sure the fee was paid correctly.
|
|
2136
|
+
assertEq(
|
|
2137
|
+
jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
|
|
2138
|
+
_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance + _nativeCurrencyPayoutLimit
|
|
2139
|
+
- _projectOwnerNativeBalance
|
|
2140
|
+
);
|
|
2141
|
+
assertEq(
|
|
2142
|
+
address(_terminal).balance,
|
|
2143
|
+
_nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
|
|
2144
|
+
);
|
|
2145
|
+
|
|
2146
|
+
// // // Make sure the project owner got the expected number of tokens.
|
|
2147
|
+
// assertEq(
|
|
2148
|
+
// _unreservedPortion(mulDiv(_nativeCurrencySurplusAllowance + _toNative(_usdCurrencySurplusAllowance) -
|
|
2149
|
+
// _beneficiaryNativeBalance + _nativeCurrencyPayoutLimit - _projectOwnerNativeBalance, _weight, 10
|
|
2150
|
+
// ** _NATIVE_DECIMALS)), _tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID));
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
// Revert if the payout limit is greater than the balance.
|
|
2154
|
+
if (_usdCurrencyPayoutLimit > _usdcPayAmount) {
|
|
2155
|
+
vm.expectRevert(
|
|
2156
|
+
abi.encodeWithSelector(
|
|
2157
|
+
JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
|
|
2158
|
+
_usdCurrencyPayoutLimit,
|
|
2159
|
+
_usdcPayAmount
|
|
2160
|
+
)
|
|
2161
|
+
);
|
|
2162
|
+
// Revert if there's no payout limit.
|
|
2163
|
+
} else if (_usdCurrencyPayoutLimit == 0) {
|
|
2164
|
+
vm.expectRevert(
|
|
2165
|
+
abi.encodeWithSelector(
|
|
2166
|
+
JBTerminalStore.JBTerminalStore_InadequateControllerPayoutLimit.selector, 0, 0
|
|
2167
|
+
)
|
|
2168
|
+
);
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
// Pay out native tokens up to the payout limit. Since `splits[]` is empty, everything goes to project
|
|
2172
|
+
// owner.
|
|
2173
|
+
_terminal2.sendPayoutsOf({
|
|
2174
|
+
projectId: _projectId,
|
|
2175
|
+
amount: _usdCurrencyPayoutLimit,
|
|
2176
|
+
currency: uint32(uint160(address(_usdcToken))),
|
|
2177
|
+
token: address(_usdcToken),
|
|
2178
|
+
minTokensPaidOut: 0
|
|
2179
|
+
});
|
|
2180
|
+
|
|
2181
|
+
uint256 _projectOwnerUsdcBalance;
|
|
2182
|
+
|
|
2183
|
+
// Check the received payout if one is expected.
|
|
2184
|
+
if (_usdCurrencyPayoutLimit <= _usdcPayAmount && _usdCurrencyPayoutLimit != 0) {
|
|
2185
|
+
// Make sure the project owner received the funds that were paid out.
|
|
2186
|
+
_projectOwnerUsdcBalance =
|
|
2187
|
+
_usdCurrencyPayoutLimit - _usdCurrencyPayoutLimit * _terminal.FEE() / JBConstants.MAX_FEE;
|
|
2188
|
+
assertEq(_usdcToken.balanceOf(_projectOwner), _projectOwnerUsdcBalance);
|
|
2189
|
+
assertEq(
|
|
2190
|
+
jbTerminalStore().balanceOf(address(_terminal2), _projectId, address(_usdcToken)),
|
|
2191
|
+
_usdcPayAmount - _usdCurrencySurplusAllowance - _usdCurrencyPayoutLimit
|
|
2192
|
+
);
|
|
2193
|
+
|
|
2194
|
+
// Make sure the fee was paid correctly.
|
|
2195
|
+
assertEq(
|
|
2196
|
+
jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, address(_usdcToken)),
|
|
2197
|
+
_usdCurrencySurplusAllowance - _beneficiaryUsdcBalance + _usdCurrencyPayoutLimit
|
|
2198
|
+
- _projectOwnerUsdcBalance
|
|
2199
|
+
);
|
|
2200
|
+
assertEq(
|
|
2201
|
+
_usdcToken.balanceOf(address(_terminal2)),
|
|
2202
|
+
_usdcPayAmount - _usdCurrencySurplusAllowance - _usdCurrencyPayoutLimit
|
|
2203
|
+
);
|
|
2204
|
+
assertEq(
|
|
2205
|
+
_usdcToken.balanceOf(address(_terminal)),
|
|
2206
|
+
_usdCurrencySurplusAllowance + _usdCurrencyPayoutLimit - _beneficiaryUsdcBalance
|
|
2207
|
+
- _projectOwnerUsdcBalance
|
|
2208
|
+
);
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
// Keep a reference to the remaining native token surplus.
|
|
2213
|
+
uint256 _nativeSurplus = _nativeCurrencyPayoutLimit + _nativeCurrencySurplusAllowance >= _nativePayAmount
|
|
2214
|
+
? 0
|
|
2215
|
+
: _nativePayAmount - _nativeCurrencyPayoutLimit - _nativeCurrencySurplusAllowance;
|
|
2216
|
+
|
|
2217
|
+
uint256 _usdcSurplus = _usdCurrencyPayoutLimit + _usdCurrencySurplusAllowance >= _usdcPayAmount
|
|
2218
|
+
? 0
|
|
2219
|
+
: _usdcPayAmount - _usdCurrencyPayoutLimit - _usdCurrencySurplusAllowance;
|
|
2220
|
+
|
|
2221
|
+
// Keep a reference to the remaining native token balance.
|
|
2222
|
+
uint256 _usdcBalanceInTerminal = _usdcPayAmount - _usdCurrencySurplusAllowance;
|
|
2223
|
+
|
|
2224
|
+
if (_usdCurrencyPayoutLimit <= _usdcPayAmount) {
|
|
2225
|
+
_usdcBalanceInTerminal -= _usdCurrencyPayoutLimit;
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
assertEq(_usdcToken.balanceOf(address(_terminal2)), _usdcBalanceInTerminal);
|
|
2229
|
+
|
|
2230
|
+
// Make sure the total token supply is correct.
|
|
2231
|
+
assertEq(
|
|
2232
|
+
jbController().totalTokenSupplyWithReservedTokensOf(_projectId),
|
|
2233
|
+
mulDiv(
|
|
2234
|
+
_beneficiaryTokenBalance,
|
|
2235
|
+
JBConstants.MAX_RESERVED_PERCENT,
|
|
2236
|
+
JBConstants.MAX_RESERVED_PERCENT - _metadata.reservedPercent
|
|
2237
|
+
)
|
|
2238
|
+
);
|
|
2239
|
+
|
|
2240
|
+
// Keep a reference to the amount of native tokens being reclaimed.
|
|
2241
|
+
uint256 _nativeReclaimAmount;
|
|
2242
|
+
|
|
2243
|
+
vm.startPrank(_beneficiary);
|
|
2244
|
+
|
|
2245
|
+
// If there's native token surplus.
|
|
2246
|
+
if (_nativeSurplus + _toNative(mulDiv(_usdcSurplus, 10 ** _NATIVE_DECIMALS, 10 ** _usdcToken.decimals())) > 0) {
|
|
2247
|
+
// Get the expected amount reclaimed.
|
|
2248
|
+
_nativeReclaimAmount = mulDiv(
|
|
2249
|
+
mulDiv(
|
|
2250
|
+
_nativeSurplus
|
|
2251
|
+
+ _toNative(mulDiv(_usdcSurplus, 10 ** _NATIVE_DECIMALS, 10 ** _usdcToken.decimals())),
|
|
2252
|
+
_beneficiaryTokenBalance,
|
|
2253
|
+
mulDiv(
|
|
2254
|
+
_beneficiaryTokenBalance,
|
|
2255
|
+
JBConstants.MAX_RESERVED_PERCENT,
|
|
2256
|
+
JBConstants.MAX_RESERVED_PERCENT - _metadata.reservedPercent
|
|
2257
|
+
)
|
|
2258
|
+
),
|
|
2259
|
+
_metadata.cashOutTaxRate
|
|
2260
|
+
+ mulDiv(
|
|
2261
|
+
_beneficiaryTokenBalance,
|
|
2262
|
+
JBConstants.MAX_CASH_OUT_TAX_RATE - _metadata.cashOutTaxRate,
|
|
2263
|
+
mulDiv(
|
|
2264
|
+
_beneficiaryTokenBalance,
|
|
2265
|
+
JBConstants.MAX_RESERVED_PERCENT,
|
|
2266
|
+
JBConstants.MAX_RESERVED_PERCENT - _metadata.reservedPercent
|
|
2267
|
+
)
|
|
2268
|
+
),
|
|
2269
|
+
JBConstants.MAX_CASH_OUT_TAX_RATE
|
|
2270
|
+
);
|
|
2271
|
+
|
|
2272
|
+
// If there is more to reclaim than there are native tokens in the tank.
|
|
2273
|
+
if (_nativeReclaimAmount > _nativeSurplus) {
|
|
2274
|
+
uint256 _usdcReclaimAmount;
|
|
2275
|
+
{
|
|
2276
|
+
// Keep a reference to the amount of project tokens to cash out for native tokens, a proportion of
|
|
2277
|
+
// available native token surplus.
|
|
2278
|
+
uint256 _tokenCountToCashOutForNative = mulDiv(
|
|
2279
|
+
_beneficiaryTokenBalance,
|
|
2280
|
+
_nativeSurplus,
|
|
2281
|
+
_nativeSurplus
|
|
2282
|
+
+ _toNative(mulDiv(_usdcSurplus, 10 ** _NATIVE_DECIMALS, 10 ** _usdcToken.decimals()))
|
|
2283
|
+
);
|
|
2284
|
+
uint256 _tokenSupply = mulDiv(
|
|
2285
|
+
_beneficiaryTokenBalance,
|
|
2286
|
+
JBConstants.MAX_RESERVED_PERCENT,
|
|
2287
|
+
JBConstants.MAX_RESERVED_PERCENT - _metadata.reservedPercent
|
|
2288
|
+
);
|
|
2289
|
+
// Cash out native tokens from the surplus using only the `_beneficiary`'s tokens needed to clear
|
|
2290
|
+
// the
|
|
2291
|
+
// native token balance.
|
|
2292
|
+
_terminal.cashOutTokensOf({
|
|
2293
|
+
holder: _beneficiary,
|
|
2294
|
+
projectId: _projectId,
|
|
2295
|
+
cashOutCount: _tokenCountToCashOutForNative,
|
|
2296
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
2297
|
+
minTokensReclaimed: 0,
|
|
2298
|
+
beneficiary: payable(_beneficiary),
|
|
2299
|
+
metadata: new bytes(0)
|
|
2300
|
+
});
|
|
2301
|
+
|
|
2302
|
+
// Cash out USDC from the surplus using only the `_beneficiary`'s tokens needed to clear the USDC
|
|
2303
|
+
// balance.
|
|
2304
|
+
_terminal2.cashOutTokensOf({
|
|
2305
|
+
holder: _beneficiary,
|
|
2306
|
+
projectId: _projectId,
|
|
2307
|
+
cashOutCount: _beneficiaryTokenBalance - _tokenCountToCashOutForNative,
|
|
2308
|
+
tokenToReclaim: address(_usdcToken),
|
|
2309
|
+
minTokensReclaimed: 0,
|
|
2310
|
+
beneficiary: payable(_beneficiary),
|
|
2311
|
+
metadata: new bytes(0)
|
|
2312
|
+
});
|
|
2313
|
+
|
|
2314
|
+
_nativeReclaimAmount = mulDiv(
|
|
2315
|
+
mulDiv(
|
|
2316
|
+
_nativeSurplus
|
|
2317
|
+
+ _toNative(mulDiv(_usdcSurplus, 10 ** _NATIVE_DECIMALS, 10 ** _usdcToken.decimals())),
|
|
2318
|
+
_tokenCountToCashOutForNative,
|
|
2319
|
+
_tokenSupply
|
|
2320
|
+
),
|
|
2321
|
+
_metadata.cashOutTaxRate
|
|
2322
|
+
+ mulDiv(
|
|
2323
|
+
_tokenCountToCashOutForNative,
|
|
2324
|
+
JBConstants.MAX_CASH_OUT_TAX_RATE - _metadata.cashOutTaxRate,
|
|
2325
|
+
_tokenSupply
|
|
2326
|
+
),
|
|
2327
|
+
JBConstants.MAX_CASH_OUT_TAX_RATE
|
|
2328
|
+
);
|
|
2329
|
+
_usdcReclaimAmount = mulDiv(
|
|
2330
|
+
mulDiv(
|
|
2331
|
+
_usdcSurplus
|
|
2332
|
+
+ _toUsd(
|
|
2333
|
+
mulDiv(
|
|
2334
|
+
_nativeSurplus - _nativeReclaimAmount,
|
|
2335
|
+
10 ** _usdcToken.decimals(),
|
|
2336
|
+
10 ** _NATIVE_DECIMALS
|
|
2337
|
+
)
|
|
2338
|
+
),
|
|
2339
|
+
_beneficiaryTokenBalance - _tokenCountToCashOutForNative,
|
|
2340
|
+
_tokenSupply - _tokenCountToCashOutForNative
|
|
2341
|
+
),
|
|
2342
|
+
_metadata.cashOutTaxRate
|
|
2343
|
+
+ mulDiv(
|
|
2344
|
+
_beneficiaryTokenBalance - _tokenCountToCashOutForNative,
|
|
2345
|
+
JBConstants.MAX_CASH_OUT_TAX_RATE - _metadata.cashOutTaxRate,
|
|
2346
|
+
_tokenSupply - _tokenCountToCashOutForNative
|
|
2347
|
+
),
|
|
2348
|
+
JBConstants.MAX_CASH_OUT_TAX_RATE
|
|
2349
|
+
);
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
assertEq(
|
|
2353
|
+
jbTerminalStore().balanceOf(address(_terminal2), _projectId, address(_usdcToken)),
|
|
2354
|
+
_usdcSurplus - _usdcReclaimAmount
|
|
2355
|
+
);
|
|
2356
|
+
|
|
2357
|
+
uint256 _usdcFeeAmount = _usdcReclaimAmount * _terminal.FEE() / JBConstants.MAX_FEE;
|
|
2358
|
+
|
|
2359
|
+
_beneficiaryUsdcBalance += _usdcReclaimAmount - _usdcFeeAmount;
|
|
2360
|
+
assertEq(_usdcToken.balanceOf(_beneficiary), _beneficiaryUsdcBalance);
|
|
2361
|
+
|
|
2362
|
+
assertEq(_usdcToken.balanceOf(address(_terminal2)), _usdcBalanceInTerminal - _usdcReclaimAmount);
|
|
2363
|
+
|
|
2364
|
+
// Only the fees left.
|
|
2365
|
+
assertEq(
|
|
2366
|
+
_usdcToken.balanceOf(address(_terminal)),
|
|
2367
|
+
_usdcPayAmount - _usdcToken.balanceOf(address(_terminal2)) - _usdcToken.balanceOf(_beneficiary)
|
|
2368
|
+
- _usdcToken.balanceOf(_projectOwner)
|
|
2369
|
+
);
|
|
2370
|
+
} else {
|
|
2371
|
+
// Reclaim native tokens from the surplus by cashing out all of the `_beneficiary`'s tokens.
|
|
2372
|
+
_terminal.cashOutTokensOf({
|
|
2373
|
+
holder: _beneficiary,
|
|
2374
|
+
projectId: _projectId,
|
|
2375
|
+
cashOutCount: _beneficiaryTokenBalance,
|
|
2376
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
2377
|
+
minTokensReclaimed: 0,
|
|
2378
|
+
beneficiary: payable(_beneficiary),
|
|
2379
|
+
metadata: new bytes(0)
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
// Burn the tokens.
|
|
2383
|
+
} else {
|
|
2384
|
+
_terminal2.cashOutTokensOf({
|
|
2385
|
+
holder: _beneficiary,
|
|
2386
|
+
projectId: _projectId,
|
|
2387
|
+
cashOutCount: _beneficiaryTokenBalance,
|
|
2388
|
+
tokenToReclaim: address(_usdcToken),
|
|
2389
|
+
minTokensReclaimed: 0,
|
|
2390
|
+
beneficiary: payable(_beneficiary),
|
|
2391
|
+
metadata: new bytes(0)
|
|
2392
|
+
});
|
|
2393
|
+
}
|
|
2394
|
+
vm.stopPrank();
|
|
2395
|
+
|
|
2396
|
+
// Keep a reference to the remaining native token balance.
|
|
2397
|
+
uint256 _projectNativeBalance = _nativePayAmount - _nativeCurrencySurplusAllowance;
|
|
2398
|
+
if (_nativeCurrencyPayoutLimit <= _nativePayAmount) {
|
|
2399
|
+
_projectNativeBalance -= _nativeCurrencyPayoutLimit;
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
// Make sure the balance is adjusted by the reclaim amount.
|
|
2403
|
+
assertEq(
|
|
2404
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
|
|
2405
|
+
_projectNativeBalance - _nativeReclaimAmount
|
|
2406
|
+
);
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
// Tests that recent changes to JBTerminalStore are safe wrt reetrency via useAllowanceOf.
|
|
2410
|
+
function testNativeAllowanceReentry() public {
|
|
2411
|
+
// Hardcode values to use.
|
|
2412
|
+
uint224 _nativeCurrencyPayoutLimit = uint224(10 * 10 ** _NATIVE_DECIMALS);
|
|
2413
|
+
uint224 _nativeCurrencySurplusAllowance = uint224(5 * 10 ** _NATIVE_DECIMALS);
|
|
2414
|
+
|
|
2415
|
+
MaliciousAllowanceBeneficiary maliciousOwner = new MaliciousAllowanceBeneficiary();
|
|
2416
|
+
|
|
2417
|
+
// Package up the limits for the given terminal.
|
|
2418
|
+
JBFundAccessLimitGroup[] memory _fundAccessLimitGroup = new JBFundAccessLimitGroup[](1);
|
|
2419
|
+
{
|
|
2420
|
+
// Specify a payout limit.
|
|
2421
|
+
JBCurrencyAmount[] memory _payoutLimits = new JBCurrencyAmount[](1);
|
|
2422
|
+
_payoutLimits[0] = JBCurrencyAmount({
|
|
2423
|
+
amount: _nativeCurrencyPayoutLimit, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
2424
|
+
});
|
|
2425
|
+
|
|
2426
|
+
// Specify a surplus allowance.
|
|
2427
|
+
JBCurrencyAmount[] memory _surplusAllowances = new JBCurrencyAmount[](1);
|
|
2428
|
+
_surplusAllowances[0] = JBCurrencyAmount({
|
|
2429
|
+
amount: _nativeCurrencySurplusAllowance, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
2430
|
+
});
|
|
2431
|
+
|
|
2432
|
+
_fundAccessLimitGroup[0] = JBFundAccessLimitGroup({
|
|
2433
|
+
terminal: address(_terminal),
|
|
2434
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
2435
|
+
payoutLimits: _payoutLimits,
|
|
2436
|
+
surplusAllowances: _surplusAllowances
|
|
2437
|
+
});
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
{
|
|
2441
|
+
// Package up the ruleset configuration.
|
|
2442
|
+
JBRulesetConfig[] memory _rulesetConfigurations = new JBRulesetConfig[](1);
|
|
2443
|
+
_rulesetConfigurations[0].mustStartAtOrAfter = 0;
|
|
2444
|
+
_rulesetConfigurations[0].duration = 0;
|
|
2445
|
+
_rulesetConfigurations[0].weight = _weight;
|
|
2446
|
+
_rulesetConfigurations[0].weightCutPercent = 0;
|
|
2447
|
+
_rulesetConfigurations[0].approvalHook = IJBRulesetApprovalHook(address(0));
|
|
2448
|
+
_rulesetConfigurations[0].metadata = _metadata;
|
|
2449
|
+
_rulesetConfigurations[0].splitGroups = new JBSplitGroup[](0);
|
|
2450
|
+
_rulesetConfigurations[0].fundAccessLimitGroups = _fundAccessLimitGroup;
|
|
2451
|
+
|
|
2452
|
+
JBTerminalConfig[] memory _terminalConfigurations = new JBTerminalConfig[](1);
|
|
2453
|
+
JBAccountingContext[] memory _tokensToAccept = new JBAccountingContext[](1);
|
|
2454
|
+
_tokensToAccept[0] = JBAccountingContext({
|
|
2455
|
+
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
2456
|
+
});
|
|
2457
|
+
|
|
2458
|
+
_terminalConfigurations[0] =
|
|
2459
|
+
JBTerminalConfig({terminal: _terminal, accountingContextsToAccept: _tokensToAccept});
|
|
2460
|
+
|
|
2461
|
+
// Create a first project to collect fees.
|
|
2462
|
+
_controller.launchProjectFor({
|
|
2463
|
+
owner: address(420), // Random.
|
|
2464
|
+
projectUri: "whatever",
|
|
2465
|
+
rulesetConfigurations: _rulesetConfigurations,
|
|
2466
|
+
terminalConfigurations: _terminalConfigurations, // Set terminals to receive fees.
|
|
2467
|
+
memo: ""
|
|
2468
|
+
});
|
|
2469
|
+
|
|
2470
|
+
// Create the project to test.
|
|
2471
|
+
_projectId = _controller.launchProjectFor({
|
|
2472
|
+
owner: address(maliciousOwner),
|
|
2473
|
+
projectUri: "myIPFSHash",
|
|
2474
|
+
rulesetConfigurations: _rulesetConfigurations,
|
|
2475
|
+
terminalConfigurations: _terminalConfigurations,
|
|
2476
|
+
memo: ""
|
|
2477
|
+
});
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
// Get a reference to the amount being paid.
|
|
2481
|
+
// The amount being paid is the payout limit plus two times the surplus allowance.
|
|
2482
|
+
uint256 _nativePayAmount = _nativeCurrencyPayoutLimit + (5 * _nativeCurrencySurplusAllowance);
|
|
2483
|
+
|
|
2484
|
+
// Pay the project such that the `_beneficiary` receives project tokens.
|
|
2485
|
+
_terminal.pay{value: _nativePayAmount}({
|
|
2486
|
+
projectId: _projectId,
|
|
2487
|
+
amount: _nativePayAmount,
|
|
2488
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
2489
|
+
beneficiary: _beneficiary,
|
|
2490
|
+
minReturnedTokens: 0,
|
|
2491
|
+
memo: "",
|
|
2492
|
+
metadata: new bytes(0)
|
|
2493
|
+
});
|
|
2494
|
+
|
|
2495
|
+
// Make sure the beneficiary got the expected number of tokens.
|
|
2496
|
+
uint256 _beneficiaryTokenBalance = mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
|
|
2497
|
+
* _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT;
|
|
2498
|
+
assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), _beneficiaryTokenBalance);
|
|
2499
|
+
|
|
2500
|
+
// Make sure the terminal holds the full native token balance.
|
|
2501
|
+
assertEq(
|
|
2502
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN), _nativePayAmount
|
|
2503
|
+
);
|
|
2504
|
+
|
|
2505
|
+
// Attempt to use more than surplus allowance via malicious beneficiary contract.
|
|
2506
|
+
// This will fail via the mock contract itself, with an expected revert therein corresponding to the amounts.
|
|
2507
|
+
// See {MockMaliciousAllowanceBeneficiary}
|
|
2508
|
+
vm.prank(address(maliciousOwner));
|
|
2509
|
+
_terminal.useAllowanceOf({
|
|
2510
|
+
projectId: _projectId,
|
|
2511
|
+
amount: _nativeCurrencySurplusAllowance,
|
|
2512
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
2513
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
2514
|
+
minTokensPaidOut: 0,
|
|
2515
|
+
beneficiary: payable(address(maliciousOwner)),
|
|
2516
|
+
feeBeneficiary: payable(_projectOwner),
|
|
2517
|
+
memo: "MEMO"
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
// Tests that recent changes to JBTerminalStore are safe wrt reetrency via sendPayoutsOf.
|
|
2522
|
+
function testNativePayoutReentry() public {
|
|
2523
|
+
// Hardcode values to use.
|
|
2524
|
+
uint224 _nativeCurrencyPayoutLimit = uint224(10 * 10 ** _NATIVE_DECIMALS);
|
|
2525
|
+
uint224 _nativeCurrencySurplusAllowance = uint224(5 * 10 ** _NATIVE_DECIMALS);
|
|
2526
|
+
|
|
2527
|
+
MaliciousPayoutBeneficiary maliciousPayoutCaller = new MaliciousPayoutBeneficiary();
|
|
2528
|
+
|
|
2529
|
+
// Package up the limits for the given terminal.
|
|
2530
|
+
JBFundAccessLimitGroup[] memory _fundAccessLimitGroup = new JBFundAccessLimitGroup[](1);
|
|
2531
|
+
{
|
|
2532
|
+
// Specify a payout limit.
|
|
2533
|
+
JBCurrencyAmount[] memory _payoutLimits = new JBCurrencyAmount[](1);
|
|
2534
|
+
_payoutLimits[0] = JBCurrencyAmount({
|
|
2535
|
+
amount: _nativeCurrencyPayoutLimit, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
2536
|
+
});
|
|
2537
|
+
|
|
2538
|
+
// Specify a surplus allowance.
|
|
2539
|
+
JBCurrencyAmount[] memory _surplusAllowances = new JBCurrencyAmount[](1);
|
|
2540
|
+
_surplusAllowances[0] = JBCurrencyAmount({
|
|
2541
|
+
amount: _nativeCurrencySurplusAllowance, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
2542
|
+
});
|
|
2543
|
+
|
|
2544
|
+
_fundAccessLimitGroup[0] = JBFundAccessLimitGroup({
|
|
2545
|
+
terminal: address(_terminal),
|
|
2546
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
2547
|
+
payoutLimits: _payoutLimits,
|
|
2548
|
+
surplusAllowances: _surplusAllowances
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
{
|
|
2553
|
+
// Package up the ruleset configuration.
|
|
2554
|
+
JBRulesetConfig[] memory _rulesetConfigurations = new JBRulesetConfig[](1);
|
|
2555
|
+
_rulesetConfigurations[0].mustStartAtOrAfter = 0;
|
|
2556
|
+
_rulesetConfigurations[0].duration = 0;
|
|
2557
|
+
_rulesetConfigurations[0].weight = _weight;
|
|
2558
|
+
_rulesetConfigurations[0].weightCutPercent = 0;
|
|
2559
|
+
_rulesetConfigurations[0].approvalHook = IJBRulesetApprovalHook(address(0));
|
|
2560
|
+
_rulesetConfigurations[0].metadata = _metadata;
|
|
2561
|
+
_rulesetConfigurations[0].splitGroups = new JBSplitGroup[](0);
|
|
2562
|
+
_rulesetConfigurations[0].fundAccessLimitGroups = _fundAccessLimitGroup;
|
|
2563
|
+
|
|
2564
|
+
JBTerminalConfig[] memory _terminalConfigurations = new JBTerminalConfig[](1);
|
|
2565
|
+
JBAccountingContext[] memory _tokensToAccept = new JBAccountingContext[](1);
|
|
2566
|
+
_tokensToAccept[0] = JBAccountingContext({
|
|
2567
|
+
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
2568
|
+
});
|
|
2569
|
+
|
|
2570
|
+
_terminalConfigurations[0] =
|
|
2571
|
+
JBTerminalConfig({terminal: _terminal, accountingContextsToAccept: _tokensToAccept});
|
|
2572
|
+
|
|
2573
|
+
// Create a first project to collect fees.
|
|
2574
|
+
_controller.launchProjectFor({
|
|
2575
|
+
owner: address(420), // Random.
|
|
2576
|
+
projectUri: "whatever",
|
|
2577
|
+
rulesetConfigurations: _rulesetConfigurations,
|
|
2578
|
+
terminalConfigurations: _terminalConfigurations, // Set terminals to receive fees.
|
|
2579
|
+
memo: ""
|
|
2580
|
+
});
|
|
2581
|
+
|
|
2582
|
+
// Create the project to test.
|
|
2583
|
+
_projectId = _controller.launchProjectFor({
|
|
2584
|
+
owner: address(maliciousPayoutCaller),
|
|
2585
|
+
projectUri: "myIPFSHash",
|
|
2586
|
+
rulesetConfigurations: _rulesetConfigurations,
|
|
2587
|
+
terminalConfigurations: _terminalConfigurations,
|
|
2588
|
+
memo: ""
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
// Get a reference to the amount being paid.
|
|
2593
|
+
// The amount being paid is the payout limit plus five times the surplus allowance.
|
|
2594
|
+
uint256 _nativePayAmount = _nativeCurrencyPayoutLimit + (5 * _nativeCurrencySurplusAllowance);
|
|
2595
|
+
|
|
2596
|
+
// Pay the project such that the `_beneficiary` receives project tokens.
|
|
2597
|
+
_terminal.pay{value: _nativePayAmount}({
|
|
2598
|
+
projectId: _projectId,
|
|
2599
|
+
amount: _nativePayAmount,
|
|
2600
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
2601
|
+
beneficiary: _beneficiary,
|
|
2602
|
+
minReturnedTokens: 0,
|
|
2603
|
+
memo: "",
|
|
2604
|
+
metadata: new bytes(0)
|
|
2605
|
+
});
|
|
2606
|
+
|
|
2607
|
+
// Make sure the beneficiary got the expected number of tokens.
|
|
2608
|
+
uint256 _beneficiaryTokenBalance = mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
|
|
2609
|
+
* _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT;
|
|
2610
|
+
assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), _beneficiaryTokenBalance);
|
|
2611
|
+
|
|
2612
|
+
// Make sure the terminal holds the full native token balance.
|
|
2613
|
+
assertEq(
|
|
2614
|
+
jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN), _nativePayAmount
|
|
2615
|
+
);
|
|
2616
|
+
|
|
2617
|
+
// Pay out native tokens up to the payout limit. Since `splits[]` is empty, everything goes to project owner.
|
|
2618
|
+
// Project owner is our malicious contract that attempts to hijack control flow and execute subsequent calls
|
|
2619
|
+
// successfully.
|
|
2620
|
+
// This will fail via the mock contract itself, with an expected revert corresponding to the amounts.
|
|
2621
|
+
// See {MockMaliciousPayoutBeneficiary}
|
|
2622
|
+
_terminal.sendPayoutsOf({
|
|
2623
|
+
projectId: _projectId,
|
|
2624
|
+
amount: _nativeCurrencyPayoutLimit,
|
|
2625
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
2626
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
2627
|
+
minTokensPaidOut: 0
|
|
2628
|
+
});
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
function _toNative(uint256 _usdVal) internal pure returns (uint256) {
|
|
2632
|
+
return mulDiv(_usdVal, 10 ** _PRICE_FEED_DECIMALS, _USD_PRICE_PER_NATIVE);
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
function _toUsd(uint256 _nativeVal) internal pure returns (uint256) {
|
|
2636
|
+
return mulDiv(_nativeVal, _USD_PRICE_PER_NATIVE, 10 ** _PRICE_FEED_DECIMALS);
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
function _unreservedPortion(uint256 _fullPortion) internal view returns (uint256) {
|
|
2640
|
+
return mulDiv(
|
|
2641
|
+
_fullPortion, JBConstants.MAX_RESERVED_PERCENT - _metadata.reservedPercent, JBConstants.MAX_RESERVED_PERCENT
|
|
2642
|
+
);
|
|
2643
|
+
}
|
|
2644
|
+
}
|