From 78581d7c0750034a1b661ca1c62dc4a99dc83c08 Mon Sep 17 00:00:00 2001 From: Brian Le Date: Thu, 21 Sep 2023 07:42:04 -0500 Subject: [PATCH 1/2] feat: implement ERC-1167 minimal proxies --- contracts/crowdfund/AuctionCrowdfund.sol | 2 +- contracts/crowdfund/BuyCrowdfund.sol | 2 +- .../crowdfund/CollectionBatchBuyCrowdfund.sol | 2 +- .../crowdfund/CollectionBuyCrowdfund.sol | 4 +- contracts/crowdfund/CrowdfundFactory.sol | 84 +++--------- contracts/crowdfund/InitialETHCrowdfund.sol | 2 +- contracts/crowdfund/ReraiseETHCrowdfund.sol | 2 +- .../crowdfund/RollingAuctionCrowdfund.sol | 2 +- contracts/party/Party.sol | 2 +- contracts/party/PartyFactory.sol | 16 +-- .../proposals/ProposalExecutionEngine.sol | 2 +- contracts/utils/Implementation.sol | 31 +++-- contracts/utils/Proxy.sol | 37 ------ test/crowdfund/AtomicManualParty.t.sol | 19 ++- test/crowdfund/AuctionCrowdfund.t.sol | 121 ++++++++---------- test/crowdfund/BuyCrowdfund.t.sol | 113 +++++++--------- .../CollectionBatchBuyCrowdfund.t.sol | 57 ++++----- test/crowdfund/CollectionBuyCrowdfund.t.sol | 107 +++++++--------- test/crowdfund/Crowdfund.t.sol | 93 ++++++-------- .../crowdfund/FoundationCrowdfundForked.t.sol | 61 ++++----- test/crowdfund/InitialETHCrowdfund.t.sol | 88 +++++++------ test/crowdfund/NounsCrowdfundForked.t.sol | 61 ++++----- .../OpenseaFulfillOrderForkedTest.t.sol | 57 ++++----- test/crowdfund/ReraiseETHCrowdfund.t.sol | 108 +++++++--------- test/crowdfund/RollingAuctionCrowdfund.t.sol | 66 +++++----- .../RollingNounsCrowdfundForked.t.sol | 65 +++++----- test/crowdfund/ZoraCrowdfundForked.t.sol | 61 ++++----- test/party/Party.t.sol | 30 ----- test/renderers/MetadataRegistry.t.sol | 1 - test/utils/Implementation.t.sol | 39 +++--- 30 files changed, 559 insertions(+), 776 deletions(-) delete mode 100644 contracts/utils/Proxy.sol delete mode 100644 test/party/Party.t.sol diff --git a/contracts/crowdfund/AuctionCrowdfund.sol b/contracts/crowdfund/AuctionCrowdfund.sol index 676825d0..c8c2af1d 100644 --- a/contracts/crowdfund/AuctionCrowdfund.sol +++ b/contracts/crowdfund/AuctionCrowdfund.sol @@ -17,7 +17,7 @@ contract AuctionCrowdfund is AuctionCrowdfundBase { /// revert if called outside the constructor. /// @param opts Options used to initialize the crowdfund. These are fixed /// and cannot be changed later. - function initialize(AuctionCrowdfundOptions memory opts) external payable onlyConstructor { + function initialize(AuctionCrowdfundOptions memory opts) external payable onlyInitialize { AuctionCrowdfundBase._initialize(opts); } diff --git a/contracts/crowdfund/BuyCrowdfund.sol b/contracts/crowdfund/BuyCrowdfund.sol index 335b8bcb..f10ede33 100644 --- a/contracts/crowdfund/BuyCrowdfund.sol +++ b/contracts/crowdfund/BuyCrowdfund.sol @@ -78,7 +78,7 @@ contract BuyCrowdfund is BuyCrowdfundBase { /// revert if called outside the constructor. /// @param opts Options used to initialize the crowdfund. These are fixed /// and cannot be changed later. - function initialize(BuyCrowdfundOptions memory opts) external payable onlyConstructor { + function initialize(BuyCrowdfundOptions memory opts) external payable onlyInitialize { if (opts.onlyHostCanBuy && opts.governanceOpts.hosts.length == 0) { revert MissingHostsError(); } diff --git a/contracts/crowdfund/CollectionBatchBuyCrowdfund.sol b/contracts/crowdfund/CollectionBatchBuyCrowdfund.sol index e02a9db9..ede03adf 100644 --- a/contracts/crowdfund/CollectionBatchBuyCrowdfund.sol +++ b/contracts/crowdfund/CollectionBatchBuyCrowdfund.sol @@ -100,7 +100,7 @@ contract CollectionBatchBuyCrowdfund is BuyCrowdfundBase { /// and cannot be changed later. function initialize( CollectionBatchBuyCrowdfundOptions memory opts - ) external payable onlyConstructor { + ) external payable onlyInitialize { if (opts.governanceOpts.hosts.length == 0) { revert MissingHostsError(); } diff --git a/contracts/crowdfund/CollectionBuyCrowdfund.sol b/contracts/crowdfund/CollectionBuyCrowdfund.sol index 33c5e4ed..36d811ef 100644 --- a/contracts/crowdfund/CollectionBuyCrowdfund.sol +++ b/contracts/crowdfund/CollectionBuyCrowdfund.sol @@ -71,9 +71,7 @@ contract CollectionBuyCrowdfund is BuyCrowdfundBase { /// revert if called outside the constructor. /// @param opts Options used to initialize the crowdfund. These are fixed /// and cannot be changed later. - function initialize( - CollectionBuyCrowdfundOptions memory opts - ) external payable onlyConstructor { + function initialize(CollectionBuyCrowdfundOptions memory opts) external payable onlyInitialize { if (opts.governanceOpts.hosts.length == 0) { revert MissingHostsError(); } diff --git a/contracts/crowdfund/CrowdfundFactory.sol b/contracts/crowdfund/CrowdfundFactory.sol index e953e8bf..4b7501ef 100644 --- a/contracts/crowdfund/CrowdfundFactory.sol +++ b/contracts/crowdfund/CrowdfundFactory.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; +import { Clones } from "openzeppelin/contracts/proxy/Clones.sol"; + import { LibRawResult } from "../utils/LibRawResult.sol"; -import { Proxy } from "../utils/Proxy.sol"; -import { Implementation } from "../utils/Implementation.sol"; import { IGateKeeper } from "../gatekeepers/IGateKeeper.sol"; import { AuctionCrowdfund, AuctionCrowdfundBase } from "./AuctionCrowdfund.sol"; @@ -18,6 +18,7 @@ import { Party } from "../party/Party.sol"; /// @notice Factory used to deploys new proxified `Crowdfund` instances. contract CrowdfundFactory { + using Clones for address; using LibRawResult for bytes; event BuyCrowdfundCreated( @@ -72,14 +73,8 @@ contract CrowdfundFactory { bytes memory createGateCallData ) external payable returns (BuyCrowdfund inst) { opts.gateKeeperId = _prepareGate(opts.gateKeeper, opts.gateKeeperId, createGateCallData); - inst = BuyCrowdfund( - payable( - new Proxy{ value: msg.value }( - Implementation(address(crowdfundImpl)), - abi.encodeCall(BuyCrowdfund.initialize, (opts)) - ) - ) - ); + inst = BuyCrowdfund(address(crowdfundImpl).clone()); + inst.initialize{ value: msg.value }(opts); emit BuyCrowdfundCreated(msg.sender, inst, opts); } @@ -96,14 +91,8 @@ contract CrowdfundFactory { bytes memory createGateCallData ) external payable returns (AuctionCrowdfund inst) { opts.gateKeeperId = _prepareGate(opts.gateKeeper, opts.gateKeeperId, createGateCallData); - inst = AuctionCrowdfund( - payable( - new Proxy{ value: msg.value }( - Implementation(address(crowdfundImpl)), - abi.encodeCall(AuctionCrowdfund.initialize, (opts)) - ) - ) - ); + inst = AuctionCrowdfund(payable(address(crowdfundImpl).clone())); + inst.initialize{ value: msg.value }(opts); emit AuctionCrowdfundCreated(msg.sender, inst, opts); } @@ -121,17 +110,8 @@ contract CrowdfundFactory { bytes memory createGateCallData ) external payable returns (RollingAuctionCrowdfund inst) { opts.gateKeeperId = _prepareGate(opts.gateKeeper, opts.gateKeeperId, createGateCallData); - inst = RollingAuctionCrowdfund( - payable( - new Proxy{ value: msg.value }( - Implementation(address(crowdfundImpl)), - abi.encodeCall( - RollingAuctionCrowdfund.initialize, - (opts, allowedAuctionsMerkleRoot) - ) - ) - ) - ); + inst = RollingAuctionCrowdfund(payable(address(crowdfundImpl).clone())); + inst.initialize{ value: msg.value }(opts, allowedAuctionsMerkleRoot); emit RollingAuctionCrowdfundCreated(msg.sender, inst, opts, allowedAuctionsMerkleRoot); } @@ -147,14 +127,8 @@ contract CrowdfundFactory { bytes memory createGateCallData ) external payable returns (CollectionBuyCrowdfund inst) { opts.gateKeeperId = _prepareGate(opts.gateKeeper, opts.gateKeeperId, createGateCallData); - inst = CollectionBuyCrowdfund( - payable( - new Proxy{ value: msg.value }( - Implementation(address(crowdfundImpl)), - abi.encodeCall(CollectionBuyCrowdfund.initialize, (opts)) - ) - ) - ); + inst = CollectionBuyCrowdfund(address(crowdfundImpl).clone()); + inst.initialize{ value: msg.value }(opts); emit CollectionBuyCrowdfundCreated(msg.sender, inst, opts); } @@ -170,14 +144,8 @@ contract CrowdfundFactory { bytes memory createGateCallData ) external payable returns (CollectionBatchBuyCrowdfund inst) { opts.gateKeeperId = _prepareGate(opts.gateKeeper, opts.gateKeeperId, createGateCallData); - inst = CollectionBatchBuyCrowdfund( - payable( - new Proxy{ value: msg.value }( - Implementation(address(crowdfundImpl)), - abi.encodeCall(CollectionBatchBuyCrowdfund.initialize, (opts)) - ) - ) - ); + inst = CollectionBatchBuyCrowdfund(address(crowdfundImpl).clone()); + inst.initialize{ value: msg.value }(opts); emit CollectionBatchBuyCrowdfundCreated(msg.sender, inst, opts); } @@ -227,16 +195,12 @@ contract CrowdfundFactory { crowdfundOpts.gateKeeperId, createGateCallData ); - inst = InitialETHCrowdfund( - payable( - new Proxy{ value: msg.value }( - Implementation(address(crowdfundImpl)), - abi.encodeCall( - InitialETHCrowdfund.initialize, - (crowdfundOpts, partyOpts, customMetadataProvider, customMetadata) - ) - ) - ) + inst = InitialETHCrowdfund(address(crowdfundImpl).clone()); + inst.initialize{ value: msg.value }( + crowdfundOpts, + partyOpts, + customMetadataProvider, + customMetadata ); emit InitialETHCrowdfundCreated(msg.sender, inst, inst.party(), crowdfundOpts, partyOpts); } @@ -253,14 +217,8 @@ contract CrowdfundFactory { bytes memory createGateCallData ) external payable returns (ReraiseETHCrowdfund inst) { opts.gateKeeperId = _prepareGate(opts.gateKeeper, opts.gateKeeperId, createGateCallData); - inst = ReraiseETHCrowdfund( - payable( - new Proxy{ value: msg.value }( - Implementation(address(crowdfundImpl)), - abi.encodeCall(ReraiseETHCrowdfund.initialize, (opts)) - ) - ) - ); + inst = ReraiseETHCrowdfund(address(crowdfundImpl).clone()); + inst.initialize{ value: msg.value }(opts); emit ReraiseETHCrowdfundCreated(msg.sender, inst, opts); } diff --git a/contracts/crowdfund/InitialETHCrowdfund.sol b/contracts/crowdfund/InitialETHCrowdfund.sol index 29729b2b..4c3656a4 100644 --- a/contracts/crowdfund/InitialETHCrowdfund.sol +++ b/contracts/crowdfund/InitialETHCrowdfund.sol @@ -110,7 +110,7 @@ contract InitialETHCrowdfund is ETHCrowdfundBase { ETHPartyOptions memory partyOpts, MetadataProvider customMetadataProvider, bytes memory customMetadata - ) external payable onlyConstructor { + ) external payable onlyInitialize { // Create party the initial crowdfund will be for. Party party_ = _createParty(partyOpts, customMetadataProvider, customMetadata); diff --git a/contracts/crowdfund/ReraiseETHCrowdfund.sol b/contracts/crowdfund/ReraiseETHCrowdfund.sol index 2c546c56..6c0f7c4c 100644 --- a/contracts/crowdfund/ReraiseETHCrowdfund.sol +++ b/contracts/crowdfund/ReraiseETHCrowdfund.sol @@ -65,7 +65,7 @@ contract ReraiseETHCrowdfund is ETHCrowdfundBase, CrowdfundNFT { /// @notice Initializer to be delegatecalled by `Proxy` constructor. Will /// revert if called outside the constructor. /// @param opts The options to initialize the crowdfund with. - function initialize(ETHCrowdfundOptions memory opts) external payable onlyConstructor { + function initialize(ETHCrowdfundOptions memory opts) external payable onlyInitialize { // Initialize the crowdfund. ETHCrowdfundBase._initialize(opts); diff --git a/contracts/crowdfund/RollingAuctionCrowdfund.sol b/contracts/crowdfund/RollingAuctionCrowdfund.sol index 79cd188e..939f19e1 100644 --- a/contracts/crowdfund/RollingAuctionCrowdfund.sol +++ b/contracts/crowdfund/RollingAuctionCrowdfund.sol @@ -58,7 +58,7 @@ contract RollingAuctionCrowdfund is AuctionCrowdfundBase { function initialize( AuctionCrowdfundBase.AuctionCrowdfundOptions memory opts, bytes32 allowedAuctionsMerkleRoot_ - ) external payable onlyConstructor { + ) external payable onlyInitialize { // Initialize the base contract. AuctionCrowdfundBase._initialize(opts); diff --git a/contracts/party/Party.sol b/contracts/party/Party.sol index 38df896d..4536c03d 100644 --- a/contracts/party/Party.sol +++ b/contracts/party/Party.sol @@ -36,7 +36,7 @@ contract Party is PartyGovernanceNFT { /// @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 { + function initialize(PartyInitData memory initData) external onlyInitialize { PartyGovernanceNFT._initialize( initData.options.name, initData.options.symbol, diff --git a/contracts/party/PartyFactory.sol b/contracts/party/PartyFactory.sol index ef14ce4f..17a8ac5e 100644 --- a/contracts/party/PartyFactory.sol +++ b/contracts/party/PartyFactory.sol @@ -1,19 +1,21 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; +import { Clones } from "openzeppelin/contracts/proxy/Clones.sol"; + import { IERC721 } from "../tokens/IERC721.sol"; -import { Proxy } from "../utils/Proxy.sol"; import { Party } from "./Party.sol"; import { IPartyFactory } from "./IPartyFactory.sol"; import { IGlobals } from "../globals/IGlobals.sol"; import { LibGlobals } from "../globals/LibGlobals.sol"; -import { Implementation } from "../utils/Implementation.sol"; import { MetadataRegistry } from "../renderers/MetadataRegistry.sol"; import { MetadataProvider } from "../renderers/MetadataProvider.sol"; /// @notice Factory used to deploy new proxified `Party` instances. contract PartyFactory is IPartyFactory { + using Clones for address; + error NoAuthorityError(); // The `Globals` contract storing global configuration values. This contract @@ -46,14 +48,8 @@ contract PartyFactory is IPartyFactory { authorities: authorities, rageQuitTimestamp: rageQuitTimestamp }); - party = Party( - payable( - new Proxy( - Implementation(address(partyImpl)), - abi.encodeCall(Party.initialize, (initData)) - ) - ) - ); + party = Party(payable(address(partyImpl).clone())); + party.initialize(initData); emit PartyCreated(party, opts, preciousTokens, preciousTokenIds, msg.sender); } diff --git a/contracts/proposals/ProposalExecutionEngine.sol b/contracts/proposals/ProposalExecutionEngine.sol index 4ea27ec9..6523a542 100644 --- a/contracts/proposals/ProposalExecutionEngine.sol +++ b/contracts/proposals/ProposalExecutionEngine.sol @@ -328,7 +328,7 @@ contract ProposalExecutionEngine is ); } _initProposalImpl(newImpl, initData); - emit ProposalEngineImplementationUpgraded(address(IMPL), expectedImpl); + emit ProposalEngineImplementationUpgraded(address(implementation), expectedImpl); } // Retrieve the explicit storage bucket for the ProposalExecutionEngine logic. diff --git a/contracts/utils/Implementation.sol b/contracts/utils/Implementation.sol index 6a9b5d93..b77e433e 100644 --- a/contracts/utils/Implementation.sol +++ b/contracts/utils/Implementation.sol @@ -3,28 +3,41 @@ pragma solidity 0.8.20; // Base contract for all contracts intended to be delegatecalled into. abstract contract Implementation { + event Initialized(); + + error AlreadyInitialized(); error OnlyDelegateCallError(); - error OnlyConstructorError(); - address public immutable IMPL; + /// @notice The address of the implementation contract. + address public immutable implementation; + + /// @notice Whether or not the implementation has been initialized. + bool public initialized; constructor() { - IMPL = address(this); + implementation = address(this); } // Reverts if the current function context is not inside of a delegatecall. modifier onlyDelegateCall() virtual { - if (address(this) == IMPL) { + if (address(this) == implementation) { revert OnlyDelegateCallError(); } _; } - // Reverts if the current function context is not inside of a constructor. - modifier onlyConstructor() { - if (address(this).code.length != 0) { - revert OnlyConstructorError(); - } + modifier onlyInitialize() { + if (initialized) revert AlreadyInitialized(); + + initialized = true; + emit Initialized(); + _; } + + /// @notice The address of the implementation contract. + /// @dev This is an alias for `implementation` for backwards compatibility. + function IMPL() external view returns (address) { + return implementation; + } } diff --git a/contracts/utils/Proxy.sol b/contracts/utils/Proxy.sol deleted file mode 100644 index 307dee91..00000000 --- a/contracts/utils/Proxy.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.20; - -import "./LibRawResult.sol"; -import "./Implementation.sol"; - -/// @notice Base class for all proxy contracts. -contract Proxy { - using LibRawResult for bytes; - - /// @notice The address of the implementation contract used by this proxy. - Implementation public immutable IMPL; - - // Made `payable` to allow initialized crowdfunds to receive ETH as an - // initial contribution. - constructor(Implementation impl, bytes memory initCallData) payable { - IMPL = impl; - (bool s, bytes memory r) = address(impl).delegatecall(initCallData); - if (!s) { - r.rawRevert(); - } - } - - // Forward all calls to the implementation. - fallback() external payable { - Implementation impl = IMPL; - assembly { - calldatacopy(0x00, 0x00, calldatasize()) - let s := delegatecall(gas(), impl, 0x00, calldatasize(), 0x00, 0) - returndatacopy(0x00, 0x00, returndatasize()) - if iszero(s) { - revert(0x00, returndatasize()) - } - return(0x00, returndatasize()) - } - } -} diff --git a/test/crowdfund/AtomicManualParty.t.sol b/test/crowdfund/AtomicManualParty.t.sol index af40afac..eb162d32 100644 --- a/test/crowdfund/AtomicManualParty.t.sol +++ b/test/crowdfund/AtomicManualParty.t.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8; import { SetupPartyHelper } from "../utils/SetupPartyHelper.sol"; import { AtomicManualParty } from "../../contracts/crowdfund/AtomicManualParty.sol"; import { Party } from "../../contracts/party/Party.sol"; -import { Proxy } from "../../contracts/utils/Proxy.sol"; import { IERC721 } from "../../contracts/tokens/IERC721.sol"; import { MetadataProvider } from "../../contracts/renderers/MetadataProvider.sol"; import { IMetadataProvider } from "../../contracts/renderers/IMetadataProvider.sol"; @@ -79,7 +78,7 @@ contract AtomicManualPartyTest is SetupPartyHelper { // total voting power ignored opts.governance.totalVotingPower = 100; Party atomicParty = atomicManualParty.createParty( - Party(payable(address(Proxy(payable(address(party))).IMPL()))), + Party(payable(address(party.implementation()))), opts, preciousTokens, preciousTokenIds, @@ -127,7 +126,7 @@ contract AtomicManualPartyTest is SetupPartyHelper { vm.expectEmit(false, true, true, true); emit ProviderSet(address(0), IMetadataProvider(address(0))); Party atomicParty = atomicManualParty.createPartyWithMetadata( - Party(payable(address(Proxy(payable(address(party))).IMPL()))), + Party(payable(address(party.implementation()))), opts, preciousTokens, preciousTokenIds, @@ -165,7 +164,7 @@ contract AtomicManualPartyTest is SetupPartyHelper { partyMemberVotingPower[0] = 100; partyMemberVotingPower[1] = 80; - Party partyImpl = Party(payable(address(Proxy(payable(address(party))).IMPL()))); + Party partyImpl = Party(payable(address(party.implementation()))); vm.expectRevert(AtomicManualParty.PartyMembersArityMismatch.selector); atomicManualParty.createParty( partyImpl, @@ -190,7 +189,7 @@ contract AtomicManualPartyTest is SetupPartyHelper { address[] memory partyMembers = new address[](0); uint96[] memory partyMemberVotingPower = new uint96[](0); - Party partyImpl = Party(payable(address(Proxy(payable(address(party))).IMPL()))); + Party partyImpl = Party(payable(address(party.implementation()))); vm.expectRevert(AtomicManualParty.NoPartyMembers.selector); atomicManualParty.createParty( partyImpl, @@ -233,7 +232,7 @@ contract AtomicManualPartyTest is SetupPartyHelper { address(atomicManualParty) ); Party atomicParty = atomicManualParty.createParty( - Party(payable(address(Proxy(payable(address(party))).IMPL()))), + Party(payable(address(party.implementation()))), opts, preciousTokens, preciousTokenIds, @@ -272,10 +271,10 @@ contract AtomicManualPartyTest is SetupPartyHelper { partyMemberVotingPower[0] = 100; partyMemberVotingPower[1] = 80; - Party partyImpl = Party(payable(address(Proxy(payable(address(party))).IMPL()))); + Party partyImpl = Party(payable(address(party.implementation()))); vm.expectRevert(AtomicManualParty.InvalidPartyMember.selector); - Party atomicParty = atomicManualParty.createParty( + atomicManualParty.createParty( partyImpl, opts, preciousTokens, @@ -304,10 +303,10 @@ contract AtomicManualPartyTest is SetupPartyHelper { partyMemberVotingPower[0] = 100; partyMemberVotingPower[1] = 0; - Party partyImpl = Party(payable(address(Proxy(payable(address(party))).IMPL()))); + Party partyImpl = Party(payable(address(party.implementation()))); vm.expectRevert(AtomicManualParty.InvalidPartyMemberVotingPower.selector); - Party atomicParty = atomicManualParty.createParty( + atomicManualParty.createParty( partyImpl, opts, preciousTokens, diff --git a/test/crowdfund/AuctionCrowdfund.t.sol b/test/crowdfund/AuctionCrowdfund.t.sol index c35e87bb..c3b73db6 100644 --- a/test/crowdfund/AuctionCrowdfund.t.sol +++ b/test/crowdfund/AuctionCrowdfund.t.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8; import "forge-std/Test.sol"; +import { Clones } from "openzeppelin/contracts/proxy/Clones.sol"; import "../../contracts/crowdfund/AuctionCrowdfund.sol"; import "../../contracts/globals/Globals.sol"; import "../../contracts/globals/LibGlobals.sol"; -import "../../contracts/utils/Proxy.sol"; import "../../contracts/gatekeepers/AllowListGateKeeper.sol"; import "../../contracts/renderers/RendererStorage.sol"; @@ -18,6 +18,8 @@ import "./MockParty.sol"; import "./MockMarketWrapper.sol"; contract AuctionCrowdfundTest is Test, TestUtils { + using Clones for address; + event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId); event MockPartyFactoryCreateParty( @@ -88,39 +90,30 @@ contract AuctionCrowdfundTest is Test, TestUtils { address[] memory hosts ) private returns (AuctionCrowdfund cf) { governanceOpts.hosts = hosts; - cf = AuctionCrowdfund( - payable( - address( - new Proxy{ value: initialContribution }( - auctionCrowdfundImpl, - abi.encodeCall( - AuctionCrowdfund.initialize, - AuctionCrowdfundBase.AuctionCrowdfundOptions({ - name: defaultName, - symbol: defaultSymbol, - customizationPresetId: 0, - auctionId: auctionId, - market: market, - nftContract: tokenToBuy, - nftTokenId: tokenId, - duration: defaultDuration, - maximumBid: defaultMaxBid, - splitRecipient: defaultSplitRecipient, - splitBps: defaultSplitBps, - initialContributor: address(this), - initialDelegate: defaultInitialDelegate, - minContribution: 0, - maxContribution: type(uint96).max, - gateKeeper: gateKeeper, - gateKeeperId: gateKeeperId, - onlyHostCanBid: onlyHostCanBid, - governanceOpts: governanceOpts, - proposalEngineOpts: proposalEngineOpts - }) - ) - ) - ) - ) + cf = AuctionCrowdfund(payable(address(auctionCrowdfundImpl).clone())); + cf.initialize{ value: initialContribution }( + AuctionCrowdfundBase.AuctionCrowdfundOptions({ + name: defaultName, + symbol: defaultSymbol, + customizationPresetId: 0, + auctionId: auctionId, + market: market, + nftContract: tokenToBuy, + nftTokenId: tokenId, + duration: defaultDuration, + maximumBid: defaultMaxBid, + splitRecipient: defaultSplitRecipient, + splitBps: defaultSplitBps, + initialContributor: address(this), + initialDelegate: defaultInitialDelegate, + minContribution: 0, + maxContribution: type(uint96).max, + gateKeeper: gateKeeper, + gateKeeperId: gateKeeperId, + onlyHostCanBid: onlyHostCanBid, + governanceOpts: governanceOpts, + proposalEngineOpts: proposalEngineOpts + }) ); } @@ -230,7 +223,7 @@ contract AuctionCrowdfundTest is Test, TestUtils { function test_cannotReinitialize() external { (uint256 auctionId, uint256 tokenId) = market.createAuction(1337); AuctionCrowdfund cf = _createCrowdfund(auctionId, tokenId, 0); - vm.expectRevert(abi.encodeWithSelector(Implementation.OnlyConstructorError.selector)); + vm.expectRevert(abi.encodeWithSelector(Implementation.AlreadyInitialized.selector)); AuctionCrowdfundBase.AuctionCrowdfundOptions memory opts; cf.initialize(opts); } @@ -778,6 +771,8 @@ contract AuctionCrowdfundTest is Test, TestUtils { } function test_creation_initialContribution() external { + AuctionCrowdfund cf = AuctionCrowdfund(payable(address(auctionCrowdfundImpl).clone())); + (uint256 auctionId, uint256 tokenId) = market.createAuction(1337); uint256 initialContribution = _randomRange(1, 1 ether); address initialContributor = _randomAddress(); @@ -790,39 +785,29 @@ contract AuctionCrowdfundTest is Test, TestUtils { initialDelegate, 0 ); - AuctionCrowdfund( - payable( - address( - new Proxy{ value: initialContribution }( - auctionCrowdfundImpl, - abi.encodeCall( - AuctionCrowdfund.initialize, - AuctionCrowdfundBase.AuctionCrowdfundOptions({ - name: defaultName, - symbol: defaultSymbol, - customizationPresetId: 0, - auctionId: auctionId, - market: market, - nftContract: tokenToBuy, - nftTokenId: tokenId, - duration: defaultDuration, - maximumBid: defaultMaxBid, - splitRecipient: defaultSplitRecipient, - splitBps: defaultSplitBps, - initialContributor: initialContributor, - initialDelegate: initialDelegate, - minContribution: 0, - maxContribution: type(uint96).max, - gateKeeper: defaultGateKeeper, - gateKeeperId: defaultGateKeeperId, - onlyHostCanBid: false, - governanceOpts: governanceOpts, - proposalEngineOpts: proposalEngineOpts - }) - ) - ) - ) - ) + cf.initialize{ value: initialContribution }( + AuctionCrowdfundBase.AuctionCrowdfundOptions({ + name: defaultName, + symbol: defaultSymbol, + customizationPresetId: 0, + auctionId: auctionId, + market: market, + nftContract: tokenToBuy, + nftTokenId: tokenId, + duration: defaultDuration, + maximumBid: defaultMaxBid, + splitRecipient: defaultSplitRecipient, + splitBps: defaultSplitBps, + initialContributor: initialContributor, + initialDelegate: initialDelegate, + minContribution: 0, + maxContribution: type(uint96).max, + gateKeeper: defaultGateKeeper, + gateKeeperId: defaultGateKeeperId, + onlyHostCanBid: false, + governanceOpts: governanceOpts, + proposalEngineOpts: proposalEngineOpts + }) ); } diff --git a/test/crowdfund/BuyCrowdfund.t.sol b/test/crowdfund/BuyCrowdfund.t.sol index da0efcfc..603950f5 100644 --- a/test/crowdfund/BuyCrowdfund.t.sol +++ b/test/crowdfund/BuyCrowdfund.t.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8; import "forge-std/Test.sol"; +import { Clones } from "openzeppelin/contracts/proxy/Clones.sol"; import "../../contracts/crowdfund/BuyCrowdfund.sol"; import "../../contracts/globals/Globals.sol"; import "../../contracts/globals/LibGlobals.sol"; -import "../../contracts/utils/Proxy.sol"; import "../../contracts/gatekeepers/AllowListGateKeeper.sol"; import "../../contracts/renderers/RendererStorage.sol"; @@ -17,6 +17,8 @@ import "./MockPartyFactory.sol"; import "./TestERC721Vault.sol"; contract BuyCrowdfundTest is Test, TestUtils { + using Clones for address; + event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId); event MockPartyFactoryCreateParty( @@ -85,37 +87,28 @@ contract BuyCrowdfundTest is Test, TestUtils { address[] memory hosts ) private returns (BuyCrowdfund cf) { govOpts.hosts = hosts; - cf = BuyCrowdfund( - payable( - address( - new Proxy{ value: initialContribution }( - buyCrowdfundImpl, - abi.encodeCall( - BuyCrowdfund.initialize, - BuyCrowdfund.BuyCrowdfundOptions({ - name: defaultName, - symbol: defaultSymbol, - customizationPresetId: 0, - nftContract: erc721Vault.token(), - nftTokenId: tokenId, - duration: defaultDuration, - maximumPrice: defaultMaxPrice, - splitRecipient: defaultSplitRecipient, - splitBps: defaultSplitBps, - initialContributor: address(this), - initialDelegate: defaultInitialDelegate, - minContribution: 0, - maxContribution: type(uint96).max, - gateKeeper: gateKeeper, - gateKeeperId: gateKeeperId, - onlyHostCanBuy: onlyHostCanBuy, - governanceOpts: govOpts, - proposalEngineOpts: proposalEngineOpts - }) - ) - ) - ) - ) + cf = BuyCrowdfund(address(buyCrowdfundImpl).clone()); + cf.initialize{ value: initialContribution }( + BuyCrowdfund.BuyCrowdfundOptions({ + name: defaultName, + symbol: defaultSymbol, + customizationPresetId: 0, + nftContract: erc721Vault.token(), + nftTokenId: tokenId, + duration: defaultDuration, + maximumPrice: defaultMaxPrice, + splitRecipient: defaultSplitRecipient, + splitBps: defaultSplitBps, + initialContributor: address(this), + initialDelegate: defaultInitialDelegate, + minContribution: 0, + maxContribution: type(uint96).max, + gateKeeper: gateKeeper, + gateKeeperId: gateKeeperId, + onlyHostCanBuy: onlyHostCanBuy, + governanceOpts: govOpts, + proposalEngineOpts: proposalEngineOpts + }) ); } @@ -509,12 +502,14 @@ contract BuyCrowdfundTest is Test, TestUtils { function testCannotReinitialize() public { uint256 tokenId = erc721Vault.mint(); BuyCrowdfund cf = _createCrowdfund(tokenId, 0); - vm.expectRevert(abi.encodeWithSelector(Implementation.OnlyConstructorError.selector)); + vm.expectRevert(abi.encodeWithSelector(Implementation.AlreadyInitialized.selector)); BuyCrowdfund.BuyCrowdfundOptions memory opts; cf.initialize(opts); } function test_creation_initialContribution() public { + BuyCrowdfund cf = BuyCrowdfund(address(buyCrowdfundImpl).clone()); + uint256 tokenId = erc721Vault.mint(); uint256 initialContribution = _randomRange(1, 1 ether); address initialContributor = _randomAddress(); @@ -527,37 +522,27 @@ contract BuyCrowdfundTest is Test, TestUtils { initialDelegate, 0 ); - BuyCrowdfund( - payable( - address( - new Proxy{ value: initialContribution }( - buyCrowdfundImpl, - abi.encodeCall( - BuyCrowdfund.initialize, - BuyCrowdfund.BuyCrowdfundOptions({ - name: defaultName, - symbol: defaultSymbol, - customizationPresetId: 0, - nftContract: erc721Vault.token(), - nftTokenId: tokenId, - duration: defaultDuration, - maximumPrice: defaultMaxPrice, - splitRecipient: defaultSplitRecipient, - splitBps: defaultSplitBps, - initialContributor: initialContributor, - initialDelegate: initialDelegate, - minContribution: 0, - maxContribution: type(uint96).max, - gateKeeper: defaultGateKeeper, - gateKeeperId: defaultGateKeeperId, - onlyHostCanBuy: false, - governanceOpts: govOpts, - proposalEngineOpts: proposalEngineOpts - }) - ) - ) - ) - ) + cf.initialize{ value: initialContribution }( + BuyCrowdfund.BuyCrowdfundOptions({ + name: defaultName, + symbol: defaultSymbol, + customizationPresetId: 0, + nftContract: erc721Vault.token(), + nftTokenId: tokenId, + duration: defaultDuration, + maximumPrice: defaultMaxPrice, + splitRecipient: defaultSplitRecipient, + splitBps: defaultSplitBps, + initialContributor: initialContributor, + initialDelegate: initialDelegate, + minContribution: 0, + maxContribution: type(uint96).max, + gateKeeper: defaultGateKeeper, + gateKeeperId: defaultGateKeeperId, + onlyHostCanBuy: false, + governanceOpts: govOpts, + proposalEngineOpts: proposalEngineOpts + }) ); } } diff --git a/test/crowdfund/CollectionBatchBuyCrowdfund.t.sol b/test/crowdfund/CollectionBatchBuyCrowdfund.t.sol index 148db75b..ca23f3f7 100644 --- a/test/crowdfund/CollectionBatchBuyCrowdfund.t.sol +++ b/test/crowdfund/CollectionBatchBuyCrowdfund.t.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8; import "forge-std/Test.sol"; +import { Clones } from "openzeppelin/contracts/proxy/Clones.sol"; import "../../contracts/crowdfund/CollectionBatchBuyCrowdfund.sol"; import "../../contracts/renderers/RendererStorage.sol"; import "../../contracts/globals/Globals.sol"; import "../../contracts/globals/LibGlobals.sol"; -import "../../contracts/utils/Proxy.sol"; import "../DummyERC721.sol"; import "../TestUtils.sol"; @@ -17,6 +17,8 @@ import "./MockParty.sol"; import "./TestERC721Vault.sol"; contract CollectionBatchBuyCrowdfundTest is Test, TestUtils { + using Clones for address; + event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId); event MockPartyFactoryCreateParty( @@ -54,36 +56,27 @@ contract CollectionBatchBuyCrowdfundTest is Test, TestUtils { function _createCrowdfund( bytes32 nftTokenIdsMerkleRoot ) internal returns (CollectionBatchBuyCrowdfund cf) { - cf = CollectionBatchBuyCrowdfund( - payable( - address( - new Proxy( - new CollectionBatchBuyCrowdfund(globals), - abi.encodeCall( - CollectionBatchBuyCrowdfund.initialize, - CollectionBatchBuyCrowdfund.CollectionBatchBuyCrowdfundOptions({ - name: "Crowdfund", - symbol: "CF", - customizationPresetId: 0, - nftContract: nftContract, - nftTokenIdsMerkleRoot: nftTokenIdsMerkleRoot, - duration: 1 days, - maximumPrice: maximumPrice, - splitRecipient: payable(address(0)), - splitBps: 0, - initialContributor: address(0), - initialDelegate: address(0), - minContribution: 0, - maxContribution: type(uint96).max, - gateKeeper: IGateKeeper(address(0)), - gateKeeperId: 0, - governanceOpts: govOpts, - proposalEngineOpts: proposalEngineOpts - }) - ) - ) - ) - ) + cf = CollectionBatchBuyCrowdfund(address(new CollectionBatchBuyCrowdfund(globals)).clone()); + cf.initialize( + CollectionBatchBuyCrowdfund.CollectionBatchBuyCrowdfundOptions({ + name: "Crowdfund", + symbol: "CF", + customizationPresetId: 0, + nftContract: nftContract, + nftTokenIdsMerkleRoot: nftTokenIdsMerkleRoot, + duration: 1 days, + maximumPrice: maximumPrice, + splitRecipient: payable(address(0)), + splitBps: 0, + initialContributor: address(0), + initialDelegate: address(0), + minContribution: 0, + maxContribution: type(uint96).max, + gateKeeper: IGateKeeper(address(0)), + gateKeeperId: 0, + governanceOpts: govOpts, + proposalEngineOpts: proposalEngineOpts + }) ); } @@ -94,7 +87,7 @@ contract CollectionBatchBuyCrowdfundTest is Test, TestUtils { function test_cannotReinitialize() public { // Create the crowdfund. CollectionBatchBuyCrowdfund cf = _createCrowdfund(); - vm.expectRevert(abi.encodeWithSelector(Implementation.OnlyConstructorError.selector)); + vm.expectRevert(abi.encodeWithSelector(Implementation.AlreadyInitialized.selector)); CollectionBatchBuyCrowdfund.CollectionBatchBuyCrowdfundOptions memory opts; cf.initialize(opts); } diff --git a/test/crowdfund/CollectionBuyCrowdfund.t.sol b/test/crowdfund/CollectionBuyCrowdfund.t.sol index 036d1815..90c0f010 100644 --- a/test/crowdfund/CollectionBuyCrowdfund.t.sol +++ b/test/crowdfund/CollectionBuyCrowdfund.t.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8; import "forge-std/Test.sol"; +import { Clones } from "openzeppelin/contracts/proxy/Clones.sol"; import "../../contracts/crowdfund/CollectionBuyCrowdfund.sol"; import "../../contracts/renderers/RendererStorage.sol"; import "../../contracts/globals/Globals.sol"; import "../../contracts/globals/LibGlobals.sol"; -import "../../contracts/utils/Proxy.sol"; import "../DummyERC721.sol"; import "../TestUtils.sol"; @@ -17,6 +17,8 @@ import "./MockParty.sol"; import "./TestERC721Vault.sol"; contract CollectionBuyCrowdfundTest is Test, TestUtils { + using Clones for address; + event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId); event MockPartyFactoryCreateParty( @@ -78,35 +80,26 @@ contract CollectionBuyCrowdfundTest is Test, TestUtils { governanceOpts.partyFactory = govOpts.partyFactory; governanceOpts.hosts = hosts; - cf = CollectionBuyCrowdfund( - payable( - address( - new Proxy{ value: initialContribution }( - collectionBuyCrowdfundImpl, - abi.encodeCall( - CollectionBuyCrowdfund.initialize, - CollectionBuyCrowdfund.CollectionBuyCrowdfundOptions({ - name: defaultName, - symbol: defaultSymbol, - customizationPresetId: 0, - nftContract: erc721Vault.token(), - duration: defaultDuration, - maximumPrice: defaultMaxPrice, - splitRecipient: defaultSplitRecipient, - splitBps: defaultSplitBps, - initialContributor: address(this), - initialDelegate: defaultInitialDelegate, - minContribution: 0, - maxContribution: type(uint96).max, - gateKeeper: defaultGateKeeper, - gateKeeperId: defaultGateKeeperId, - governanceOpts: governanceOpts, - proposalEngineOpts: proposalEngineOpts - }) - ) - ) - ) - ) + cf = CollectionBuyCrowdfund(address(collectionBuyCrowdfundImpl).clone()); + cf.initialize{ value: initialContribution }( + CollectionBuyCrowdfund.CollectionBuyCrowdfundOptions({ + name: defaultName, + symbol: defaultSymbol, + customizationPresetId: 0, + nftContract: erc721Vault.token(), + duration: defaultDuration, + maximumPrice: defaultMaxPrice, + splitRecipient: defaultSplitRecipient, + splitBps: defaultSplitBps, + initialContributor: address(this), + initialDelegate: defaultInitialDelegate, + minContribution: 0, + maxContribution: type(uint96).max, + gateKeeper: defaultGateKeeper, + gateKeeperId: defaultGateKeeperId, + governanceOpts: governanceOpts, + proposalEngineOpts: proposalEngineOpts + }) ); } @@ -242,12 +235,16 @@ contract CollectionBuyCrowdfundTest is Test, TestUtils { function testCannotReinitialize() public { (CollectionBuyCrowdfund cf, ) = _createCrowdfund(_toAddressArray(_randomAddress()), 0); - vm.expectRevert(abi.encodeWithSelector(Implementation.OnlyConstructorError.selector)); + vm.expectRevert(abi.encodeWithSelector(Implementation.AlreadyInitialized.selector)); CollectionBuyCrowdfund.CollectionBuyCrowdfundOptions memory opts; cf.initialize(opts); } function test_creation_initialContribution() public { + CollectionBuyCrowdfund cf = CollectionBuyCrowdfund( + address(collectionBuyCrowdfundImpl).clone() + ); + uint256 initialContribution = _randomRange(1, 1 ether); address initialContributor = _randomAddress(); address initialDelegate = _randomAddress(); @@ -261,35 +258,25 @@ contract CollectionBuyCrowdfundTest is Test, TestUtils { initialDelegate, 0 ); - CollectionBuyCrowdfund( - payable( - address( - new Proxy{ value: initialContribution }( - collectionBuyCrowdfundImpl, - abi.encodeCall( - CollectionBuyCrowdfund.initialize, - CollectionBuyCrowdfund.CollectionBuyCrowdfundOptions({ - name: defaultName, - symbol: defaultSymbol, - customizationPresetId: 0, - nftContract: erc721Vault.token(), - duration: defaultDuration, - maximumPrice: defaultMaxPrice, - splitRecipient: defaultSplitRecipient, - splitBps: defaultSplitBps, - initialContributor: initialContributor, - initialDelegate: initialDelegate, - minContribution: 0, - maxContribution: type(uint96).max, - gateKeeper: defaultGateKeeper, - gateKeeperId: defaultGateKeeperId, - governanceOpts: govOpts, - proposalEngineOpts: proposalEngineOpts - }) - ) - ) - ) - ) + cf.initialize{ value: initialContribution }( + CollectionBuyCrowdfund.CollectionBuyCrowdfundOptions({ + name: defaultName, + symbol: defaultSymbol, + customizationPresetId: 0, + nftContract: erc721Vault.token(), + duration: defaultDuration, + maximumPrice: defaultMaxPrice, + splitRecipient: defaultSplitRecipient, + splitBps: defaultSplitBps, + initialContributor: initialContributor, + initialDelegate: initialDelegate, + minContribution: 0, + maxContribution: type(uint96).max, + gateKeeper: defaultGateKeeper, + gateKeeperId: defaultGateKeeperId, + governanceOpts: govOpts, + proposalEngineOpts: proposalEngineOpts + }) ); } } diff --git a/test/crowdfund/Crowdfund.t.sol b/test/crowdfund/Crowdfund.t.sol index 42d22bb0..a98fb757 100644 --- a/test/crowdfund/Crowdfund.t.sol +++ b/test/crowdfund/Crowdfund.t.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8; +import { Clones } from "openzeppelin/contracts/proxy/Clones.sol"; + import "../../contracts/gatekeepers/AllowListGateKeeper.sol"; import "../../contracts/globals/Globals.sol"; import "../../contracts/globals/LibGlobals.sol"; import "../../contracts/renderers/CrowdfundNFTRenderer.sol"; import "../../contracts/renderers/fonts/PixeldroidConsoleFont.sol"; -import "../../contracts/utils/Proxy.sol"; import "../../contracts/utils/EIP165.sol"; import "../DummyERC721.sol"; @@ -29,6 +30,8 @@ contract BadERC721Receiver { } contract CrowdfundTest is LintJSON, TestUtils { + using Clones for address; + event MockPartyFactoryCreateParty( address caller, address[] authorities, @@ -128,32 +131,23 @@ contract CrowdfundTest is LintJSON, TestUtils { address initialDelegate, uint256 customizationPresetId ) private returns (TestableCrowdfund cf) { - cf = TestableCrowdfund( - payable( - new Proxy{ value: initialContribution }( - Implementation(new TestableCrowdfund(globals)), - abi.encodeCall( - TestableCrowdfund.initialize, - ( - Crowdfund.CrowdfundOptions({ - name: defaultName, - symbol: defaultSymbol, - customizationPresetId: customizationPresetId, - splitRecipient: defaultSplitRecipient, - splitBps: defaultSplitBps, - initialContributor: initialContributor, - initialDelegate: initialDelegate, - minContribution: 0, - maxContribution: type(uint96).max, - gateKeeper: gateKeeper, - gateKeeperId: gateKeeperId, - governanceOpts: govOpts, - proposalEngineOpts: proposalEngineOpts - }) - ) - ) - ) - ) + cf = TestableCrowdfund(address(new TestableCrowdfund(globals)).clone()); + cf.initialize{ value: initialContribution }( + Crowdfund.CrowdfundOptions({ + name: defaultName, + symbol: defaultSymbol, + customizationPresetId: customizationPresetId, + splitRecipient: defaultSplitRecipient, + splitBps: defaultSplitBps, + initialContributor: initialContributor, + initialDelegate: initialDelegate, + minContribution: 0, + maxContribution: type(uint96).max, + gateKeeper: gateKeeper, + gateKeeperId: gateKeeperId, + governanceOpts: govOpts, + proposalEngineOpts: proposalEngineOpts + }) ); } @@ -941,37 +935,28 @@ contract CrowdfundTest is LintJSON, TestUtils { } function test_revertIfNullContributor() external { - Implementation impl = Implementation(new TestableCrowdfund(globals)); + address impl = address(new TestableCrowdfund(globals)); + TestableCrowdfund cf = TestableCrowdfund(impl.clone()); // Attempt creating a crowdfund and setting a null address as the // initial contributor. Should revert when it attempts to mint a // contributor NFT to `address(0)`. vm.expectRevert(CrowdfundNFT.InvalidAddressError.selector); - TestableCrowdfund( - payable( - new Proxy{ value: 1 ether }( - impl, - abi.encodeCall( - TestableCrowdfund.initialize, - ( - Crowdfund.CrowdfundOptions({ - name: defaultName, - symbol: defaultSymbol, - customizationPresetId: 0, - splitRecipient: defaultSplitRecipient, - splitBps: defaultSplitBps, - initialContributor: address(0), - initialDelegate: address(this), - minContribution: 0, - maxContribution: type(uint96).max, - gateKeeper: gateKeeper, - gateKeeperId: gateKeeperId, - governanceOpts: govOpts, - proposalEngineOpts: proposalEngineOpts - }) - ) - ) - ) - ) + cf.initialize{ value: 1 ether }( + Crowdfund.CrowdfundOptions({ + name: defaultName, + symbol: defaultSymbol, + customizationPresetId: 0, + splitRecipient: defaultSplitRecipient, + splitBps: defaultSplitBps, + initialContributor: address(0), + initialDelegate: address(this), + minContribution: 0, + maxContribution: type(uint96).max, + gateKeeper: gateKeeper, + gateKeeperId: gateKeeperId, + governanceOpts: govOpts, + proposalEngineOpts: proposalEngineOpts + }) ); } diff --git a/test/crowdfund/FoundationCrowdfundForked.t.sol b/test/crowdfund/FoundationCrowdfundForked.t.sol index 240918c4..0a63d603 100644 --- a/test/crowdfund/FoundationCrowdfundForked.t.sol +++ b/test/crowdfund/FoundationCrowdfundForked.t.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8; import "forge-std/Test.sol"; +import { Clones } from "openzeppelin/contracts/proxy/Clones.sol"; import "../../contracts/crowdfund/AuctionCrowdfund.sol"; import "../../contracts/crowdfund/Crowdfund.sol"; import "../../contracts/globals/Globals.sol"; import "../../contracts/globals/LibGlobals.sol"; -import "../../contracts/utils/Proxy.sol"; import "../../contracts/vendor/markets/IFoundationMarket.sol"; import "../../contracts/renderers/RendererStorage.sol"; @@ -18,6 +18,8 @@ import "../DummyERC721.sol"; import "../TestUtils.sol"; contract FoundationCrowdfundForkedTest is TestUtils { + using Clones for address; + event Won(uint256 bid, Party party); event Lost(); @@ -58,39 +60,30 @@ contract FoundationCrowdfundForkedTest is TestUtils { ); // Create a AuctionCrowdfund crowdfund - cf = AuctionCrowdfund( - payable( - address( - new Proxy( - pbImpl, - abi.encodeCall( - AuctionCrowdfund.initialize, - AuctionCrowdfundBase.AuctionCrowdfundOptions({ - name: "Party", - symbol: "PRTY", - customizationPresetId: 0, - auctionId: auctionId, - market: foundationMarket, - nftContract: nftContract, - nftTokenId: tokenId, - duration: 1 days, - maximumBid: type(uint96).max, - splitRecipient: payable(address(0)), - splitBps: 0, - initialContributor: address(this), - initialDelegate: address(0), - minContribution: 0, - maxContribution: type(uint96).max, - gateKeeper: IGateKeeper(address(0)), - gateKeeperId: 0, - onlyHostCanBid: false, - governanceOpts: govOpts, - proposalEngineOpts: proposalEngineOpts - }) - ) - ) - ) - ) + cf = AuctionCrowdfund(payable(address(pbImpl).clone())); + cf.initialize( + AuctionCrowdfundBase.AuctionCrowdfundOptions({ + name: "Party", + symbol: "PRTY", + customizationPresetId: 0, + auctionId: auctionId, + market: foundationMarket, + nftContract: nftContract, + nftTokenId: tokenId, + duration: 1 days, + maximumBid: type(uint96).max, + splitRecipient: payable(address(0)), + splitBps: 0, + initialContributor: address(this), + initialDelegate: address(0), + minContribution: 0, + maxContribution: type(uint96).max, + gateKeeper: IGateKeeper(address(0)), + gateKeeperId: 0, + onlyHostCanBid: false, + governanceOpts: govOpts, + proposalEngineOpts: proposalEngineOpts + }) ); // Contribute ETH used to bid. diff --git a/test/crowdfund/InitialETHCrowdfund.t.sol b/test/crowdfund/InitialETHCrowdfund.t.sol index 6ca62d2b..bb4d94b8 100644 --- a/test/crowdfund/InitialETHCrowdfund.t.sol +++ b/test/crowdfund/InitialETHCrowdfund.t.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8; import "forge-std/Test.sol"; +import { Clones } from "openzeppelin/contracts/proxy/Clones.sol"; import "../../contracts/crowdfund/InitialETHCrowdfund.sol"; import "../../contracts/globals/Globals.sol"; -import "../../contracts/utils/Proxy.sol"; import "../../contracts/party/PartyFactory.sol"; import "../../contracts/tokens/ERC721Receiver.sol"; import "../../contracts/renderers/PartyNFTRenderer.sol"; @@ -19,6 +19,8 @@ import "../TestUtils.sol"; import { LintJSON } from "../utils/LintJSON.sol"; contract InitialETHCrowdfundTestBase is LintJSON, TestUtils, ERC721Receiver { + using Clones for address; + event Contributed( address indexed sender, address indexed contributor, @@ -36,6 +38,9 @@ contract InitialETHCrowdfundTestBase is LintJSON, TestUtils, ERC721Receiver { RendererStorage nftRendererStorage; TokenDistributor tokenDistributor; + InitialETHCrowdfund.ETHPartyOptions partyOpts; + InitialETHCrowdfund.InitialETHCrowdfundOptions crowdfundOpts; + constructor() { globals = new Globals(address(this)); partyImpl = new Party(globals); @@ -92,9 +97,9 @@ contract InitialETHCrowdfundTestBase is LintJSON, TestUtils, ERC721Receiver { } function _createCrowdfund( - CreateCrowdfundArgs memory args + CreateCrowdfundArgs memory args, + bool initialize ) internal returns (InitialETHCrowdfund crowdfund) { - InitialETHCrowdfund.InitialETHCrowdfundOptions memory crowdfundOpts; crowdfundOpts.initialContributor = args.initialContributor; crowdfundOpts.initialDelegate = args.initialDelegate; crowdfundOpts.minContribution = args.minContributions; @@ -109,7 +114,6 @@ contract InitialETHCrowdfundTestBase is LintJSON, TestUtils, ERC721Receiver { crowdfundOpts.gateKeeper = args.gateKeeper; crowdfundOpts.gateKeeperId = args.gateKeeperId; - InitialETHCrowdfund.ETHPartyOptions memory partyOpts; partyOpts.name = "Test Party"; partyOpts.symbol = "TEST"; partyOpts.governanceOpts.partyImpl = partyImpl; @@ -120,17 +124,21 @@ contract InitialETHCrowdfundTestBase is LintJSON, TestUtils, ERC721Receiver { partyOpts.governanceOpts.hosts = new address[](1); partyOpts.governanceOpts.hosts[0] = address(this); - crowdfund = InitialETHCrowdfund( - payable( - new Proxy{ value: args.initialContribution }( - initialETHCrowdfundImpl, - abi.encodeCall( - InitialETHCrowdfund.initialize, - (crowdfundOpts, partyOpts, MetadataProvider(address(0)), "") - ) - ) - ) - ); + crowdfund = InitialETHCrowdfund(payable(address(initialETHCrowdfundImpl).clone())); + if (initialize) { + crowdfund.initialize{ value: args.initialContribution }( + crowdfundOpts, + partyOpts, + MetadataProvider(address(0)), + "" + ); + } + } + + function _createCrowdfund( + CreateCrowdfundArgs memory args + ) internal returns (InitialETHCrowdfund) { + return _createCrowdfund(args, true); } } @@ -155,25 +163,23 @@ contract InitialETHCrowdfundTest is InitialETHCrowdfundTestBase { }) ); - InitialETHCrowdfund.InitialETHCrowdfundOptions memory crowdfundOpts; - InitialETHCrowdfund.ETHPartyOptions memory partyOpts; + InitialETHCrowdfund.InitialETHCrowdfundOptions memory defaultCrowdfundOpts; + InitialETHCrowdfund.ETHPartyOptions memory defaultPartyOpts; - vm.expectRevert(Implementation.OnlyConstructorError.selector); - crowdfund.initialize(crowdfundOpts, partyOpts, MetadataProvider(address(0)), ""); + vm.expectRevert(Implementation.AlreadyInitialized.selector); + crowdfund.initialize( + defaultCrowdfundOpts, + defaultPartyOpts, + MetadataProvider(address(0)), + "" + ); } function test_initialization_minTotalContributionsGreaterThanMax() public { uint96 minTotalContributions = 5 ether; uint96 maxTotalContributions = 3 ether; - vm.expectRevert( - abi.encodeWithSelector( - ETHCrowdfundBase.MinGreaterThanMaxError.selector, - minTotalContributions, - maxTotalContributions - ) - ); - _createCrowdfund( + InitialETHCrowdfund crowdfund = _createCrowdfund( CreateCrowdfundArgs({ initialContribution: 0, initialContributor: payable(address(0)), @@ -189,20 +195,22 @@ contract InitialETHCrowdfundTest is InitialETHCrowdfundTestBase { fundingSplitRecipient: payable(address(0)), gateKeeper: IGateKeeper(address(0)), gateKeeperId: bytes12(0) - }) + }), + false ); - } - - function test_initialization_maxTotalContributionsZero() public { - uint96 maxTotalContributions = 0; - vm.expectRevert( abi.encodeWithSelector( - ETHCrowdfundBase.MaxTotalContributionsCannotBeZeroError.selector, + ETHCrowdfundBase.MinGreaterThanMaxError.selector, + minTotalContributions, maxTotalContributions ) ); - _createCrowdfund( + crowdfund.initialize(crowdfundOpts, partyOpts, MetadataProvider(address(0)), ""); + } + + function test_initialization_maxTotalContributionsZero() public { + uint96 maxTotalContributions = 0; + InitialETHCrowdfund crowdfund = _createCrowdfund( CreateCrowdfundArgs({ initialContribution: 0, initialContributor: payable(address(0)), @@ -218,8 +226,16 @@ contract InitialETHCrowdfundTest is InitialETHCrowdfundTestBase { fundingSplitRecipient: payable(address(0)), gateKeeper: IGateKeeper(address(0)), gateKeeperId: bytes12(0) - }) + }), + false ); + vm.expectRevert( + abi.encodeWithSelector( + ETHCrowdfundBase.MaxTotalContributionsCannotBeZeroError.selector, + maxTotalContributions + ) + ); + crowdfund.initialize(crowdfundOpts, partyOpts, MetadataProvider(address(0)), ""); } function test_initialContribution_works() public { diff --git a/test/crowdfund/NounsCrowdfundForked.t.sol b/test/crowdfund/NounsCrowdfundForked.t.sol index 642d155a..a768eb52 100644 --- a/test/crowdfund/NounsCrowdfundForked.t.sol +++ b/test/crowdfund/NounsCrowdfundForked.t.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8; import "forge-std/Test.sol"; +import { Clones } from "openzeppelin/contracts/proxy/Clones.sol"; import "../../contracts/crowdfund/AuctionCrowdfund.sol"; import "../../contracts/crowdfund/Crowdfund.sol"; import "../../contracts/renderers/RendererStorage.sol"; import "../../contracts/globals/Globals.sol"; import "../../contracts/globals/LibGlobals.sol"; -import "../../contracts/utils/Proxy.sol"; import "../../contracts/vendor/markets/INounsAuctionHouse.sol"; import "./MockPartyFactory.sol"; @@ -17,6 +17,8 @@ import "./MockParty.sol"; import "../TestUtils.sol"; contract NounsCrowdfundForkedTest is TestUtils { + using Clones for address; + event Won(uint256 bid, Party party); event Lost(); @@ -51,39 +53,30 @@ contract NounsCrowdfundForkedTest is TestUtils { (tokenId, , , , , ) = nounsAuctionHouse.auction(); // Create a AuctionCrowdfund crowdfund - cf = AuctionCrowdfund( - payable( - address( - new Proxy( - pbImpl, - abi.encodeCall( - AuctionCrowdfund.initialize, - AuctionCrowdfundBase.AuctionCrowdfundOptions({ - name: "Party", - symbol: "PRTY", - customizationPresetId: 0, - auctionId: tokenId, - market: nounsMarket, - nftContract: nounsToken, - nftTokenId: tokenId, - duration: type(uint32).max, - maximumBid: type(uint96).max, - splitRecipient: payable(address(0)), - splitBps: 0, - initialContributor: address(this), - initialDelegate: address(0), - minContribution: 0, - maxContribution: type(uint96).max, - gateKeeper: IGateKeeper(address(0)), - gateKeeperId: 0, - onlyHostCanBid: false, - governanceOpts: govOpts, - proposalEngineOpts: proposalEngineOpts - }) - ) - ) - ) - ) + cf = AuctionCrowdfund(payable(address(pbImpl).clone())); + cf.initialize( + AuctionCrowdfundBase.AuctionCrowdfundOptions({ + name: "Party", + symbol: "PRTY", + customizationPresetId: 0, + auctionId: tokenId, + market: nounsMarket, + nftContract: nounsToken, + nftTokenId: tokenId, + duration: type(uint32).max, + maximumBid: type(uint96).max, + splitRecipient: payable(address(0)), + splitBps: 0, + initialContributor: address(this), + initialDelegate: address(0), + minContribution: 0, + maxContribution: type(uint96).max, + gateKeeper: IGateKeeper(address(0)), + gateKeeperId: 0, + onlyHostCanBid: false, + governanceOpts: govOpts, + proposalEngineOpts: proposalEngineOpts + }) ); // Contribute ETH used to bid. diff --git a/test/crowdfund/OpenseaFulfillOrderForkedTest.t.sol b/test/crowdfund/OpenseaFulfillOrderForkedTest.t.sol index 4851bf8d..40279d34 100644 --- a/test/crowdfund/OpenseaFulfillOrderForkedTest.t.sol +++ b/test/crowdfund/OpenseaFulfillOrderForkedTest.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8; import "forge-std/Test.sol"; +import { Clones } from "openzeppelin/contracts/proxy/Clones.sol"; import "./MockPartyFactory.sol"; import "../TestUtils.sol"; @@ -11,9 +12,10 @@ import "../../contracts/crowdfund/BuyCrowdfund.sol"; import "../../contracts/renderers/RendererStorage.sol"; import "../../contracts/globals/Globals.sol"; import "../../contracts/globals/LibGlobals.sol"; -import "../../contracts/utils/Proxy.sol"; contract OpenseaFulfillOrderForkedTest is Test, TestUtils, OpenseaTestUtils { + using Clones for address; + BuyCrowdfund cf; Crowdfund.FixedGovernanceOpts govOpts; ProposalStorage.ProposalEngineOpts proposalEngineOpts; @@ -42,37 +44,28 @@ contract OpenseaFulfillOrderForkedTest is Test, TestUtils, OpenseaTestUtils { govOpts.partyFactory = partyFactory; // Create a BuyCrowdfund - cf = BuyCrowdfund( - payable( - address( - new Proxy( - buyCrowdfundImpl, - abi.encodeCall( - BuyCrowdfund.initialize, - BuyCrowdfund.BuyCrowdfundOptions({ - name: "Test", - symbol: "TEST", - customizationPresetId: 0, - nftContract: token, - nftTokenId: tokenId, - duration: 7 days, - maximumPrice: type(uint96).max, - splitRecipient: payable(address(0)), - splitBps: 0, - initialContributor: address(0), - initialDelegate: address(0), - minContribution: 0, - maxContribution: type(uint96).max, - gateKeeper: IGateKeeper(address(0)), - gateKeeperId: 0, - onlyHostCanBuy: false, - governanceOpts: govOpts, - proposalEngineOpts: proposalEngineOpts - }) - ) - ) - ) - ) + cf = BuyCrowdfund(address(buyCrowdfundImpl).clone()); + cf.initialize( + BuyCrowdfund.BuyCrowdfundOptions({ + name: "Test", + symbol: "TEST", + customizationPresetId: 0, + nftContract: token, + nftTokenId: tokenId, + duration: 7 days, + maximumPrice: type(uint96).max, + splitRecipient: payable(address(0)), + splitBps: 0, + initialContributor: address(0), + initialDelegate: address(0), + minContribution: 0, + maxContribution: type(uint96).max, + gateKeeper: IGateKeeper(address(0)), + gateKeeperId: 0, + onlyHostCanBuy: false, + governanceOpts: govOpts, + proposalEngineOpts: proposalEngineOpts + }) ); } diff --git a/test/crowdfund/ReraiseETHCrowdfund.t.sol b/test/crowdfund/ReraiseETHCrowdfund.t.sol index 3162c880..5aec1e1c 100644 --- a/test/crowdfund/ReraiseETHCrowdfund.t.sol +++ b/test/crowdfund/ReraiseETHCrowdfund.t.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8; +import { Clones } from "openzeppelin/contracts/proxy/Clones.sol"; + import "../../contracts/crowdfund/ReraiseETHCrowdfund.sol"; import "../../contracts/globals/Globals.sol"; -import "../../contracts/utils/Proxy.sol"; import "../../contracts/party/PartyFactory.sol"; import "../../contracts/tokens/ERC721Receiver.sol"; import "../../contracts/renderers/CrowdfundNFTRenderer.sol"; @@ -15,6 +16,8 @@ import { LintJSON } from "../utils/LintJSON.sol"; import "../TestUtils.sol"; contract ReraiseETHCrowdfundTest is LintJSON, TestUtils, ERC721Receiver { + using Clones for address; + event Transfer(address indexed owner, address indexed to, uint256 indexed tokenId); event Contributed( address indexed sender, @@ -32,6 +35,8 @@ contract ReraiseETHCrowdfundTest is LintJSON, TestUtils, ERC721Receiver { RendererStorage nftRendererStorage; TokenDistributor tokenDistributor; + ETHCrowdfundBase.ETHCrowdfundOptions opts; + constructor() { Globals globals = new Globals(address(this)); @@ -73,9 +78,8 @@ contract ReraiseETHCrowdfundTest is LintJSON, TestUtils, ERC721Receiver { partyOpts.options.governance.hosts = new address[](1); partyOpts.options.governance.hosts[0] = address(this); - party = Party( - payable(new Proxy(new Party(globals), abi.encodeCall(Party.initialize, (partyOpts)))) - ); + party = Party(payable(address(new Party(globals)).clone())); + party.initialize(partyOpts); } struct CreateCrowdfundArgs { @@ -96,9 +100,9 @@ contract ReraiseETHCrowdfundTest is LintJSON, TestUtils, ERC721Receiver { } function _createCrowdfund( - CreateCrowdfundArgs memory args + CreateCrowdfundArgs memory args, + bool initialize ) private returns (ReraiseETHCrowdfund crowdfund) { - ETHCrowdfundBase.ETHCrowdfundOptions memory opts; opts.party = party; opts.initialContributor = args.initialContributor; opts.initialDelegate = args.initialDelegate; @@ -114,19 +118,21 @@ contract ReraiseETHCrowdfundTest is LintJSON, TestUtils, ERC721Receiver { opts.gateKeeper = args.gateKeeper; opts.gateKeeperId = args.gateKeeperId; - crowdfund = ReraiseETHCrowdfund( - payable( - new Proxy{ value: args.initialContribution }( - reraiseETHCrowdfundImpl, - abi.encodeCall(ReraiseETHCrowdfund.initialize, (opts)) - ) - ) - ); + crowdfund = ReraiseETHCrowdfund(address(reraiseETHCrowdfundImpl).clone()); + if (initialize) { + crowdfund.initialize{ value: args.initialContribution }(opts); + } vm.prank(address(party)); party.addAuthority(address(crowdfund)); } + function _createCrowdfund( + CreateCrowdfundArgs memory args + ) private returns (ReraiseETHCrowdfund crowdfund) { + return _createCrowdfund(args, true); + } + function test_initialization_cannotReinitialize() public { ReraiseETHCrowdfund crowdfund = _createCrowdfund( CreateCrowdfundArgs({ @@ -147,24 +153,17 @@ contract ReraiseETHCrowdfundTest is LintJSON, TestUtils, ERC721Receiver { }) ); - ETHCrowdfundBase.ETHCrowdfundOptions memory opts; + ETHCrowdfundBase.ETHCrowdfundOptions memory emptyOpts; - vm.expectRevert(Implementation.OnlyConstructorError.selector); - crowdfund.initialize(opts); + vm.expectRevert(Implementation.AlreadyInitialized.selector); + crowdfund.initialize(emptyOpts); } function test_initialization_minTotalContributionsGreaterThanMax() public { uint96 minTotalContributions = 5 ether; uint96 maxTotalContributions = 3 ether; - vm.expectRevert( - abi.encodeWithSelector( - ETHCrowdfundBase.MinGreaterThanMaxError.selector, - minTotalContributions, - maxTotalContributions - ) - ); - _createCrowdfund( + ReraiseETHCrowdfund crowdfund = _createCrowdfund( CreateCrowdfundArgs({ initialContribution: 0, initialContributor: payable(address(0)), @@ -180,20 +179,23 @@ contract ReraiseETHCrowdfundTest is LintJSON, TestUtils, ERC721Receiver { fundingSplitRecipient: payable(address(0)), gateKeeper: IGateKeeper(address(0)), gateKeeperId: bytes12(0) - }) + }), + false ); - } - - function test_initialization_maxTotalContributionsZero() public { - uint96 maxTotalContributions = 0; - vm.expectRevert( abi.encodeWithSelector( - ETHCrowdfundBase.MaxTotalContributionsCannotBeZeroError.selector, + ETHCrowdfundBase.MinGreaterThanMaxError.selector, + minTotalContributions, maxTotalContributions ) ); - _createCrowdfund( + crowdfund.initialize(opts); + } + + function test_initialization_maxTotalContributionsZero() public { + uint96 maxTotalContributions = 0; + + ReraiseETHCrowdfund crowdfund = _createCrowdfund( CreateCrowdfundArgs({ initialContribution: 0, initialContributor: payable(address(0)), @@ -209,8 +211,16 @@ contract ReraiseETHCrowdfundTest is LintJSON, TestUtils, ERC721Receiver { fundingSplitRecipient: payable(address(0)), gateKeeper: IGateKeeper(address(0)), gateKeeperId: bytes12(0) - }) + }), + false ); + vm.expectRevert( + abi.encodeWithSelector( + ETHCrowdfundBase.MaxTotalContributionsCannotBeZeroError.selector, + maxTotalContributions + ) + ); + crowdfund.initialize(opts); } function test_initialContribution_works() public { @@ -244,36 +254,6 @@ contract ReraiseETHCrowdfundTest is LintJSON, TestUtils, ERC721Receiver { assertEq(crowdfund.pendingVotingPower(initialContributor), initialContribution); } - function test_initialContribution_aboveMaxTotalContribution() public { - address payable initialContributor = payable(_randomAddress()); - address initialDelegate = _randomAddress(); - uint96 initialContribution = 1 ether; - - // Will fail because initial contribution should trigger crowdfund to - // try to finalize a win but it will fail because it is not yet set as - // an authority on the party - vm.expectRevert(PartyGovernanceNFT.OnlyAuthorityError.selector); - // Create crowdfund with initial contribution - _createCrowdfund( - CreateCrowdfundArgs({ - initialContribution: initialContribution, - initialContributor: initialContributor, - initialDelegate: initialDelegate, - minContributions: 0, - maxContributions: type(uint96).max, - disableContributingForExistingCard: false, - minTotalContributions: initialContribution, - maxTotalContributions: initialContribution, - duration: 7 days, - exchangeRateBps: 1e4, - fundingSplitBps: 0, - fundingSplitRecipient: payable(address(0)), - gateKeeper: IGateKeeper(address(0)), - gateKeeperId: bytes12(0) - }) - ); - } - function test_contribute_works() public { ReraiseETHCrowdfund crowdfund = _createCrowdfund( CreateCrowdfundArgs({ diff --git a/test/crowdfund/RollingAuctionCrowdfund.t.sol b/test/crowdfund/RollingAuctionCrowdfund.t.sol index 29838c2d..9c3ded23 100644 --- a/test/crowdfund/RollingAuctionCrowdfund.t.sol +++ b/test/crowdfund/RollingAuctionCrowdfund.t.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8; +import { Clones } from "openzeppelin/contracts/proxy/Clones.sol"; + import "contracts/crowdfund/RollingAuctionCrowdfund.sol"; import "contracts/renderers/RendererStorage.sol"; import "contracts/globals/Globals.sol"; -import "contracts/utils/Proxy.sol"; import "contracts/tokens/ERC721Receiver.sol"; import "./MockPartyFactory.sol"; import "./MockMarketWrapper.sol"; @@ -12,6 +13,8 @@ import "./MockMarketWrapper.sol"; import "../TestUtils.sol"; contract RollingAuctionCrowdfundTest is TestUtils, ERC721Receiver { + using Clones for address; + event Won(uint256 bid, Party party); event Lost(); event AuctionUpdated(uint256 nextNftTokenId, uint256 nextAuctionId, uint256 nextMaximumBid); @@ -64,42 +67,31 @@ contract RollingAuctionCrowdfundTest is TestUtils, ERC721Receiver { function _createCrowdfund(bytes32 allowedAuctionsMerkleRoot) internal { // Create crowdfund - crowdfund = RollingAuctionCrowdfund( - payable( - address( - new Proxy( - rollingAuctionCrowdfundImpl, - abi.encodeCall( - RollingAuctionCrowdfund.initialize, - ( - AuctionCrowdfundBase.AuctionCrowdfundOptions({ - name: "Crowfund", - symbol: "CF", - customizationPresetId: 0, - auctionId: auctionId, - market: market, - nftContract: nftContract, - nftTokenId: tokenId, - duration: 2 days, - maximumBid: type(uint96).max, - splitRecipient: payable(address(0)), - splitBps: 0, - initialContributor: address(this), - initialDelegate: address(this), - minContribution: 0, - maxContribution: type(uint96).max, - gateKeeper: IGateKeeper(address(0)), - gateKeeperId: 0, - onlyHostCanBid: false, - governanceOpts: govOpts, - proposalEngineOpts: proposalEngineOpts - }), - allowedAuctionsMerkleRoot - ) - ) - ) - ) - ) + crowdfund = RollingAuctionCrowdfund(payable(address(rollingAuctionCrowdfundImpl).clone())); + crowdfund.initialize( + AuctionCrowdfundBase.AuctionCrowdfundOptions({ + name: "Crowfund", + symbol: "CF", + customizationPresetId: 0, + auctionId: auctionId, + market: market, + nftContract: nftContract, + nftTokenId: tokenId, + duration: 2 days, + maximumBid: type(uint96).max, + splitRecipient: payable(address(0)), + splitBps: 0, + initialContributor: address(this), + initialDelegate: address(this), + minContribution: 0, + maxContribution: type(uint96).max, + gateKeeper: IGateKeeper(address(0)), + gateKeeperId: 0, + onlyHostCanBid: false, + governanceOpts: govOpts, + proposalEngineOpts: proposalEngineOpts + }), + allowedAuctionsMerkleRoot ); // Contribute enough ETH to play with diff --git a/test/crowdfund/RollingNounsCrowdfundForked.t.sol b/test/crowdfund/RollingNounsCrowdfundForked.t.sol index 42db5ef6..b159f171 100644 --- a/test/crowdfund/RollingNounsCrowdfundForked.t.sol +++ b/test/crowdfund/RollingNounsCrowdfundForked.t.sol @@ -1,11 +1,15 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8; +import { Clones } from "openzeppelin/contracts/proxy/Clones.sol"; + import "./RollingAuctionCrowdfund.t.sol"; import "contracts/vendor/markets/INounsAuctionHouse.sol"; import "contracts/renderers/RendererStorage.sol"; contract RollingNounsCrowdfundForkedTest is RollingAuctionCrowdfundTest { + using Clones for address; + INounsAuctionHouse nounsAuctionHouse; constructor() { @@ -31,42 +35,31 @@ contract RollingNounsCrowdfundForkedTest is RollingAuctionCrowdfundTest { govOpts.partyFactory = partyFactory; // Create crowdfund - crowdfund = RollingAuctionCrowdfund( - payable( - address( - new Proxy( - rollingAuctionCrowdfundImpl, - abi.encodeCall( - RollingAuctionCrowdfund.initialize, - ( - AuctionCrowdfundBase.AuctionCrowdfundOptions({ - name: "Crowfund", - symbol: "CF", - customizationPresetId: 0, - auctionId: auctionId, - market: market, - nftContract: nftContract, - nftTokenId: tokenId, - duration: 7 days, - maximumBid: type(uint96).max, - splitRecipient: payable(address(0)), - splitBps: 0, - initialContributor: address(this), - initialDelegate: address(this), - minContribution: 0, - maxContribution: type(uint96).max, - gateKeeper: IGateKeeper(address(0)), - gateKeeperId: 0, - onlyHostCanBid: false, - governanceOpts: govOpts, - proposalEngineOpts: proposalEngineOpts - }), - bytes32(0) - ) - ) - ) - ) - ) + crowdfund = RollingAuctionCrowdfund(payable(address(rollingAuctionCrowdfundImpl).clone())); + crowdfund.initialize( + AuctionCrowdfundBase.AuctionCrowdfundOptions({ + name: "Crowfund", + symbol: "CF", + customizationPresetId: 0, + auctionId: auctionId, + market: market, + nftContract: nftContract, + nftTokenId: tokenId, + duration: 7 days, + maximumBid: type(uint96).max, + splitRecipient: payable(address(0)), + splitBps: 0, + initialContributor: address(this), + initialDelegate: address(this), + minContribution: 0, + maxContribution: type(uint96).max, + gateKeeper: IGateKeeper(address(0)), + gateKeeperId: 0, + onlyHostCanBid: false, + governanceOpts: govOpts, + proposalEngineOpts: proposalEngineOpts + }), + bytes32(0) ); // Contribute enough ETH to play with diff --git a/test/crowdfund/ZoraCrowdfundForked.t.sol b/test/crowdfund/ZoraCrowdfundForked.t.sol index de861728..801f0aaf 100644 --- a/test/crowdfund/ZoraCrowdfundForked.t.sol +++ b/test/crowdfund/ZoraCrowdfundForked.t.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8; import "forge-std/Test.sol"; +import { Clones } from "openzeppelin/contracts/proxy/Clones.sol"; import "../../contracts/crowdfund/AuctionCrowdfund.sol"; import "../../contracts/crowdfund/Crowdfund.sol"; import "../../contracts/globals/Globals.sol"; import "../../contracts/globals/LibGlobals.sol"; -import "../../contracts/utils/Proxy.sol"; import "../../contracts/vendor/markets/IZoraAuctionHouse.sol"; import "../../contracts/renderers/RendererStorage.sol"; @@ -18,6 +18,8 @@ import "../DummyERC721.sol"; import "../TestUtils.sol"; contract ZoraCrowdfundForkedTest is TestUtils, ERC721Receiver { + using Clones for address; + event Won(uint256 bid, Party party); event Lost(); @@ -61,39 +63,30 @@ contract ZoraCrowdfundForkedTest is TestUtils, ERC721Receiver { ); // Create a AuctionCrowdfund crowdfund - cf = AuctionCrowdfund( - payable( - address( - new Proxy( - pbImpl, - abi.encodeCall( - AuctionCrowdfund.initialize, - AuctionCrowdfundBase.AuctionCrowdfundOptions({ - name: "Party", - symbol: "PRTY", - customizationPresetId: 0, - auctionId: auctionId, - market: zoraMarket, - nftContract: nftContract, - nftTokenId: tokenId, - duration: 1 days, - maximumBid: type(uint96).max, - splitRecipient: payable(address(0)), - splitBps: 0, - initialContributor: address(this), - initialDelegate: address(0), - minContribution: 0, - maxContribution: type(uint96).max, - gateKeeper: IGateKeeper(address(0)), - gateKeeperId: 0, - onlyHostCanBid: false, - governanceOpts: govOpts, - proposalEngineOpts: proposalEngineOpts - }) - ) - ) - ) - ) + cf = AuctionCrowdfund(payable(address(pbImpl).clone())); + cf.initialize( + AuctionCrowdfundBase.AuctionCrowdfundOptions({ + name: "Party", + symbol: "PRTY", + customizationPresetId: 0, + auctionId: auctionId, + market: zoraMarket, + nftContract: nftContract, + nftTokenId: tokenId, + duration: 1 days, + maximumBid: type(uint96).max, + splitRecipient: payable(address(0)), + splitBps: 0, + initialContributor: address(this), + initialDelegate: address(0), + minContribution: 0, + maxContribution: type(uint96).max, + gateKeeper: IGateKeeper(address(0)), + gateKeeperId: 0, + onlyHostCanBid: false, + governanceOpts: govOpts, + proposalEngineOpts: proposalEngineOpts + }) ); // Contribute ETH used to bid. diff --git a/test/party/Party.t.sol b/test/party/Party.t.sol deleted file mode 100644 index 3dacad6e..00000000 --- a/test/party/Party.t.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8; - -import "forge-std/Test.sol"; - -import "../../contracts/party/Party.sol"; -import "../../contracts/globals/Globals.sol"; -import "../../contracts/utils/Proxy.sol"; -import "../DummyERC20.sol"; -import "../DummyERC1155.sol"; -import "../DummyERC721.sol"; -import "../TestUtils.sol"; - -contract PartyTest is Test, TestUtils { - Party partyImpl; - - constructor() { - Globals globals = new Globals(address(this)); - partyImpl = new Party(globals); - } - - function test_cannotReinitialize() external { - Party.PartyInitData memory initData; - Party party = Party( - payable(address(new Proxy(partyImpl, abi.encodeCall(Party.initialize, initData)))) - ); - vm.expectRevert(abi.encodeWithSelector(Implementation.OnlyConstructorError.selector)); - party.initialize(initData); - } -} diff --git a/test/renderers/MetadataRegistry.t.sol b/test/renderers/MetadataRegistry.t.sol index 6e2864ad..9816f1a6 100644 --- a/test/renderers/MetadataRegistry.t.sol +++ b/test/renderers/MetadataRegistry.t.sol @@ -172,7 +172,6 @@ contract MetadataRegistryTest is Test, TestUtils { function test_getMetadata_works() public { MetadataProvider provider = new MetadataProvider(globals); - address registrar = _randomAddress(); address instance = _randomAddress(); // Set the provider diff --git a/test/utils/Implementation.t.sol b/test/utils/Implementation.t.sol index 604cf714..564b588d 100644 --- a/test/utils/Implementation.t.sol +++ b/test/utils/Implementation.t.sol @@ -1,39 +1,38 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8; +import { Clones } from "openzeppelin/contracts/proxy/Clones.sol"; + import "../../contracts/utils/Implementation.sol"; import "../TestUtils.sol"; contract TestableImplementation is Implementation { uint256 public initializeCount; - constructor() { - initialize(); - } - - function initialize() public onlyConstructor { + function initialize() public onlyInitialize { ++initializeCount; } } -contract ReinitializingImplementation is TestableImplementation { - constructor() { - // Attempt to call initialize() again by calling back in. - // This should be a noop call because the contract has no bytecode - // during construction. - address(this).call(abi.encodeCall(this.initialize, ())); - } -} - contract ImplementationTest is Test, TestUtils { - function test_cannotInitializeOutsideOfConstructor() external { - TestableImplementation impl = new TestableImplementation(); - vm.expectRevert(abi.encodeWithSelector(Implementation.OnlyConstructorError.selector)); + using Clones for address; + + TestableImplementation impl = new TestableImplementation(); + + function test_cannotReinitialize_logicContract() external { + impl.initialize(); + vm.expectRevert(abi.encodeWithSelector(Implementation.AlreadyInitialized.selector)); impl.initialize(); } - function test_cannotReenterReinitialize() external { - ReinitializingImplementation impl = new ReinitializingImplementation(); - assertEq(impl.initializeCount(), 1); + function test_cannotReinitialize_proxyContract() external { + TestableImplementation proxy = TestableImplementation(address(impl).clone()); + proxy.initialize(); + vm.expectRevert(abi.encodeWithSelector(Implementation.AlreadyInitialized.selector)); + proxy.initialize(); + } + + function test_implIsAliasForImplementation() external { + assertEq(impl.IMPL(), address(impl)); } } From d7e8a9c4ae7b18fbf4348fd1256e8a6d46975b52 Mon Sep 17 00:00:00 2001 From: Brian Le Date: Thu, 21 Sep 2023 14:37:07 -0500 Subject: [PATCH 2/2] add back test --- test/crowdfund/ReraiseETHCrowdfund.t.sol | 40 ++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/test/crowdfund/ReraiseETHCrowdfund.t.sol b/test/crowdfund/ReraiseETHCrowdfund.t.sol index 5aec1e1c..cc55329b 100644 --- a/test/crowdfund/ReraiseETHCrowdfund.t.sol +++ b/test/crowdfund/ReraiseETHCrowdfund.t.sol @@ -119,12 +119,13 @@ contract ReraiseETHCrowdfundTest is LintJSON, TestUtils, ERC721Receiver { opts.gateKeeperId = args.gateKeeperId; crowdfund = ReraiseETHCrowdfund(address(reraiseETHCrowdfundImpl).clone()); + if (initialize) { crowdfund.initialize{ value: args.initialContribution }(opts); - } - vm.prank(address(party)); - party.addAuthority(address(crowdfund)); + vm.prank(address(party)); + party.addAuthority(address(crowdfund)); + } } function _createCrowdfund( @@ -254,6 +255,39 @@ contract ReraiseETHCrowdfundTest is LintJSON, TestUtils, ERC721Receiver { assertEq(crowdfund.pendingVotingPower(initialContributor), initialContribution); } + function test_initialContribution_aboveMaxTotalContribution() public { + address payable initialContributor = payable(_randomAddress()); + address initialDelegate = _randomAddress(); + uint96 initialContribution = 1 ether; + + // Create crowdfund with initial contribution + ReraiseETHCrowdfund crowdfund = _createCrowdfund( + CreateCrowdfundArgs({ + initialContribution: initialContribution, + initialContributor: initialContributor, + initialDelegate: initialDelegate, + minContributions: 0, + maxContributions: type(uint96).max, + disableContributingForExistingCard: false, + minTotalContributions: initialContribution, + maxTotalContributions: initialContribution, + duration: 7 days, + exchangeRateBps: 1e4, + fundingSplitBps: 0, + fundingSplitRecipient: payable(address(0)), + gateKeeper: IGateKeeper(address(0)), + gateKeeperId: bytes12(0) + }), + false + ); + + // Will fail because initial contribution should trigger crowdfund to + // try to finalize a win but it will fail because it is not yet set as + // an authority on the party + vm.expectRevert(PartyGovernanceNFT.OnlyAuthorityError.selector); + crowdfund.initialize{ value: initialContribution }(opts); + } + function test_contribute_works() public { ReraiseETHCrowdfund crowdfund = _createCrowdfund( CreateCrowdfundArgs({