-
Notifications
You must be signed in to change notification settings - Fork 556
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
Airdrop #628
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
a874e4d
Airdrop
kumaryash90 1ed800a
airdrop with signature
kumaryash90 6fae75a
benchmark tests
kumaryash90 e9704e5
Merge branch 'main' into yash/airdrop-combined
jakeloo 0b84c84
Partial changes
jakeloo aa163a5
remove unused
kumaryash90 556e78f
remove failsafe and refund from eth airdrop
kumaryash90 65dd7cf
reorg
kumaryash90 14553e2
no sig airdrop for eth
kumaryash90 0b51a7f
claim condition id and reset
kumaryash90 bfbb385
cleanup
kumaryash90 ed78b5c
prefix ERC
jakeloo d5eb80a
fix
kumaryash90 731bda9
unit tests wip
kumaryash90 9b7f494
rename
kumaryash90 c205d93
update tests
kumaryash90 0439309
Update
jakeloo 9f16b8a
receive and withdraw functions
kumaryash90 a08405c
cleanup
kumaryash90 0a8755d
fix uid check
kumaryash90 6367762
test revert cases
kumaryash90 a43526d
update claim hash generation and merkle root checks
kumaryash90 489d535
tests
kumaryash90 f5bc183
gasreport
kumaryash90 3db324d
Add onlyOwner for airdrop functions
jakeloo 150fbd1
isClaimed view function
kumaryash90 17df1dc
remove conditionId param from isClaimed
kumaryash90 e4f83a6
events
kumaryash90 ea526c3
update isClaimed
jakeloo d364ddc
merge main
kumaryash90 770874d
update dependencies
kumaryash90 9fe8f19
fix
kumaryash90 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.