Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
0xble committed Mar 20, 2024
1 parent fcaf780 commit bdd51d7
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 7 deletions.
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@
[submodule "lib/party-addresses"]
path = lib/party-addresses
url = https://github.com/PartyDAO/party-addresses
[submodule "lib/v2-periphery"]
path = lib/v2-periphery
url = https://github.com/Uniswap/v2-periphery
[submodule "lib/v2-core"]
path = lib/v2-core
url = https://github.com/Uniswap/v2-core
126 changes: 123 additions & 3 deletions contracts/crowdfund/ERC20LaunchCrowdfund.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ import { LibSafeCast } from "../utils/LibSafeCast.sol";
import { Party, PartyGovernance } from "../party/Party.sol";
import { Crowdfund } from "../crowdfund/Crowdfund.sol";
import { MetadataProvider } from "../renderers/MetadataProvider.sol";
import { GovernableERC20, ERC20 } from "../tokens/GovernableERC20.sol";
import { IGateKeeper } from "../gatekeepers/IGateKeeper.sol";
import { IGlobals } from "../globals/IGlobals.sol";
import { IERC721 } from "../tokens/IERC721.sol";
import { ITokenDistributor, IERC20 } from "../distribution/ITokenDistributor.sol";
import { IUniswapV2Router02 } from "uniswap-v2-periphery/interfaces/IUniswapV2Router02.sol";
import { IUniswapV2Factory } from "uniswap-v2-core/interfaces/IUniswapV2Factory.sol";

/// @notice A crowdfund for raising the initial funds for new parties.
/// Unlike other crowdfunds that are started for the purpose of
Expand Down Expand Up @@ -62,6 +66,22 @@ contract ERC20LaunchCrowdfund is ETHCrowdfundBase {
address[] authorities;
}

// TODO: Add comments
// TODO: Pack storage?
struct ERC20LaunchOptions {
// The name of the ERC20 token launched.
string name;
// The symbol of the ERC20 token launched.
string symbol;
// The total supply to mint for the ERC20 token.
uint256 numTokensForDistribution;
// The number of tokens to send to an arbitrary recipient.
uint256 numTokensForRecipient;
// The number of tokens to use for the Uniswap LP pair.
uint256 numTokensForLP;
address recipient;
}

