Skip to content

Airdrop #628

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

Merged
merged 32 commits into from
Mar 25, 2024
Merged
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a874e4d
Airdrop
kumaryash90 Mar 18, 2024
1ed800a
airdrop with signature
kumaryash90 Mar 19, 2024
6fae75a
benchmark tests
kumaryash90 Mar 19, 2024
e9704e5
Merge branch 'main' into yash/airdrop-combined
jakeloo Mar 20, 2024
0b84c84
Partial changes
jakeloo Mar 20, 2024
aa163a5
remove unused
kumaryash90 Mar 21, 2024
556e78f
remove failsafe and refund from eth airdrop
kumaryash90 Mar 21, 2024
65dd7cf
reorg
kumaryash90 Mar 21, 2024
14553e2
no sig airdrop for eth
kumaryash90 Mar 21, 2024
0b51a7f
claim condition id and reset
kumaryash90 Mar 21, 2024
bfbb385
cleanup
kumaryash90 Mar 21, 2024
ed78b5c
prefix ERC
jakeloo Mar 21, 2024
d5eb80a
fix
kumaryash90 Mar 21, 2024
731bda9
unit tests wip
kumaryash90 Mar 21, 2024
9b7f494
rename
kumaryash90 Mar 22, 2024
c205d93
update tests
kumaryash90 Mar 22, 2024
0439309
Update
jakeloo Mar 22, 2024
9f16b8a
receive and withdraw functions
kumaryash90 Mar 23, 2024
a08405c
cleanup
kumaryash90 Mar 23, 2024
0a8755d
fix uid check
kumaryash90 Mar 25, 2024
6367762
test revert cases
kumaryash90 Mar 25, 2024
a43526d
update claim hash generation and merkle root checks
kumaryash90 Mar 25, 2024
489d535
tests
kumaryash90 Mar 25, 2024
f5bc183
gasreport
kumaryash90 Mar 25, 2024
3db324d
Add onlyOwner for airdrop functions
jakeloo Mar 25, 2024
150fbd1
isClaimed view function
kumaryash90 Mar 25, 2024
17df1dc
remove conditionId param from isClaimed
kumaryash90 Mar 25, 2024
e4f83a6
events
kumaryash90 Mar 25, 2024
ea526c3
update isClaimed
jakeloo Mar 25, 2024
d364ddc
merge main
kumaryash90 Mar 25, 2024
770874d
update dependencies
kumaryash90 Mar 25, 2024
9fe8f19
fix
kumaryash90 Mar 25, 2024
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
279 changes: 279 additions & 0 deletions contracts/prebuilts/unaudited/airdrop/Airdrop.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.11;

/// @author thirdweb

// $$\ $$\ $$\ $$\ $$\
// $$ | $$ | \__| $$ | $$ |
// $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$$$$$$ |$$\ $$\ $$\ $$$$$$\ $$$$$$$\
// \_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$ |$$ | $$ | $$ |$$ __$$\ $$ __$$\
// $$ | $$ | $$ |$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ |
// $$ |$$\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ |
// \$$$$ |$$ | $$ |$$ |$$ | \$$$$$$$ |\$$$$$\$$$$ |\$$$$$$$\ $$$$$$$ |
// \____/ \__| \__|\__|\__| \_______| \_____\____/ \_______|\_______/

import { Initializable } from "../../../extension/Initializable.sol";
import { Ownable } from "../../../extension/Ownable.sol";

import "../../../eip/interface/IERC721.sol";
import "../../../eip/interface/IERC1155.sol";
import "../../../lib/MerkleProof.sol";
import "../../../lib/CurrencyTransferLib.sol";

