Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: atomic manual parties #267

Closed
wants to merge 14 commits into from
149 changes: 149 additions & 0 deletions contracts/crowdfund/AtomicManualParty.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import { IGlobals } from "../globals/IGlobals.sol";
import { LibGlobals } from "../globals/LibGlobals.sol";
import { IPartyFactory } from "../party/IPartyFactory.sol";
import { Party } from "../party/Party.sol";
import { IERC721 } from "../tokens/IERC721.sol";
import { MetadataProvider } from "../renderers/MetadataProvider.sol";

/// @title AtomicManualParty
/// @notice Singleton that is called to create a party manually with an array
/// of party members and their voting power.
contract AtomicManualParty {
/// @notice Emitted when an atomic manual party is created
event AtomicManualPartyCreated(
Party indexed party,
address[] partyMembers,
uint96[] partyMemberVotingPowers
);
/// @notice Returned if the `AtomicManualParty` is created with no members
error NoPartyMembers();
/// @notice Returned if the lengths of `partyMembers` and `partyMemberVotingPowers` do not match
error PartyMembersArityMismatch();
/// @notice Returned if a party card would be issued to the null address
error InvalidPartyMember();
/// @notice Returned if a party card would be issued with no voting power
error InvalidPartyMemberVotingPower();

// The `Globals` contract storing global configuration values. This contract
// is immutable and it’s address will never change.
IGlobals private immutable _GLOBALS;

constructor(IGlobals globals) {
_GLOBALS = globals;
}

/// @notice Atomically creates the party and distributes the party cards
function createParty(
Party partyImpl,
Party.PartyOptions memory opts,
IERC721[] memory preciousTokens,
uint256[] memory preciousTokenIds,
uint40 rageQuitTimestamp,
address[] memory partyMembers,
uint96[] memory partyMemberVotingPowers
) public returns (Party party) {
uint96 totalVotingPower = _validateAtomicManualPartyArrays(
partyMembers,
partyMemberVotingPowers
);

address[] memory authorities = new address[](1);
authorities[0] = address(this);

opts.governance.totalVotingPower = totalVotingPower;

party = IPartyFactory(_GLOBALS.getAddress(LibGlobals.GLOBAL_PARTY_FACTORY)).createParty(
partyImpl,
authorities,
opts,
preciousTokens,
preciousTokenIds,
rageQuitTimestamp
);

_issuePartyCards(party, partyMembers, partyMemberVotingPowers);
}

/// @notice Atomically creates the party and distributes the party cards
/// with metadata
function createPartyWithMetadata(
Party partyImpl,
Party.PartyOptions memory opts,
IERC721[] memory preciousTokens,
uint256[] memory preciousTokenIds,
uint40 rageQuitTimestamp,
MetadataProvider provider,
bytes memory metadata,
address[] memory partyMembers,
uint96[] memory partyMemberVotingPowers
) external returns (Party party) {
uint96 totalVotingPower = _validateAtomicManualPartyArrays(
partyMembers,
partyMemberVotingPowers
);

address[] memory authorities = new address[](1);
authorities[0] = address(this);

opts.governance.totalVotingPower = totalVotingPower;

party = IPartyFactory(_GLOBALS.getAddress(LibGlobals.GLOBAL_PARTY_FACTORY))
.createPartyWithMetadata(
partyImpl,
authorities,
opts,
preciousTokens,
preciousTokenIds,
rageQuitTimestamp,
provider,
metadata
);

_issuePartyCards(party, partyMembers, partyMemberVotingPowers);
}

/// @notice Issue party cards to the party members and finishes up creation
/// @param party The party to issue cards for
/// @param partyMembers The party members to issue cards to
/// @param partyMemberVotingPowers The voting power each party member gets
function _issuePartyCards(
Party party,
address[] memory partyMembers,
uint96[] memory partyMemberVotingPowers
) internal {
for (uint256 i; i < partyMembers.length; i++) {
party.mint(partyMembers[i], partyMemberVotingPowers[i], partyMembers[i]);
}
party.abdicateAuthority();
emit AtomicManualPartyCreated(party, partyMembers, partyMemberVotingPowers);
}

/// @notice Validate manual party cards arrays, returns total voting power
/// @param partyMembers The party members to issue cards to
/// @param partyMemberVotingPowers The voting power each party member gets
/// @return totalVotingPower The total voting power of the party
function _validateAtomicManualPartyArrays(
address[] memory partyMembers,
uint96[] memory partyMemberVotingPowers
) private pure returns (uint96 totalVotingPower) {
if (partyMembers.length == 0) {
revert NoPartyMembers();
}
if (partyMembers.length != partyMemberVotingPowers.length) {
revert PartyMembersArityMismatch();
}

for (uint256 i = 0; i < partyMemberVotingPowers.length; ++i) {
if (partyMemberVotingPowers[i] == 0) {
revert InvalidPartyMemberVotingPower();
}
if (partyMembers[i] == address(0)) {
revert InvalidPartyMember();
}
totalVotingPower += partyMemberVotingPowers[i];
}
}
}
2 changes: 1 addition & 1 deletion contracts/globals/LibGlobals.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ library LibGlobals {

uint256 internal constant GLOBAL_PARTY_IMPL = 1;
uint256 internal constant GLOBAL_PROPOSAL_ENGINE_IMPL = 2;
// uint256 internal constant GLOBAL_PARTY_FACTORY = 3;
uint256 internal constant GLOBAL_PARTY_FACTORY = 3;
uint256 internal constant GLOBAL_GOVERNANCE_NFT_RENDER_IMPL = 4;
uint256 internal constant GLOBAL_CF_NFT_RENDER_IMPL = 5;
uint256 internal constant GLOBAL_OS_ZORA_AUCTION_TIMEOUT = 6;
Expand Down
12 changes: 11 additions & 1 deletion deploy/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import "../contracts/proposals/ProposalExecutionEngine.sol";
import "../contracts/utils/PartyHelpers.sol";
import "../contracts/market-wrapper/FoundationMarketWrapper.sol";
import "../contracts/market-wrapper/NounsMarketWrapper.sol";
import { AtomicManualParty } from "../contracts/crowdfund/AtomicManualParty.sol";
import "./LibDeployConstants.sol";

abstract contract Deploy {
Expand Down Expand Up @@ -74,6 +75,7 @@ abstract contract Deploy {
FoundationMarketWrapper public foundationMarketWrapper;
NounsMarketWrapper public nounsMarketWrapper;
PixeldroidConsoleFont public pixeldroidConsoleFont;
AtomicManualParty public atomicManualParty;

function deploy(LibDeployConstants.DeployConstants memory deployConstants) public virtual {
_switchDeployer(DeployerRole.Default);
Expand Down Expand Up @@ -393,6 +395,13 @@ abstract contract Deploy {
_trackDeployerGasAfter();
console.log(" Deployed - TokenGateKeeper", address(tokenGateKeeper));

console.log("");
console.log(" Deploying - AtomicManualParty");
_trackDeployerGasBefore();
atomicManualParty = new AtomicManualParty(globals);
_trackDeployerGasAfter();
console.log(" Deployed - AtomicManualParty", address(atomicManualParty));

// Set Global values and transfer ownership
{
console.log("### Configure Globals");
Expand Down Expand Up @@ -649,7 +658,7 @@ contract DeployScript is Script, Deploy {
Deploy.deploy(deployConstants);
vm.stopBroadcast();

AddressMapping[] memory addressMapping = new AddressMapping[](23);
AddressMapping[] memory addressMapping = new AddressMapping[](24);
addressMapping[0] = AddressMapping("Globals", address(globals));
addressMapping[1] = AddressMapping("TokenDistributor", address(tokenDistributor));
addressMapping[2] = AddressMapping(
Expand Down Expand Up @@ -692,6 +701,7 @@ contract DeployScript is Script, Deploy {
"PixeldroidConsoleFont",
address(pixeldroidConsoleFont)
);
addressMapping[23] = AddressMapping("AtomicManualParty", address(atomicManualParty));

console.log("");
console.log("### Deployed addresses");
Expand Down
4 changes: 4 additions & 0 deletions test/TestUsers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ contract GlobalsAdmin is Test {
function setOffChainSignatureValidator(address signatureValidator) public {
globals.setAddress(LibGlobals.GLOBAL_OFF_CHAIN_SIGNATURE_VALIDATOR, signatureValidator);
}

function setGlobalPartyFactory(address partyFactory) public {
globals.setAddress(LibGlobals.GLOBAL_PARTY_FACTORY, partyFactory);
}
}

contract PartyAdmin is Test {
Expand Down
Loading