Skip to content

Commit

Permalink
feat: implement ERC-1167 minimal proxies (#289)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xble authored and arr00 committed Oct 2, 2023
1 parent a4c1d94 commit 32daa9b
Show file tree
Hide file tree
Showing 30 changed files with 571 additions and 754 deletions.
2 changes: 1 addition & 1 deletion contracts/crowdfund/AuctionCrowdfund.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
2 changes: 1 addition & 1 deletion contracts/crowdfund/BuyCrowdfund.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
2 changes: 1 addition & 1 deletion contracts/crowdfund/CollectionBatchBuyCrowdfund.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
4 changes: 1 addition & 3 deletions contracts/crowdfund/CollectionBuyCrowdfund.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
84 changes: 21 additions & 63 deletions contracts/crowdfund/CrowdfundFactory.sol
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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(
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}

Expand Down
2 changes: 1 addition & 1 deletion contracts/crowdfund/InitialETHCrowdfund.sol
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,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);

Expand Down
2 changes: 1 addition & 1 deletion contracts/crowdfund/ReraiseETHCrowdfund.sol
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,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);

Expand Down
2 changes: 1 addition & 1 deletion contracts/crowdfund/RollingAuctionCrowdfund.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion contracts/party/Party.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
16 changes: 6 additions & 10 deletions contracts/party/PartyFactory.sol
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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);
}

Expand Down
2 changes: 1 addition & 1 deletion contracts/proposals/ProposalExecutionEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
31 changes: 22 additions & 9 deletions contracts/utils/Implementation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
37 changes: 0 additions & 37 deletions contracts/utils/Proxy.sol

This file was deleted.

Loading

0 comments on commit 32daa9b

Please sign in to comment.