contract Airdrop is Initializable, Ownable {
/*///////////////////////////////////////////////////////////////
State & structs
//////////////////////////////////////////////////////////////*/

// ERC20 claimable
address public tokenAddress20;
mapping(address => bytes32) public merkleRoot20;
mapping(address => bool) public claimed20;

// Native token claimable
bytes32 public merkleRootETH;
mapping(address => bool) public claimedETH;

// ERC721 claimable
address public tokenAddress721;
mapping(address => bytes32) public merkleRoot721;
mapping(uint256 => bool) public claimed721;

// ERC1155 claimable
address public tokenAddress1155;
mapping(address => bytes32) public merkleRoot1155;
mapping(uint256 => mapping(address => bool)) public claimed1155;

struct AirdropContent20 {
address recipient;
uint256 amount;
}

struct AirdropContent721 {
address recipient;
uint256 tokenId;
}

struct AirdropContent1155 {
address recipient;
uint256 tokenId;
uint256 amount;
}

/*///////////////////////////////////////////////////////////////
Errors
//////////////////////////////////////////////////////////////*/

error AirdropInvalidProof();
error AirdropFailed();
error AirdropNoMerkleRoot();
error AirdropValueMismatch();

/*///////////////////////////////////////////////////////////////
Constructor
//////////////////////////////////////////////////////////////*/

constructor() {
_disableInitializers();
}

function initialize(address _defaultAdmin) external initializer {
_setupOwner(_defaultAdmin);
}

/*///////////////////////////////////////////////////////////////
Airdrop Push
//////////////////////////////////////////////////////////////*/

function airdropERC20(address _tokenAddress, AirdropContent20[] calldata _contents) external {
address _from = msg.sender;
uint256 len = _contents.length;

for (uint256 i = 0; i < len; ) {
CurrencyTransferLib.transferCurrency(_tokenAddress, _from, _contents[i].recipient, _contents[i].amount);

unchecked {
i += 1;
}
}
}

function airdropNativeToken(AirdropContent20[] calldata _contents) external payable {
uint256 len = _contents.length;

uint256 nativeTokenAmount;
uint256 refundAmount;

for (uint256 i = 0; i < len; ) {
nativeTokenAmount += _contents[i].amount;

if (nativeTokenAmount > msg.value) {
revert AirdropValueMismatch();
}

(bool success, ) = _contents[i].recipient.call{ value: _contents[i].amount }("");

if (!success) {
refundAmount += _contents[i].amount;
}

unchecked {
i += 1;
}
}

if (nativeTokenAmount != msg.value) {
revert AirdropValueMismatch();
}

if (refundAmount > 0) {
// refund failed payments' amount to sender address
// solhint-disable avoid-low-level-calls
// slither-disable-next-line low-level-calls
(bool refundSuccess, ) = msg.sender.call{ value: refundAmount }("");
}
}

function airdrop721(address _tokenAddress, AirdropContent721[] calldata _contents) external {
address _from = msg.sender;

uint256 len = _contents.length;

for (uint256 i = 0; i < len; ) {
IERC721(_tokenAddress).safeTransferFrom(_from, _contents[i].recipient, _contents[i].tokenId);

unchecked {
i += 1;
}
}
}

function airdropERC1155(address _tokenAddress, AirdropContent1155[] calldata _contents) external {
address _from = msg.sender;

uint256 len = _contents.length;

for (uint256 i = 0; i < len; ) {
IERC1155(_tokenAddress).safeTransferFrom(
_from,
_contents[i].recipient,
_contents[i].tokenId,
_contents[i].amount,
""
);

unchecked {
i += 1;
}
}
}

/*///////////////////////////////////////////////////////////////
Airdrop Claimable
//////////////////////////////////////////////////////////////*/

function claim20(address _token, address _receiver, uint256 _quantity, bytes32[] calldata _proofs) external {
bytes32 _merkleRoot = merkleRoot20[_token];

if (_merkleRoot == bytes32(0)) {
revert AirdropNoMerkleRoot();
}

bool valid;
(valid, ) = MerkleProof.verify(_proofs, _merkleRoot, keccak256(abi.encodePacked(msg.sender, _quantity)));

if (!valid) {
revert AirdropInvalidProof();
}

claimed20[msg.sender] = true;

CurrencyTransferLib.transferCurrency(tokenAddress20, owner(), _receiver, _quantity);
}

function claimETH(address _receiver, uint256 _quantity, bytes32[] calldata _proofs) external {
bool valid;
(valid, ) = MerkleProof.verify(_proofs, merkleRootETH, keccak256(abi.encodePacked(msg.sender, _quantity)));

if (!valid) {
revert AirdropInvalidProof();
}

claimedETH[msg.sender] = true;

(bool success, ) = _receiver.call{ value: _quantity }("");
if (!success) revert AirdropFailed();
}

function claim721(address _token, address _receiver, uint256 _tokenId, bytes32[] calldata _proofs) external {
bytes32 _merkleRoot = merkleRoot721[_token];

if (_merkleRoot == bytes32(0)) {
revert AirdropNoMerkleRoot();
}

bool valid;
(valid, ) = MerkleProof.verify(_proofs, _merkleRoot, keccak256(abi.encodePacked(msg.sender, _tokenId)));

if (!valid) {
revert AirdropInvalidProof();
}

claimed721[_tokenId] = true;

IERC721(tokenAddress721).safeTransferFrom(owner(), _receiver, _tokenId);
}

function claim1155(
address _token,
address _receiver,
uint256 _tokenId,
uint256 _quantity,
bytes32[] calldata _proofs
) external {
bytes32 _merkleRoot = merkleRoot1155[_token];

if (_merkleRoot == bytes32(0)) {
revert AirdropNoMerkleRoot();
}

bool valid;
(valid, ) = MerkleProof.verify(_proofs, _merkleRoot, keccak256(abi.encodePacked(msg.sender, _quantity)));

if (!valid) {
revert AirdropInvalidProof();
}

claimed1155[_tokenId][msg.sender] = true;

IERC1155(tokenAddress1155).safeTransferFrom(owner(), _receiver, _tokenId, _quantity, "");
}

/*///////////////////////////////////////////////////////////////
Setter functions
//////////////////////////////////////////////////////////////*/

function setMerkleRoot20(address _token, bytes32 _merkleRoot) external onlyOwner {
merkleRoot20[_token] = _merkleRoot;
}

function setMerkleRootETH(bytes32 _merkleRoot) external onlyOwner {
merkleRootETH = _merkleRoot;
}

function setMerkleRoot721(address _token, bytes32 _merkleRoot) external onlyOwner {
merkleRoot721[_token] = _merkleRoot;
}

function setMerkleRoot1155(address _token, bytes32 _merkleRoot) external onlyOwner {
merkleRoot1155[_token] = _merkleRoot;
}

/*///////////////////////////////////////////////////////////////
Miscellaneous
//////////////////////////////////////////////////////////////*/

function _canSetOwner() internal view virtual override returns (bool) {
return msg.sender == owner();
}
}