diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64ed19f1d..fcf10cea1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,5 +116,3 @@ jobs: run: forge install - name: Check contract deployable mainnet run: "node js/contracts-deployable.js --via-ir --optimize --optimizer-runs 0" - - name: Check contracts deployable base - run: "node js/contracts-deployable.js --via-ir --optimize --optimizer-runs 0 --evm-version paris" diff --git a/contracts/authorities/BondingCurveAuthority.sol b/contracts/authorities/BondingCurveAuthority.sol index 169f888e7..1dead07c8 100644 --- a/contracts/authorities/BondingCurveAuthority.sol +++ b/contracts/authorities/BondingCurveAuthority.sol @@ -6,6 +6,7 @@ import { PartyFactory } from "../party/PartyFactory.sol"; import { IERC721 } from "../tokens/IERC721.sol"; import { MetadataProvider } from "../renderers/MetadataProvider.sol"; import { LibSafeCast } from "contracts/utils/LibSafeCast.sol"; +import { ProposalStorage } from "contracts/proposals/ProposalStorage.sol"; contract BondingCurveAuthority { using LibSafeCast for uint256; @@ -23,6 +24,7 @@ contract BondingCurveAuthority { error ExcessSlippage(); error AddAuthorityProposalNotSupported(); error SellZeroPartyCards(); + error DistributionsNotSupported(); event TreasuryFeeUpdated(uint16 previousTreasuryFee, uint16 newTreasuryFee); event PartyDaoFeeUpdated(uint16 previousPartyDaoFee, uint16 newPartyDaoFee); @@ -229,6 +231,13 @@ contract BondingCurveAuthority { if (partyOpts.proposalEngine.enableAddAuthorityProposal) { revert AddAuthorityProposalNotSupported(); } + + if ( + partyOpts.proposalEngine.distributionsConfig != + ProposalStorage.DistributionsConfig.NotAllowed + ) { + revert DistributionsNotSupported(); + } } /** diff --git a/contracts/party/PartyGovernance.sol b/contracts/party/PartyGovernance.sol index b6a74c61d..79b842fbe 100644 --- a/contracts/party/PartyGovernance.sol +++ b/contracts/party/PartyGovernance.sol @@ -498,7 +498,10 @@ abstract contract PartyGovernance is // Must not require a vote to create a distribution, otherwise // distributions can only be created through a distribution // proposal. - if (_getSharedProposalStorage().opts.distributionsRequireVote) { + if ( + _getSharedProposalStorage().opts.distributionsConfig != + DistributionsConfig.AllowedWithoutVote + ) { revert DistributionsRequireVoteError(); } // Must be an active member. @@ -1117,14 +1120,6 @@ abstract contract PartyGovernance is return snapshotNumHosts > 0 && snapshotNumHosts == numHostsAccepted; } - function _areVotesPassing( - uint96 voteCount, - uint96 totalVotingPower, - uint16 passThresholdBps - ) private pure returns (bool) { - return (uint256(voteCount) * 1e4) / uint256(totalVotingPower) >= uint256(passThresholdBps); - } - function _setPreciousList( IERC721[] memory preciousTokens, uint256[] memory preciousTokenIds diff --git a/contracts/party/PartyGovernanceNFT.sol b/contracts/party/PartyGovernanceNFT.sol index 4bbbbdfb8..fd6553181 100644 --- a/contracts/party/PartyGovernanceNFT.sol +++ b/contracts/party/PartyGovernanceNFT.sol @@ -99,7 +99,7 @@ abstract contract PartyGovernanceNFT is PartyGovernance, ERC721, IERC2981 { name = name_; symbol = symbol_; if (rageQuitTimestamp_ != 0) { - if (!proposalEngineOpts.distributionsRequireVote) { + if (proposalEngineOpts.distributionsConfig == DistributionsConfig.AllowedWithoutVote) { revert CannotEnableRageQuitIfNotDistributionsRequireVoteError(); } @@ -346,8 +346,10 @@ abstract contract PartyGovernanceNFT is PartyGovernance, ERC721, IERC2981 { } // Prevent enabling ragequit if distributions can be created without a vote. - if (!_getSharedProposalStorage().opts.distributionsRequireVote) - revert CannotEnableRageQuitIfNotDistributionsRequireVoteError(); + if ( + _getSharedProposalStorage().opts.distributionsConfig == + DistributionsConfig.AllowedWithoutVote + ) revert CannotEnableRageQuitIfNotDistributionsRequireVoteError(); uint40 oldRageQuitTimestamp = rageQuitTimestamp; diff --git a/contracts/proposals/ProposalExecutionEngine.sol b/contracts/proposals/ProposalExecutionEngine.sol index 596133947..c2a897357 100644 --- a/contracts/proposals/ProposalExecutionEngine.sol +++ b/contracts/proposals/ProposalExecutionEngine.sol @@ -263,7 +263,10 @@ contract ProposalExecutionEngine is _getSharedProposalStorage().opts.allowArbCallsToSpendPartyEth ); } else if (pt == ProposalType.Distribute) { - if (!_getSharedProposalStorage().opts.distributionsRequireVote) { + if ( + _getSharedProposalStorage().opts.distributionsConfig != + DistributionsConfig.AllowedWithVote + ) { revert ProposalDisabled(pt); } diff --git a/contracts/proposals/ProposalStorage.sol b/contracts/proposals/ProposalStorage.sol index 5a866523e..0b9a83395 100644 --- a/contracts/proposals/ProposalStorage.sol +++ b/contracts/proposals/ProposalStorage.sol @@ -24,6 +24,12 @@ abstract contract ProposalStorage { uint96 totalVotingPower; } + enum DistributionsConfig { + AllowedWithoutVote, + AllowedWithVote, + NotAllowed + } + struct ProposalEngineOpts { // Whether the party can add new authorities with the add authority proposal. bool enableAddAuthorityProposal; @@ -32,8 +38,8 @@ abstract contract ProposalStorage { bool allowArbCallsToSpendPartyEth; // Whether operators can be used. bool allowOperators; - // Whether distributions require a vote or can be executed by any active member. - bool distributionsRequireVote; + // Distributions config for the party. + DistributionsConfig distributionsConfig; } uint256 internal constant PROPOSAL_FLAG_UNANIMOUS = 0x1; diff --git a/test/authorities/BondingCurveAuthority.t.sol b/test/authorities/BondingCurveAuthority.t.sol index 9458469e8..ac4300518 100644 --- a/test/authorities/BondingCurveAuthority.t.sol +++ b/test/authorities/BondingCurveAuthority.t.sol @@ -6,6 +6,7 @@ import { PartyFactory } from "../../contracts/party/PartyFactory.sol"; import { BondingCurveAuthority } from "../../contracts/authorities/BondingCurveAuthority.sol"; import { SetupPartyHelper } from "../utils/SetupPartyHelper.sol"; import { MetadataProvider } from "contracts/renderers/MetadataProvider.sol"; +import { ProposalStorage } from "../../contracts/proposals/ProposalStorage.sol"; contract BondingCurveAuthorityTest is SetupPartyHelper { event TreasuryFeeUpdated(uint16 previousTreasuryFee, uint16 newTreasuryFee); @@ -59,6 +60,7 @@ contract BondingCurveAuthorityTest is SetupPartyHelper { opts.governance.executionDelay = 4 days; opts.governance.passThresholdBps = 1000; opts.governance.totalVotingPower = 0; + opts.proposalEngine.distributionsConfig = ProposalStorage.DistributionsConfig.NotAllowed; // Set a default treasury fee vm.prank(globalDaoWalletAddress); @@ -157,7 +159,7 @@ contract BondingCurveAuthorityTest is SetupPartyHelper { ); } - function test_creatorParty_revertAddAuthorityProposalNotSupported() external { + function test_createParty_revertAddAuthorityProposalNotSupported() external { opts.proposalEngine.enableAddAuthorityProposal = true; vm.expectRevert(BondingCurveAuthority.AddAuthorityProposalNotSupported.selector); @@ -174,7 +176,26 @@ contract BondingCurveAuthorityTest is SetupPartyHelper { ); } - function test_creatorParty_revertBelowMinExecutionDelay() external { + function test_createParty_revertDistributionsNotSupported() external { + opts.proposalEngine.distributionsConfig = ProposalStorage + .DistributionsConfig + .AllowedWithoutVote; + + vm.expectRevert(BondingCurveAuthority.DistributionsNotSupported.selector); + authority.createParty( + BondingCurveAuthority.BondingCurvePartyOptions({ + partyFactory: partyFactory, + partyImpl: partyImpl, + opts: opts, + creatorFeeOn: true, + a: 50_000, + b: uint80(0.001 ether) + }), + 1 + ); + } + + function test_createParty_revertBelowMinExecutionDelay() external { opts.governance.executionDelay = 0; vm.expectRevert(BondingCurveAuthority.ExecutionDelayTooShort.selector); diff --git a/test/crowdfund/CrowdfundFactory.t.sol b/test/crowdfund/CrowdfundFactory.t.sol index 38bcf6ea8..6e82bd61c 100644 --- a/test/crowdfund/CrowdfundFactory.t.sol +++ b/test/crowdfund/CrowdfundFactory.t.sol @@ -14,6 +14,7 @@ import "./MockMarketWrapper.sol"; import "contracts/globals/Globals.sol"; import "contracts/globals/LibGlobals.sol"; import "contracts/renderers/MetadataRegistry.sol"; +import { ProposalStorage } from "contracts/proposals/ProposalStorage.sol"; import "contracts/renderers/MetadataProvider.sol"; import { FixedPointMathLib } from "solmate/utils/FixedPointMathLib.sol"; import { LibSafeCast } from "contracts/utils/LibSafeCast.sol"; @@ -141,7 +142,7 @@ contract CrowdfundFactoryTest is Test, TestUtils { enableAddAuthorityProposal: true, allowArbCallsToSpendPartyEth: true, allowOperators: true, - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) }); @@ -219,7 +220,7 @@ contract CrowdfundFactoryTest is Test, TestUtils { enableAddAuthorityProposal: true, allowArbCallsToSpendPartyEth: true, allowOperators: true, - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) }); @@ -422,7 +423,7 @@ contract CrowdfundFactoryTest is Test, TestUtils { enableAddAuthorityProposal: true, allowArbCallsToSpendPartyEth: true, allowOperators: true, - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) }); @@ -495,7 +496,7 @@ contract CrowdfundFactoryTest is Test, TestUtils { enableAddAuthorityProposal: true, allowArbCallsToSpendPartyEth: true, allowOperators: true, - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) }); @@ -566,7 +567,7 @@ contract CrowdfundFactoryTest is Test, TestUtils { enableAddAuthorityProposal: true, allowArbCallsToSpendPartyEth: true, allowOperators: true, - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) }); @@ -638,7 +639,7 @@ contract CrowdfundFactoryTest is Test, TestUtils { enableAddAuthorityProposal: true, allowArbCallsToSpendPartyEth: true, allowOperators: true, - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }), preciousTokens: new IERC721[](0), preciousTokenIds: new uint256[](0), @@ -714,7 +715,7 @@ contract CrowdfundFactoryTest is Test, TestUtils { enableAddAuthorityProposal: true, allowArbCallsToSpendPartyEth: true, allowOperators: true, - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }), preciousTokens: new IERC721[](0), preciousTokenIds: new uint256[](0), @@ -804,7 +805,7 @@ contract CrowdfundFactoryTest is Test, TestUtils { enableAddAuthorityProposal: true, allowArbCallsToSpendPartyEth: true, allowOperators: true, - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }), preciousTokens: new IERC721[](0), preciousTokenIds: new uint256[](0), @@ -870,7 +871,7 @@ contract CrowdfundFactoryTest is Test, TestUtils { enableAddAuthorityProposal: true, allowArbCallsToSpendPartyEth: true, allowOperators: true, - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }), preciousTokens: new IERC721[](0), preciousTokenIds: new uint256[](0), diff --git a/test/crowdfund/InitialETHCrowdfund.t.sol b/test/crowdfund/InitialETHCrowdfund.t.sol index 1c0c6f402..5a9176492 100644 --- a/test/crowdfund/InitialETHCrowdfund.t.sol +++ b/test/crowdfund/InitialETHCrowdfund.t.sol @@ -1748,7 +1748,7 @@ contract InitialETHCrowdfundTest is InitialETHCrowdfundTestBase { enableAddAuthorityProposal: true, allowArbCallsToSpendPartyEth: true, allowOperators: true, - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }), preciousTokens: new IERC721[](0), preciousTokenIds: new uint256[](0), diff --git a/test/party/PartyFactory.t.sol b/test/party/PartyFactory.t.sol index 623463d8e..10161b111 100644 --- a/test/party/PartyFactory.t.sol +++ b/test/party/PartyFactory.t.sol @@ -82,7 +82,7 @@ contract PartyFactoryTest is Test, TestUtils { enableAddAuthorityProposal: true, allowArbCallsToSpendPartyEth: true, allowOperators: true, - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) }); uint40 rageQuitTimestamp = uint40(block.timestamp + 30 days); @@ -111,7 +111,10 @@ contract PartyFactoryTest is Test, TestUtils { .getProposalEngineOpts(); assertEq(proposalEngineOpts.allowArbCallsToSpendPartyEth, true); assertEq(proposalEngineOpts.allowOperators, true); - assertEq(proposalEngineOpts.distributionsRequireVote, true); + assertEq( + uint8(proposalEngineOpts.distributionsConfig), + uint8(ProposalStorage.DistributionsConfig.AllowedWithVote) + ); assertEq(party.preciousListHash(), _hashPreciousList(preciousTokens, preciousTokenIds)); } @@ -135,7 +138,7 @@ contract PartyFactoryTest is Test, TestUtils { enableAddAuthorityProposal: true, allowArbCallsToSpendPartyEth: true, allowOperators: true, - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) }); bytes memory customMetadata = abi.encodePacked(_randomBytes32()); @@ -168,7 +171,10 @@ contract PartyFactoryTest is Test, TestUtils { .getProposalEngineOpts(); assertEq(proposalEngineOpts.allowArbCallsToSpendPartyEth, true); assertEq(proposalEngineOpts.allowOperators, true); - assertEq(proposalEngineOpts.distributionsRequireVote, true); + assertEq( + uint8(proposalEngineOpts.distributionsConfig), + uint8(ProposalStorage.DistributionsConfig.AllowedWithVote) + ); assertEq(party.preciousListHash(), _hashPreciousList(preciousTokens, preciousTokenIds)); assertEq(address(registry.getProvider(address(party))), address(provider)); assertEq(provider.getMetadata(address(party), 0), customMetadata); diff --git a/test/party/PartyGovernanceNFT.t.sol b/test/party/PartyGovernanceNFT.t.sol index d40e7bce6..d6429a02f 100644 --- a/test/party/PartyGovernanceNFT.t.sol +++ b/test/party/PartyGovernanceNFT.t.sol @@ -540,7 +540,7 @@ contract PartyGovernanceNFTTest is PartyGovernanceNFTTestBase { allowArbCallsToSpendPartyEth: false, allowOperators: false, // Needs to be true to set non-zero rageQuitTimestamp - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) ); uint40 newTimestamp = uint40(block.timestamp + 1); @@ -568,7 +568,7 @@ contract PartyGovernanceNFTTest is PartyGovernanceNFTTestBase { allowArbCallsToSpendPartyEth: false, allowOperators: true, // Needs to be true to set non-zero rageQuitTimestamp - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) ); address notHost = _randomAddress(); @@ -596,7 +596,7 @@ contract PartyGovernanceNFTTest is PartyGovernanceNFTTestBase { allowArbCallsToSpendPartyEth: false, allowOperators: false, // Needs to be true to set non-zero rageQuitTimestamp - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) ); @@ -632,7 +632,7 @@ contract PartyGovernanceNFTTest is PartyGovernanceNFTTestBase { allowArbCallsToSpendPartyEth: false, allowOperators: false, // Needs to be true to set non-zero rageQuitTimestamp - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) ); @@ -665,7 +665,7 @@ contract PartyGovernanceNFTTest is PartyGovernanceNFTTestBase { allowArbCallsToSpendPartyEth: false, allowOperators: false, // Needs to be true to set non-zero rageQuitTimestamp - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) ); @@ -693,7 +693,7 @@ contract PartyGovernanceNFTTest is PartyGovernanceNFTTestBase { allowArbCallsToSpendPartyEth: false, allowOperators: false, // Needs to be true to set non-zero rageQuitTimestamp - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) ); @@ -788,7 +788,7 @@ contract PartyGovernanceNFTTest is PartyGovernanceNFTTestBase { allowArbCallsToSpendPartyEth: false, allowOperators: false, // Needs to be true to set non-zero rageQuitTimestamp - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) ); @@ -923,7 +923,7 @@ contract PartyGovernanceNFTTest is PartyGovernanceNFTTestBase { allowArbCallsToSpendPartyEth: false, allowOperators: false, // Needs to be true to set non-zero rageQuitTimestamp - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) ); @@ -990,7 +990,7 @@ contract PartyGovernanceNFTTest is PartyGovernanceNFTTestBase { allowArbCallsToSpendPartyEth: false, allowOperators: false, // Needs to be true to set non-zero rageQuitTimestamp - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) ); @@ -1078,7 +1078,7 @@ contract PartyGovernanceNFTTest is PartyGovernanceNFTTestBase { allowArbCallsToSpendPartyEth: false, allowOperators: false, // Needs to be true to set non-zero rageQuitTimestamp - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) ); @@ -1148,7 +1148,7 @@ contract PartyGovernanceNFTTest is PartyGovernanceNFTTestBase { allowArbCallsToSpendPartyEth: false, allowOperators: false, // Needs to be true to set non-zero rageQuitTimestamp - distributionsRequireVote: false + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithoutVote }) ); @@ -1179,7 +1179,7 @@ contract PartyGovernanceNFTTest is PartyGovernanceNFTTestBase { allowArbCallsToSpendPartyEth: false, allowOperators: false, // Needs to be true to set non-zero rageQuitTimestamp - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) ); @@ -1248,7 +1248,7 @@ contract PartyGovernanceNFTTest is PartyGovernanceNFTTestBase { allowArbCallsToSpendPartyEth: false, allowOperators: false, // Needs to be true to set non-zero rageQuitTimestamp - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) ); @@ -1318,7 +1318,7 @@ contract PartyGovernanceNFTTest is PartyGovernanceNFTTestBase { allowArbCallsToSpendPartyEth: false, allowOperators: false, // Needs to be true to set non-zero rageQuitTimestamp - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) ); @@ -1409,7 +1409,7 @@ contract PartyGovernanceNFTTest is PartyGovernanceNFTTestBase { allowArbCallsToSpendPartyEth: false, allowOperators: false, // Needs to be true to set non-zero rageQuitTimestamp - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) ); @@ -1480,7 +1480,7 @@ contract PartyGovernanceNFTTest is PartyGovernanceNFTTestBase { allowArbCallsToSpendPartyEth: false, allowOperators: false, // Needs to be true to set non-zero rageQuitTimestamp - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) ); @@ -1549,7 +1549,7 @@ contract PartyGovernanceNFTTest is PartyGovernanceNFTTestBase { allowArbCallsToSpendPartyEth: false, allowOperators: false, // Needs to be true to set non-zero rageQuitTimestamp - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) ); @@ -1637,7 +1637,7 @@ contract PartyGovernanceNFTTest is PartyGovernanceNFTTestBase { allowArbCallsToSpendPartyEth: false, allowOperators: false, // Needs to be true to set non-zero rageQuitTimestamp - distributionsRequireVote: true + distributionsConfig: ProposalStorage.DistributionsConfig.AllowedWithVote }) ); diff --git a/test/party/PartyGovernanceUnit.t.sol b/test/party/PartyGovernanceUnit.t.sol index de2cbeda3..1eee2dec4 100644 --- a/test/party/PartyGovernanceUnit.t.sol +++ b/test/party/PartyGovernanceUnit.t.sol @@ -296,7 +296,9 @@ contract PartyGovernanceUnitTest is Test, TestUtils { uint256[] memory preciousTokenIds ) private returns (TestablePartyGovernance gov) { defaultGovernanceOpts.totalVotingPower = totalVotingPower; - defaultProposalEngineOpts.distributionsRequireVote = distributionsRequireVote; + defaultProposalEngineOpts.distributionsConfig = ProposalStorage.DistributionsConfig( + distributionsRequireVote ? 1 : 0 + ); return new TestablePartyGovernance( diff --git a/test/utils/SetupPartyHelper.sol b/test/utils/SetupPartyHelper.sol index 8deb93f03..e2ecf8534 100644 --- a/test/utils/SetupPartyHelper.sol +++ b/test/utils/SetupPartyHelper.sol @@ -17,6 +17,7 @@ import { ERC721Receiver } from "../../contracts/tokens/ERC721Receiver.sol"; import { MetadataRegistry } from "../../contracts/renderers/MetadataRegistry.sol"; import { TokenDistributor } from "../../contracts/distribution/TokenDistributor.sol"; import { OffChainSignatureValidator } from "../../contracts/signature-validators/OffChainSignatureValidator.sol"; +import { ProposalStorage } from "../../contracts/proposals/ProposalStorage.sol"; /// @notice This contract provides a fully functioning party instance for testing. /// Run setup from inheriting contract. @@ -111,7 +112,9 @@ abstract contract SetupPartyHelper is TestUtils, ERC721Receiver { opts.governance.executionDelay = _EXECUTION_DELAY; opts.governance.passThresholdBps = 1000; opts.proposalEngine.allowArbCallsToSpendPartyEth = true; - opts.proposalEngine.distributionsRequireVote = true; + opts.proposalEngine.distributionsConfig = ProposalStorage + .DistributionsConfig + .AllowedWithVote; opts.governance.totalVotingPower = johnVotes + dannyVotes + steveVotes + thisVotes; address[] memory authorities = new address[](1);