struct BatchContributeArgs {
// IDs of cards to credit the contributions to. When set to 0, it means
// a new one should be minted.
Expand Down Expand Up @@ -97,9 +117,36 @@ contract ERC20LaunchCrowdfund is ETHCrowdfundBase {
}

event Refunded(address indexed contributor, uint256 indexed tokenId, uint256 amount);

// Set the `Globals` contract.
constructor(IGlobals globals) ETHCrowdfundBase(globals) {}
event ERC20Created(ERC20 indexed token, Party indexed party, ERC20LaunchOptions opts);

error InvalidTokenDistribution();

// TODO: Pack storage?
uint16 public immutable FEE_BPS;
address payable public immutable FEE_RECIPIENT;
ITokenDistributor public immutable TOKEN_DISTRIBUTOR;
IUniswapV2Router02 public immutable UNISWAP_V2_ROUTER;
IUniswapV2Factory public immutable UNISWAP_V2_FACTORY;
address public immutable WETH;

ERC20LaunchOptions public tokenOpts;

constructor(
IGlobals globals,
uint16 feeBps,
address payable feeRecipient,
ITokenDistributor tokenDistributor,
IUniswapV2Router02 uniswapV2Router,
IUniswapV2Factory uniswapV2Factory,
address weth
) ETHCrowdfundBase(globals) {
FEE_BPS = feeBps;
FEE_RECIPIENT = feeRecipient;
TOKEN_DISTRIBUTOR = tokenDistributor;
UNISWAP_V2_ROUTER = uniswapV2Router;
UNISWAP_V2_FACTORY = uniswapV2Factory;
WETH = weth;
}

/// @notice Initializer to be called prior to using the contract.
/// @param crowdfundOpts Options to initialize the crowdfund with.
Expand All @@ -110,9 +157,21 @@ contract ERC20LaunchCrowdfund is ETHCrowdfundBase {
function initialize(
InitialETHCrowdfundOptions memory crowdfundOpts,
ETHPartyOptions memory partyOpts,
ERC20LaunchOptions memory _tokenOpts,
MetadataProvider customMetadataProvider,
bytes memory customMetadata
) external payable onlyInitialize {
if (
_tokenOpts.numTokensForDistribution +
_tokenOpts.numTokensForRecipient +
_tokenOpts.numTokensForLP !=
_tokenOpts.totalSupply
) {
revert InvalidTokenDistribution();
}

tokenOpts = _tokenOpts;

// Create party the initial crowdfund will be for.
Party party_ = _createParty(partyOpts, customMetadataProvider, customMetadata);

Expand Down Expand Up @@ -456,4 +515,65 @@ contract ERC20LaunchCrowdfund is ETHCrowdfundBase {
);
}
}

function _finalize(uint96 totalContributions_) internal override {
Party _party = party;

// Finalize the crowdfund.
delete expiry;

// Transfer funding split to recipient if applicable.
uint16 fundingSplitBps_ = fundingSplitBps;
if (fundingSplitBps_ > 0) {
// Assuming fundingSplitBps_ <= 1e4, this cannot overflow uint96
totalContributions_ -= uint96((uint256(totalContributions_) * fundingSplitBps_) / 1e4);
}

// Update the party's total voting power.
uint96 newVotingPower = _calculateContributionToVotingPower(totalContributions_);
_party.increaseTotalVotingPower(newVotingPower);

emit Finalized();

ERC20LaunchOptions memory _tokenOpts = tokenOpts;

// Create token
ERC20 token = new GovernableERC20(
_tokenOpts.name,
_tokenOpts.symbol,
_tokenOpts.totalSupply,
address(this)
);

// Create distribution
token.transfer(address(TOKEN_DISTRIBUTOR), _tokenOpts.numTokensForDistribution);
TOKEN_DISTRIBUTOR.createErc20Distribution(
IERC20(address(token)),
_party,
payable(address(0)),
0
);

// Take fee
uint256 ethValue = msg.value;
uint256 feeAmount = (ethValue * FEE_BPS) / 1e4;
payable(FEE_RECIPIENT).transfer(feeAmount);

// Create locked LP pair
uint256 numETHForLP = ethValue - feeAmount;
token.approve(address(UNISWAP_V2_ROUTER), _tokenOpts.numTokensForLP);
UNISWAP_V2_ROUTER.addLiquidityETH{ value: numETHForLP }(
address(token),
_tokenOpts.numTokensForLP,
_tokenOpts.numTokensForLP,
numETHForLP,
address(0), // Burn LP position
block.timestamp + 10 minutes
);

// Transfer tokens to recipient
token.transfer(_tokenOpts.recipient, _tokenOpts.numTokensForRecipient);

emit ERC20Created(token, _party, _tokenOpts);
}
}
2 changes: 1 addition & 1 deletion contracts/crowdfund/ETHCrowdfundBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ abstract contract ETHCrowdfundBase is Implementation {

function _calculateContributionToVotingPower(
uint96 contribution
) private view returns (uint96) {
) internal view returns (uint96) {
return contribution.mulDivDown(exchangeRate, 1e18).safeCastUint256ToUint96();
}

Expand Down
35 changes: 35 additions & 0 deletions contracts/tokens/GovernableERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import "openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Permit.sol";
import "openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Votes.sol";

contract GovernableERC20 is ERC20, ERC20Permit, ERC20Votes {
constructor(
string memory _name,
string memory _symbol,
uint256 _totalSupply,
address _receiver
) ERC20(_name, _symbol) ERC20Permit(_name) {
_mint(_receiver, _totalSupply);
}

// Default to self-delegation if no delegate is set. This enables snapshots
// to work as expected, otherwise when transferring votes to undelegated addresses
// the votes would not be moved (see `Votes._moveDelegateVotes`).
function delegates(address account) public view override returns (address) {
address delegate = super.delegates(account);
return delegate == address(0) ? account : delegate;
}

// The following functions are overrides required by Solidity.

function _update(address from, address to, uint256 value) internal override(ERC20, ERC20Votes) {
super._update(from, to, value);
}

function nonces(address owner) public view override(ERC20Permit, Nonces) returns (uint256) {
return super.nonces(owner);
}
}
2 changes: 1 addition & 1 deletion lib/party-addresses
Submodule party-addresses updated 310 files
1 change: 1 addition & 0 deletions lib/v2-core
Submodule v2-core added at 4dd590
1 change: 1 addition & 0 deletions lib/v2-periphery
Submodule v2-periphery added at 0335e8
6 changes: 5 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
forge-std/=lib/forge-std/src/
openzeppelin/=lib/openzeppelin-contracts/
solmate/=lib/solmate/src/
solmate/=lib/solmate/src/
ds-test/=lib/forge-std/lib/ds-test/src/
party-addresses/=lib/party-addresses/
uniswap-v2-core/=lib/v2-core/contracts/
uniswap-v2-periphery/=lib/v2-periphery/contracts/

0 comments on commit bdd51d7

Please sign in to comment.