@@ -16,6 +16,7 @@ import "@solady/src/utils/MerkleProofLib.sol";
1616import "@solady/src/utils/ECDSA.sol " ;
1717import "@solady/src/utils/EIP712.sol " ;
1818import "@solady/src/utils/SafeTransferLib.sol " ;
19+ import "@solady/src/utils/SignatureCheckerLib.sol " ;
1920
2021import { Initializable } from "../../../extension/Initializable.sol " ;
2122import { Ownable } from "../../../extension/Ownable.sol " ;
@@ -25,8 +26,6 @@ import "../../../eip/interface/IERC721.sol";
2526import "../../../eip/interface/IERC1155.sol " ;
2627
2728contract Airdrop is EIP712 , Initializable , Ownable {
28- using ECDSA for bytes32 ;
29-
3029 /*///////////////////////////////////////////////////////////////
3130 State, constants & structs
3231 //////////////////////////////////////////////////////////////*/
@@ -38,7 +37,7 @@ contract Airdrop is EIP712, Initializable, Ownable {
3837 /// @dev conditionId => hash(claimer address, token address, token id [1155]) => has claimed
3938 mapping (uint256 => mapping (bytes32 => bool )) private claimed;
4039 /// @dev Mapping from request UID => whether the request is processed.
41- mapping (bytes32 => bool ) private processed;
40+ mapping (bytes32 => bool ) public processed;
4241
4342 struct AirdropContentERC20 {
4443 address recipient;
@@ -106,19 +105,18 @@ contract Airdrop is EIP712, Initializable, Ownable {
106105
107106 error AirdropInvalidProof ();
108107 error AirdropAlreadyClaimed ();
109- error AirdropFailed ();
110108 error AirdropNoMerkleRoot ();
111109 error AirdropValueMismatch ();
112110 error AirdropRequestExpired (uint256 expirationTimestamp );
113111 error AirdropRequestAlreadyProcessed ();
114112 error AirdropRequestInvalidSigner ();
115- error AirdropInvalidTokenAddress ();
116113
117114 /*///////////////////////////////////////////////////////////////
118115 Events
119116 //////////////////////////////////////////////////////////////*/
120117
121118 event Airdrop (address token );
119+ event AirdropWithSignature (address token );
122120 event AirdropClaimed (address token , address receiver );
123121
124122 /*///////////////////////////////////////////////////////////////
@@ -133,34 +131,25 @@ contract Airdrop is EIP712, Initializable, Ownable {
133131 _setupOwner (_defaultAdmin);
134132 }
135133
136- /*///////////////////////////////////////////////////////////////
137- Receive and withdraw logic
138- //////////////////////////////////////////////////////////////*/
139-
140- receive () external payable {}
141-
142- function withdraw (address _tokenAddress , uint256 _amount ) external onlyOwner {
143- if (_tokenAddress == NATIVE_TOKEN_ADDRESS) {
144- SafeTransferLib.safeTransferETH (msg .sender , _amount);
145- } else {
146- SafeTransferLib.safeTransferFrom (_tokenAddress, address (this ), msg .sender , _amount);
147- }
148- }
149-
150134 /*///////////////////////////////////////////////////////////////
151135 Airdrop Push
152136 //////////////////////////////////////////////////////////////*/
153137
138+ /**
139+ * @notice Lets contract-owner send native token (eth) to a list of addresses.
140+ * @dev Owner should send total airdrop amount as msg.value.
141+ * Can only be called by contract owner.
142+ *
143+ * @param _contents List containing recipients and amounts to airdrop
144+ */
154145 function airdropNativeToken (AirdropContentERC20[] calldata _contents ) external payable onlyOwner {
155146 uint256 len = _contents.length ;
156147 uint256 nativeTokenAmount;
157148
158149 for (uint256 i = 0 ; i < len; i++ ) {
159150 nativeTokenAmount += _contents[i].amount;
160- (bool success , ) = _contents[i].recipient.call { value: _contents[i].amount }("" );
161- if (! success) {
162- revert AirdropFailed ();
163- }
151+
152+ SafeTransferLib.safeTransferETH (_contents[i].recipient, _contents[i].amount);
164153 }
165154
166155 if (nativeTokenAmount != msg .value ) {
@@ -170,6 +159,14 @@ contract Airdrop is EIP712, Initializable, Ownable {
170159 emit Airdrop (NATIVE_TOKEN_ADDRESS);
171160 }
172161
162+ /**
163+ * @notice Lets contract owner send ERC20 tokens to a list of addresses.
164+ * @dev The token-owner should approve total airdrop amount to this contract.
165+ * Can only be called by contract owner.
166+ *
167+ * @param _tokenAddress Address of the ERC20 token being airdropped
168+ * @param _contents List containing recipients and amounts to airdrop
169+ */
173170 function airdropERC20 (address _tokenAddress , AirdropContentERC20[] calldata _contents ) external onlyOwner {
174171 uint256 len = _contents.length ;
175172
@@ -180,6 +177,14 @@ contract Airdrop is EIP712, Initializable, Ownable {
180177 emit Airdrop (_tokenAddress);
181178 }
182179
180+ /**
181+ * @notice Lets contract owner send ERC721 tokens to a list of addresses.
182+ * @dev The token-owner should approve airdrop tokenIds to this contract.
183+ * Can only be called by contract owner.
184+ *
185+ * @param _tokenAddress Address of the ERC721 token being airdropped
186+ * @param _contents List containing recipients and tokenIds to airdrop
187+ */
183188 function airdropERC721 (address _tokenAddress , AirdropContentERC721[] calldata _contents ) external onlyOwner {
184189 uint256 len = _contents.length ;
185190
@@ -190,6 +195,14 @@ contract Airdrop is EIP712, Initializable, Ownable {
190195 emit Airdrop (_tokenAddress);
191196 }
192197
198+ /**
199+ * @notice Lets contract owner send ERC1155 tokens to a list of addresses.
200+ * @dev The token-owner should approve airdrop tokenIds and amounts to this contract.
201+ * Can only be called by contract owner.
202+ *
203+ * @param _tokenAddress Address of the ERC1155 token being airdropped
204+ * @param _contents List containing recipients, tokenIds, and amounts to airdrop
205+ */
193206 function airdropERC1155 (address _tokenAddress , AirdropContentERC1155[] calldata _contents ) external onlyOwner {
194207 uint256 len = _contents.length ;
195208
@@ -210,6 +223,14 @@ contract Airdrop is EIP712, Initializable, Ownable {
210223 Airdrop With Signature
211224 //////////////////////////////////////////////////////////////*/
212225
226+ /**
227+ * @notice Lets contract owner send ERC20 tokens to a list of addresses with EIP-712 signature.
228+ * @dev The token-owner should approve airdrop amounts to this contract.
229+ * Signer should be the contract owner.
230+ *
231+ * @param req Struct containing airdrop contents, uid, and expiration timestamp
232+ * @param signature EIP-712 signature to perform the airdrop
233+ */
213234 function airdropERC20WithSignature (AirdropRequestERC20 calldata req , bytes calldata signature ) external {
214235 // verify expiration timestamp
215236 if (req.expirationTimestamp < block .timestamp ) {
@@ -239,9 +260,17 @@ contract Airdrop is EIP712, Initializable, Ownable {
239260 );
240261 }
241262
242- emit Airdrop (req.tokenAddress);
263+ emit AirdropWithSignature (req.tokenAddress);
243264 }
244265
266+ /**
267+ * @notice Lets contract owner send ERC721 tokens to a list of addresses with EIP-712 signature.
268+ * @dev The token-owner should approve airdrop tokenIds to this contract.
269+ * Signer should be the contract owner.
270+ *
271+ * @param req Struct containing airdrop contents, uid, and expiration timestamp
272+ * @param signature EIP-712 signature to perform the airdrop
273+ */
245274 function airdropERC721WithSignature (AirdropRequestERC721 calldata req , bytes calldata signature ) external {
246275 // verify expiration timestamp
247276 if (req.expirationTimestamp < block .timestamp ) {
@@ -266,9 +295,17 @@ contract Airdrop is EIP712, Initializable, Ownable {
266295 IERC721 (req.tokenAddress).safeTransferFrom (_from, req.contents[i].recipient, req.contents[i].tokenId);
267296 }
268297
269- emit Airdrop (req.tokenAddress);
298+ emit AirdropWithSignature (req.tokenAddress);
270299 }
271300
301+ /**
302+ * @notice Lets contract owner send ERC1155 tokens to a list of addresses with EIP-712 signature.
303+ * @dev The token-owner should approve airdrop tokenIds and amounts to this contract.
304+ * Signer should be the contract owner.
305+ *
306+ * @param req Struct containing airdrop contents, uid, and expiration timestamp
307+ * @param signature EIP-712 signature to perform the airdrop
308+ */
272309 function airdropERC1155WithSignature (AirdropRequestERC1155 calldata req , bytes calldata signature ) external {
273310 // verify expiration timestamp
274311 if (req.expirationTimestamp < block .timestamp ) {
@@ -299,13 +336,23 @@ contract Airdrop is EIP712, Initializable, Ownable {
299336 );
300337 }
301338
302- emit Airdrop (req.tokenAddress);
339+ emit AirdropWithSignature (req.tokenAddress);
303340 }
304341
305342 /*///////////////////////////////////////////////////////////////
306343 Airdrop Claimable
307344 //////////////////////////////////////////////////////////////*/
308345
346+ /**
347+ * @notice Lets allowlisted addresses claim ERC20 airdrop tokens.
348+ * @dev The token-owner should approve total airdrop amount to this contract,
349+ * and set merkle root of allowlisted address for this airdrop.
350+ *
351+ * @param _token Address of ERC20 airdrop token
352+ * @param _receiver Allowlisted address for which the token is being claimed
353+ * @param _quantity Allowlisted quantity of tokens to claim
354+ * @param _proofs Merkle proofs for allowlist verification
355+ */
309356 function claimERC20 (address _token , address _receiver , uint256 _quantity , bytes32 [] calldata _proofs ) external {
310357 bytes32 claimHash = _getClaimHashERC20 (_receiver, _token);
311358 uint256 conditionId = tokenConditionId[_token];
@@ -319,7 +366,7 @@ contract Airdrop is EIP712, Initializable, Ownable {
319366 revert AirdropNoMerkleRoot ();
320367 }
321368
322- bool valid = MerkleProofLib.verify (
369+ bool valid = MerkleProofLib.verifyCalldata (
323370 _proofs,
324371 _tokenMerkleRoot,
325372 keccak256 (abi.encodePacked (_receiver, _quantity))
@@ -335,6 +382,16 @@ contract Airdrop is EIP712, Initializable, Ownable {
335382 emit AirdropClaimed (_token, _receiver);
336383 }
337384
385+ /**
386+ * @notice Lets allowlisted addresses claim ERC721 airdrop tokens.
387+ * @dev The token-owner should approve airdrop tokenIds to this contract,
388+ * and set merkle root of allowlisted address for this airdrop.
389+ *
390+ * @param _token Address of ERC721 airdrop token
391+ * @param _receiver Allowlisted address for which the token is being claimed
392+ * @param _tokenId Allowlisted tokenId to claim
393+ * @param _proofs Merkle proofs for allowlist verification
394+ */
338395 function claimERC721 (address _token , address _receiver , uint256 _tokenId , bytes32 [] calldata _proofs ) external {
339396 bytes32 claimHash = _getClaimHashERC721 (_receiver, _token, _tokenId);
340397 uint256 conditionId = tokenConditionId[_token];
@@ -348,7 +405,11 @@ contract Airdrop is EIP712, Initializable, Ownable {
348405 revert AirdropNoMerkleRoot ();
349406 }
350407
351- bool valid = MerkleProofLib.verify (_proofs, _tokenMerkleRoot, keccak256 (abi.encodePacked (_receiver, _tokenId)));
408+ bool valid = MerkleProofLib.verifyCalldata (
409+ _proofs,
410+ _tokenMerkleRoot,
411+ keccak256 (abi.encodePacked (_receiver, _tokenId))
412+ );
352413 if (! valid) {
353414 revert AirdropInvalidProof ();
354415 }
@@ -360,6 +421,17 @@ contract Airdrop is EIP712, Initializable, Ownable {
360421 emit AirdropClaimed (_token, _receiver);
361422 }
362423
424+ /**
425+ * @notice Lets allowlisted addresses claim ERC1155 airdrop tokens.
426+ * @dev The token-owner should approve tokenIds and total airdrop amounts to this contract,
427+ * and set merkle root of allowlisted address for this airdrop.
428+ *
429+ * @param _token Address of ERC1155 airdrop token
430+ * @param _receiver Allowlisted address for which the token is being claimed
431+ * @param _tokenId Allowlisted tokenId to claim
432+ * @param _quantity Allowlisted quantity of tokens to claim
433+ * @param _proofs Merkle proofs for allowlist verification
434+ */
363435 function claimERC1155 (
364436 address _token ,
365437 address _receiver ,
@@ -379,7 +451,7 @@ contract Airdrop is EIP712, Initializable, Ownable {
379451 revert AirdropNoMerkleRoot ();
380452 }
381453
382- bool valid = MerkleProofLib.verify (
454+ bool valid = MerkleProofLib.verifyCalldata (
383455 _proofs,
384456 _tokenMerkleRoot,
385457 keccak256 (abi.encodePacked (_receiver, _tokenId, _quantity))
@@ -399,6 +471,13 @@ contract Airdrop is EIP712, Initializable, Ownable {
399471 Setter functions
400472 //////////////////////////////////////////////////////////////*/
401473
474+ /**
475+ * @notice Lets contract owner set merkle root (allowlist) for claim based airdrops.
476+ *
477+ * @param _token Address of airdrop token
478+ * @param _tokenMerkleRoot Merkle root of allowlist
479+ * @param _resetClaimStatus Reset claim status / amount claimed so far to zero for all recipients
480+ */
402481 function setMerkleRoot (address _token , bytes32 _tokenMerkleRoot , bool _resetClaimStatus ) external onlyOwner {
403482 if (_resetClaimStatus || tokenConditionId[_token] == 0 ) {
404483 tokenConditionId[_token] += 1 ;
@@ -410,6 +489,7 @@ contract Airdrop is EIP712, Initializable, Ownable {
410489 Miscellaneous
411490 //////////////////////////////////////////////////////////////*/
412491
492+ /// @notice Returns claim status of a receiver for a claim based airdrop
413493 function isClaimed (address _receiver , address _token , uint256 _tokenId ) external view returns (bool ) {
414494 uint256 _conditionId = tokenConditionId[_token];
415495
@@ -425,28 +505,33 @@ contract Airdrop is EIP712, Initializable, Ownable {
425505
426506 return false ;
427507 }
428-
508+ /// @dev Checks whether contract owner can be set in the given execution context.
429509 function _canSetOwner () internal view virtual override returns (bool ) {
430510 return msg .sender == owner ();
431511 }
432512
513+ /// @dev Domain name and version for EIP-712
433514 function _domainNameAndVersion () internal pure override returns (string memory name , string memory version ) {
434515 name = "Airdrop " ;
435516 version = "1 " ;
436517 }
437518
519+ /// @dev Keccak256 hash of receiver and token addresses, for claim based airdrop status tracking
438520 function _getClaimHashERC20 (address _receiver , address _token ) private view returns (bytes32 ) {
439521 return keccak256 (abi.encodePacked (_receiver, _token));
440522 }
441523
524+ /// @dev Keccak256 hash of receiver, token address and tokenId, for claim based airdrop status tracking
442525 function _getClaimHashERC721 (address _receiver , address _token , uint256 _tokenId ) private view returns (bytes32 ) {
443526 return keccak256 (abi.encodePacked (_receiver, _token, _tokenId));
444527 }
445528
529+ /// @dev Keccak256 hash of receiver, token address and tokenId, for claim based airdrop status tracking
446530 function _getClaimHashERC1155 (address _receiver , address _token , uint256 _tokenId ) private view returns (bytes32 ) {
447531 return keccak256 (abi.encodePacked (_receiver, _token, _tokenId));
448532 }
449533
534+ /// @dev Hash nested struct within AirdropRequest___
450535 function _hashContentInfoERC20 (AirdropContentERC20[] calldata contents ) private pure returns (bytes32 ) {
451536 bytes32 [] memory contentHashes = new bytes32 [](contents.length );
452537 for (uint256 i = 0 ; i < contents.length ; i++ ) {
@@ -455,6 +540,7 @@ contract Airdrop is EIP712, Initializable, Ownable {
455540 return keccak256 (abi.encodePacked (contentHashes));
456541 }
457542
543+ /// @dev Hash nested struct within AirdropRequest___
458544 function _hashContentInfoERC721 (AirdropContentERC721[] calldata contents ) private pure returns (bytes32 ) {
459545 bytes32 [] memory contentHashes = new bytes32 [](contents.length );
460546 for (uint256 i = 0 ; i < contents.length ; i++ ) {
@@ -465,6 +551,7 @@ contract Airdrop is EIP712, Initializable, Ownable {
465551 return keccak256 (abi.encodePacked (contentHashes));
466552 }
467553
554+ /// @dev Hash nested struct within AirdropRequest___
468555 function _hashContentInfoERC1155 (AirdropContentERC1155[] calldata contents ) private pure returns (bytes32 ) {
469556 bytes32 [] memory contentHashes = new bytes32 [](contents.length );
470557 for (uint256 i = 0 ; i < contents.length ; i++ ) {
@@ -475,6 +562,7 @@ contract Airdrop is EIP712, Initializable, Ownable {
475562 return keccak256 (abi.encodePacked (contentHashes));
476563 }
477564
565+ /// @dev Verify EIP-712 signature
478566 function _verifyRequestSignerERC20 (
479567 AirdropRequestERC20 calldata req ,
480568 bytes calldata signature
@@ -485,10 +573,11 @@ contract Airdrop is EIP712, Initializable, Ownable {
485573 );
486574
487575 bytes32 digest = _hashTypedData (structHash);
488- address recovered = digest. recover (signature);
489- return recovered == owner ();
576+
577+ return SignatureCheckerLib. isValidSignatureNowCalldata ( owner (), digest, signature );
490578 }
491579
580+ /// @dev Verify EIP-712 signature
492581 function _verifyRequestSignerERC721 (
493582 AirdropRequestERC721 calldata req ,
494583 bytes calldata signature
@@ -499,10 +588,11 @@ contract Airdrop is EIP712, Initializable, Ownable {
499588 );
500589
501590 bytes32 digest = _hashTypedData (structHash);
502- address recovered = digest. recover (signature);
503- return recovered == owner ();
591+
592+ return SignatureCheckerLib. isValidSignatureNowCalldata ( owner (), digest, signature );
504593 }
505594
595+ /// @dev Verify EIP-712 signature
506596 function _verifyRequestSignerERC1155 (
507597 AirdropRequestERC1155 calldata req ,
508598 bytes calldata signature
@@ -513,7 +603,7 @@ contract Airdrop is EIP712, Initializable, Ownable {
513603 );
514604
515605 bytes32 digest = _hashTypedData (structHash);
516- address recovered = digest. recover (signature);
517- return recovered == owner ();
606+
607+ return SignatureCheckerLib. isValidSignatureNowCalldata ( owner (), digest, signature );
518608 }
519609}
0 commit comments