Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: ERC20 airdrop tool #27

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@
[submodule "lib/v3-periphery"]
path = lib/v3-periphery
url = https://github.com/ChrisiPK/v3-periphery
[submodule "lib/dropper-util"]
path = lib/dropper-util
url = https://github.com/PartyDAO/dropper-util
1 change: 1 addition & 0 deletions lib/dropper-util
Submodule dropper-util added at d49770
16 changes: 16 additions & 0 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8;

import "forge-std/Script.sol";
import "src/ERC20Airdropper.sol";

contract DeployScript is Script {
function run() external {
vm.startBroadcast();
ERC20Airdropper airdropper = new ERC20Airdropper(
Dropper(address(0x2871e49a08AceE842C8F225bE7BFf9cC311b9F43))
);
console.log(address(airdropper));
vm.stopBroadcast();
}
}
109 changes: 109 additions & 0 deletions src/ERC20Airdropper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

import { GovernableERC20 } from "./GovernableERC20.sol";
import { ERC20 } from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import { Dropper } from "dropper-util/src/Dropper.sol";

contract ERC20Airdropper {
struct TokenArgs {
string name;
string symbol;
// Token metadata image URI that only gets emitted (not stored on-chain)
string image;
// Token metadata description that only gets emitted (not stored on-chain)
string description;
uint256 totalSupply;
address owner;
}

struct DropArgs {
bytes32 merkleRoot;
uint256 totalTokens;
uint40 startTimestamp;
uint40 expirationTimestamp;
address expirationRecipient;
string merkleTreeURI;
string dropDescription;
}

event DropCreated(
uint256 indexed dropId,
address indexed token,
bytes32 merkleRoot,
uint256 totalTokens,
uint40 startTimestamp,
uint40 expirationTimestamp
);

event ERC20Created(
address indexed token,
address indexed owner,
string name,
string symbol,
uint256 totalSupply
);

Dropper public immutable DROPPER;

constructor(Dropper dropper) {
DROPPER = dropper;
}

function createTokenAndAirdrop(
TokenArgs memory tokenArgs,
DropArgs memory dropArgs
) external returns (ERC20 token, uint256 dropId) {
token = createToken(tokenArgs, address(this));

token.approve(address(DROPPER), dropArgs.totalTokens);

dropId = DROPPER.createDrop(
dropArgs.merkleRoot,
dropArgs.totalTokens,
address(token),
dropArgs.startTimestamp,
dropArgs.expirationTimestamp,
dropArgs.expirationRecipient,
dropArgs.merkleTreeURI,
dropArgs.dropDescription
);

emit DropCreated(
dropId,
address(token),
dropArgs.merkleRoot,
dropArgs.totalTokens,
dropArgs.startTimestamp,
dropArgs.expirationTimestamp
);

uint256 remaining = token.balanceOf(address(this));
if (remaining > 0) {
token.transfer(msg.sender, remaining);
}
}

function createToken(
TokenArgs memory tokenArgs,
address receiver
) public returns (ERC20 token) {
token = new GovernableERC20(
tokenArgs.name,
tokenArgs.symbol,
tokenArgs.image,
tokenArgs.description,
tokenArgs.totalSupply,
receiver,
tokenArgs.owner
);

emit ERC20Created(
address(token),
tokenArgs.owner,
tokenArgs.name,
tokenArgs.symbol,
tokenArgs.totalSupply
);
}
}
140 changes: 0 additions & 140 deletions src/ERC20Creator.sol

This file was deleted.

27 changes: 20 additions & 7 deletions src/ERC20CreatorV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ contract ERC20CreatorV3 is IERC721Receiver {
uint256 numTokensForLP; // Number of tokens for the Uniswap V3 LP
}

struct TokenMetadata {
string name;
string symbol;
string image;
string description;
}

event ERC20Created(
address indexed token,
address indexed party,
Expand Down Expand Up @@ -99,16 +106,14 @@ contract ERC20CreatorV3 is IERC721Receiver {
/// @dev The party is assumed to be `msg.sender`
/// @param party The party to allocate the token distribution to
/// @param lpFeeRecipient The address to receive the LP fee
/// @param name The name of the new token
/// @param symbol The symbol of the new token
/// @param metadata Token metadata including name, symbol, image, and description
/// @param config Token distribution configuration. See above for additional information.
/// @param tokenRecipientAddress The address to receive the tokens allocated for the token recipient
/// @return token The address of the newly created token
function createToken(
address party,
address lpFeeRecipient,
string memory name,
string memory symbol,
TokenMetadata memory metadata,
TokenDistributionConfiguration memory config,
address tokenRecipientAddress
) external payable returns (address) {
Expand All @@ -129,7 +134,15 @@ contract ERC20CreatorV3 is IERC721Receiver {
address(
new GovernableERC20{
salt: keccak256(abi.encode(blockhash(block.number - 1), msg.sender))
}(name, symbol, config.totalSupply, address(this))
}(
metadata.name,
metadata.symbol,
metadata.image,
metadata.description,
config.totalSupply,
address(this),
party
)
)
);

Expand Down Expand Up @@ -234,8 +247,8 @@ contract ERC20CreatorV3 is IERC721Receiver {
address(token),
party,
tokenRecipientAddress,
name,
symbol,
metadata.name,
metadata.symbol,
msg.value,
config
);
Expand Down
27 changes: 20 additions & 7 deletions src/GovernableERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,28 @@ pragma solidity ^0.8;

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

contract GovernableERC20 is ERC20Permit, ERC20Votes, Ownable {
event MetadataSet(string image, string description);

contract GovernableERC20 is ERC20Permit, ERC20Votes {
constructor(
string memory _name,
string memory _symbol,
uint256 _totalSupply,
address _receiver
) ERC20(_name, _symbol) ERC20Permit(_name) {
_mint(_receiver, _totalSupply);
string memory name_,
string memory symbol_,
string memory image_,
string memory description_,
uint256 totalSupply_,
address receiver_,
address owner_
) ERC20(name_, symbol_) ERC20Permit(name_) Ownable(owner_) {
_mint(receiver_, totalSupply_);

emit MetadataSet(image_, description_);
}

/// @notice Updates the emitted metadata for the token.
function setMetadata(string memory image, string memory description) external onlyOwner {
emit MetadataSet(image, description);
}

// The following functions are overrides required by Solidity.
Expand Down
Loading
Loading