Skip to content
Open
2 changes: 1 addition & 1 deletion contracts/crowdfund/CollectionBatchBuyCrowdfund.sol
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ contract CollectionBatchBuyCrowdfund is BuyCrowdfundBase {
// Update length of `tokens`
mstore(tokens, tokensBought)
// Update length of `tokenIds`
mstore(0x1A0, tokensBought)
mstore(mload(args), tokensBought)
}

return
Expand Down
23 changes: 18 additions & 5 deletions contracts/crowdfund/Crowdfund.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity 0.8.17;
import "../utils/LibAddress.sol";
import "../utils/LibRawResult.sol";
import "../utils/LibSafeCast.sol";
import "../utils/LibPreciousList.sol";
import "../tokens/ERC721Receiver.sol";
import "../party/Party.sol";
import "../globals/IGlobals.sol";
Expand Down Expand Up @@ -99,6 +100,7 @@ abstract contract Crowdfund is Implementation, ERC721Receiver, CrowdfundNFT {
error OnlyPartyHostError();
error OnlyContributorError();
error MissingHostsError();
error MismatchedPreciousListLengths();
error OnlyPartyDaoError(address notDao);
error OnlyPartyDaoOrHostError(address notDao);
error OnlyWhenEmergencyActionsAllowedError();
Expand Down Expand Up @@ -476,12 +478,14 @@ abstract contract Crowdfund is Implementation, ERC721Receiver, CrowdfundNFT {
}
// Create a party.
party = party_ = _getPartyFactory().createParty(
address(this),
Party.PartyOptions({
Party.PartyOpts({
name: name,
symbol: symbol,
// Indicates to the party to use the same customization preset as the crowdfund.
// ID of 0 typically not a valid option, but here it indicates
// to the party to use the same customization preset as the
// crowdfund.
customizationPresetId: 0,
preciousListHash: _hashPreciousList(preciousTokens, preciousTokenIds),
governance: PartyGovernance.GovernanceOpts({
hosts: governanceOpts.hosts,
voteDuration: governanceOpts.voteDuration,
Expand All @@ -492,8 +496,7 @@ abstract contract Crowdfund is Implementation, ERC721Receiver, CrowdfundNFT {
feeRecipient: governanceOpts.feeRecipient
})
}),
preciousTokens,
preciousTokenIds
address(this)
);
// Transfer the acquired NFTs to the new party.
for (uint256 i; i < preciousTokens.length; ++i) {
Expand Down Expand Up @@ -523,6 +526,16 @@ abstract contract Crowdfund is Implementation, ERC721Receiver, CrowdfundNFT {
}
}

function _hashPreciousList(
IERC721[] memory preciousTokens,
uint256[] memory preciousTokenIds
) private pure returns (bytes32 preciousListHash) {
if (preciousTokens.length != preciousTokenIds.length) {
revert MismatchedPreciousListLengths();
}
preciousListHash = LibPreciousList.hashPreciousList(preciousTokens, preciousTokenIds);
}

function _getFinalContribution(
address contributor
) internal view returns (uint256 ethUsed, uint256 ethOwed, uint256 votingPower) {
Expand Down
51 changes: 34 additions & 17 deletions contracts/party/IPartyFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,48 @@ import "./Party.sol";
interface IPartyFactory {
event PartyCreated(
Party indexed party,
Party.PartyOptions opts,
IERC721[] preciousTokens,
uint256[] preciousTokenIds,
Party.PartyOpts opts,
address mintAuthority,
address creator
);

struct PartyFromListOpts {
// Options used to initialize the party. These are fixed and cannot be
// changed later.
Party.PartyOpts partyOpts;
// The tokens to transfer to the party.
IERC721[] tokens;
// The IDs associated with each token in `tokens`.
uint256[] tokenIds;
// The address of the party creator to mint card for.
address creator;
// The voting power of the party creator.
uint96 creatorVotingPower;
// The address to delegate creator's voting power to.
address creatorDelegate;
// Merkle root of list of initial members and voting power for each member.
// Each leaf in the list should be encoded as:
// `abi.encodePacked(address member, uint96 votingPower, uint256 nonce)`
bytes32 listMerkleRoot;
}

/// @notice Deploy a new party instance. Afterwards, governance NFTs can be minted
/// for party members using the `mint()` function from the newly
/// created party.
/// @param authority The address that can call `mint()`.
/// @param opts Options used to initialize the party. These are fixed
/// and cannot be changed later.
/// @param preciousTokens The tokens that are considered precious by the
/// party.These are protected assets and are subject
/// to extra restrictions in proposals vs other
/// assets.
/// @param preciousTokenIds The IDs associated with each token in `preciousTokens`.
/// for party members by the authority (usually the crowdfund
/// instance, if created from a successful crowdfund) using the
/// `mint()` function.
/// @param opts Options used to initialize the party.
/// @return party The newly created `Party` instance.
function createParty(
address authority,
Party.PartyOptions calldata opts,
IERC721[] memory preciousTokens,
uint256[] memory preciousTokenIds
Party.PartyOpts memory opts,
address mintAuthority
) external returns (Party party);

/// @notice Deploy a new party instance from a list of members and their
/// voting powers. Afterwards, governance NFTs can be minted for
/// party members through the `PartyList` contract using the `mint()` function.
/// @param opts Options used to initialize the party from a list.
function createPartyFromList(PartyFromListOpts memory opts) external returns (Party party);

/// @notice The `Globals` contract storing global configuration values. This contract
/// is immutable and it’s address will never change.
function GLOBALS() external view returns (IGlobals);
Expand Down
36 changes: 14 additions & 22 deletions contracts/party/Party.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,30 @@ import "./PartyGovernance.sol";
/// @notice The governance contract that also custodies the precious NFTs. This
/// is also the Governance NFT 721 contract.
contract Party is PartyGovernanceNFT {
// Arguments used to initialize the party.
struct PartyOptions {
PartyGovernance.GovernanceOpts governance;
struct PartyOpts {
// The name of the party.
string name;
// The symbol of the party.
string symbol;
// Preset ID to use for customizing rendering of governance NFTs.
uint256 customizationPresetId;
}

// Arguments used to initialize the `PartyGovernanceNFT`.
struct PartyInitData {
PartyOptions options;
IERC721[] preciousTokens;
uint256[] preciousTokenIds;
address mintAuthority;
// Hash of tokens and token IDs that are considered precious by the
// party. These are protected assets and are subject to extra
// restrictions in proposals vs other assets. This is used to verify
// that the list of precious tokens and token IDs is correct.
bytes32 preciousListHash;
// Options used to initialize the party governance.
PartyGovernance.GovernanceOpts governance;
}

// Set the `Globals` contract.
constructor(IGlobals globals) PartyGovernanceNFT(globals) {}

/// @notice Initializer to be delegatecalled by `Proxy` constructor. Will
/// revert if called outside the constructor.
/// @param initData Options used to initialize the party governance.
function initialize(PartyInitData memory initData) external onlyConstructor {
PartyGovernanceNFT._initialize(
initData.options.name,
initData.options.symbol,
initData.options.customizationPresetId,
initData.options.governance,
initData.preciousTokens,
initData.preciousTokenIds,
initData.mintAuthority
);
/// @param opts Options used to initialize the party governance.
function initialize(PartyOpts memory opts, address mintAuthority) external onlyConstructor {
PartyGovernanceNFT._initialize(opts, mintAuthority);
}

receive() external payable {}
Expand Down
53 changes: 36 additions & 17 deletions contracts/party/PartyFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,45 +9,64 @@ import "../renderers/RendererStorage.sol";

import "./Party.sol";
import "./IPartyFactory.sol";
import "./PartyList.sol";

/// @notice Factory used to deploy new proxified `Party` instances.
contract PartyFactory is IPartyFactory {
error InvalidAuthorityError(address authority);

/// @inheritdoc IPartyFactory
IGlobals public immutable GLOBALS;
PartyList public immutable PARTY_LIST;

// Set the `Globals` contract.
constructor(IGlobals globals) {
constructor(IGlobals globals, PartyList partyList) {
GLOBALS = globals;
PARTY_LIST = partyList;
}

/// @inheritdoc IPartyFactory
function createParty(
address authority,
Party.PartyOptions memory opts,
IERC721[] memory preciousTokens,
uint256[] memory preciousTokenIds
Party.PartyOpts memory opts,
address mintAuthority
) external returns (Party party) {
// Ensure a valid authority is set to mint governance NFTs.
if (authority == address(0)) {
revert InvalidAuthorityError(authority);
if (mintAuthority == address(0)) {
revert InvalidAuthorityError(mintAuthority);
}
// Create the party.
return _createParty(opts, mintAuthority);
}

function createPartyFromList(PartyFromListOpts memory opts) public returns (Party party) {
// Create the party.
party = _createParty(opts.partyOpts, address(PARTY_LIST));
// Create the list used to determine the initial list of members and voting
// power for each member and mint the party creator their card.
PARTY_LIST.createList(
party,
opts.listMerkleRoot,
opts.creator,
opts.creatorVotingPower,
opts.creatorDelegate
);
// Transfer the tokens to the party.
for (uint256 i; i < opts.tokens.length; ++i) {
opts.tokens[i].transferFrom(msg.sender, address(party), opts.tokenIds[i]);
}
}

function _createParty(
Party.PartyOpts memory opts,
address mintAuthority
) private returns (Party party) {
// Deploy a new proxified `Party` instance.
Party.PartyInitData memory initData = Party.PartyInitData({
options: opts,
preciousTokens: preciousTokens,
preciousTokenIds: preciousTokenIds,
mintAuthority: authority
});
party = Party(
payable(
new Proxy(
GLOBALS.getImplementation(LibGlobals.GLOBAL_PARTY_IMPL),
abi.encodeCall(Party.initialize, (initData))
abi.encodeCall(Party.initialize, (opts, mintAuthority))
)
)
);
emit PartyCreated(party, opts, preciousTokens, preciousTokenIds, msg.sender);
emit PartyCreated(party, opts, mintAuthority, msg.sender);
}
}
34 changes: 5 additions & 29 deletions contracts/party/PartyGovernance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import "../tokens/ERC1155Receiver.sol";
import "../utils/LibERC20Compat.sol";
import "../utils/LibRawResult.sol";
import "../utils/LibSafeCast.sol";
import "../utils/LibPreciousList.sol";
import "../globals/IGlobals.sol";
import "../globals/LibGlobals.sol";
import "../proposals/IProposalExecutionEngine.sol";
Expand Down Expand Up @@ -165,7 +166,6 @@ abstract contract PartyGovernance is
event HostStatusTransferred(address oldHost, address newHost);
event EmergencyExecuteDisabled();

error MismatchedPreciousListLengths();
error BadProposalStatusError(ProposalStatus status);
error BadProposalHashError(bytes32 proposalHash, bytes32 actualHash);
error ExecutionTimeExceededError(uint40 maxExecutableTime, uint40 timestamp);
Expand Down Expand Up @@ -284,11 +284,7 @@ abstract contract PartyGovernance is
}

// Initialize storage for proxy contracts and initialize the proposal execution engine.
function _initialize(
GovernanceOpts memory opts,
IERC721[] memory preciousTokens,
uint256[] memory preciousTokenIds
) internal virtual {
function _initialize(GovernanceOpts memory opts, bytes32 preciousListHash_) internal virtual {
// Check BPS are valid.
if (opts.feeBps > 1e4) {
revert InvalidBpsError(opts.feeBps);
Expand All @@ -312,7 +308,7 @@ abstract contract PartyGovernance is
feeBps = opts.feeBps;
feeRecipient = opts.feeRecipient;
// Set the precious list.
_setPreciousList(preciousTokens, preciousTokenIds);
preciousListHash = preciousListHash_;
// Set the party hosts.
for (uint256 i = 0; i < opts.hosts.length; ++i) {
isHost[opts.hosts[i]] = true;
Expand Down Expand Up @@ -1044,32 +1040,12 @@ abstract contract PartyGovernance is
return (uint256(voteCount) * 1e4) / uint256(totalVotingPower) >= uint256(passThresholdBps);
}

function _setPreciousList(
IERC721[] memory preciousTokens,
uint256[] memory preciousTokenIds
) private {
if (preciousTokens.length != preciousTokenIds.length) {
revert MismatchedPreciousListLengths();
}
preciousListHash = _hashPreciousList(preciousTokens, preciousTokenIds);
}

function _isPreciousListCorrect(
IERC721[] memory preciousTokens,
uint256[] memory preciousTokenIds
) private view returns (bool) {
return preciousListHash == _hashPreciousList(preciousTokens, preciousTokenIds);
}

function _hashPreciousList(
IERC721[] memory preciousTokens,
uint256[] memory preciousTokenIds
) internal pure returns (bytes32 h) {
assembly {
mstore(0x00, keccak256(add(preciousTokens, 0x20), mul(mload(preciousTokens), 0x20)))
mstore(0x20, keccak256(add(preciousTokenIds, 0x20), mul(mload(preciousTokenIds), 0x20)))
h := keccak256(0x00, 0x40)
}
return
preciousListHash == LibPreciousList.hashPreciousList(preciousTokens, preciousTokenIds);
}

// Assert that the hash of a proposal matches expectedHash.
Expand Down
23 changes: 8 additions & 15 deletions contracts/party/PartyGovernanceNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "openzeppelin/contracts/interfaces/IERC2981.sol";
import "../globals/IGlobals.sol";
import "../tokens/IERC721.sol";
import "../vendor/solmate/ERC721.sol";
import "./Party.sol";
import "./PartyGovernance.sol";
import "../renderers/RendererStorage.sol";

Expand Down Expand Up @@ -47,22 +48,14 @@ contract PartyGovernanceNFT is PartyGovernance, ERC721, IERC2981 {
}

// Initialize storage for proxy contracts.
function _initialize(
string memory name_,
string memory symbol_,
uint256 customizationPresetId,
PartyGovernance.GovernanceOpts memory governanceOpts,
IERC721[] memory preciousTokens,
uint256[] memory preciousTokenIds,
address mintAuthority_
) internal {
PartyGovernance._initialize(governanceOpts, preciousTokens, preciousTokenIds);
name = name_;
symbol = symbol_;
mintAuthority = mintAuthority_;
if (customizationPresetId != 0) {
function _initialize(Party.PartyOpts memory opts, address authority) internal {
PartyGovernance._initialize(opts.governance, opts.preciousListHash);
name = opts.name;
symbol = opts.symbol;
mintAuthority = authority;
if (opts.customizationPresetId != 0) {
RendererStorage(_GLOBALS.getAddress(LibGlobals.GLOBAL_RENDERER_STORAGE))
.useCustomizationPreset(customizationPresetId);
.useCustomizationPreset(opts.customizationPresetId);
}
}

Expand Down
Loading