Skip to content

Commit 2104a1a

Browse files
authoredApr 5, 2024··
Airdrop audit fixes (#634)
* [Q-2] Make processed mapping public * [Q-3] Unused error definition * [Q-4] Use separate events for each airdrop type * [Q-5] Use safeTransferETH instead of low-level call * remove receive and withdraw * cleanup * [Q-1] Missing natspec documentation * [L-1] Airdropping tokens using push or signature-based methods can be griefed * [G-1] Use verifyCalldata to verify Merkle tree * support EIP1271 signatures * revert L-1 * The statement using ECDSA for bytes32 is not needed anymore
1 parent a9e6477 commit 2104a1a

File tree

4 files changed

+485
-183
lines changed

4 files changed

+485
-183
lines changed
 

Diff for: ‎contracts/prebuilts/unaudited/airdrop/Airdrop.sol

+126-36
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import "@solady/src/utils/MerkleProofLib.sol";
1616
import "@solady/src/utils/ECDSA.sol";
1717
import "@solady/src/utils/EIP712.sol";
1818
import "@solady/src/utils/SafeTransferLib.sol";
19+
import "@solady/src/utils/SignatureCheckerLib.sol";
1920

2021
import { Initializable } from "../../../extension/Initializable.sol";
2122
import { Ownable } from "../../../extension/Ownable.sol";
@@ -25,8 +26,6 @@ import "../../../eip/interface/IERC721.sol";
2526
import "../../../eip/interface/IERC1155.sol";
2627

2728
contract 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
}

Diff for: ‎gasreport.txt

+87-135
Original file line numberDiff line numberDiff line change
@@ -1,196 +1,148 @@
1-
Compiling 1 files with 0.8.23
2-
Solc 0.8.23 finished in 22.27s
3-
Compiler run successful with warnings:
4-
Warning (5667): Unused function parameter. Remove or comment out the variable name to silence this warning.
5-
--> contracts/prebuilts/pack/Pack.sol:101:9:
6-
 |
7-
101 | address[] memory _trustedForwarders,
8-
 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
9-
10-
Warning (2018): Function state mutability can be restricted to pure
11-
--> contracts/prebuilts/unaudited/airdrop/Airdrop.sol:395:5:
12-
 |
13-
395 | function _getClaimHashERC20(address _receiver, address _token) private view returns (bytes32) {
14-
 | ^ (Relevant source part starts here and spans across multiple lines).
15-
16-
Warning (2018): Function state mutability can be restricted to pure
17-
--> contracts/prebuilts/unaudited/airdrop/Airdrop.sol:399:5:
18-
 |
19-
399 | function _getClaimHashERC721(address _receiver, address _token, uint256 _tokenId) private view returns (bytes32) {
20-
 | ^ (Relevant source part starts here and spans across multiple lines).
21-
22-
Warning (2018): Function state mutability can be restricted to pure
23-
--> contracts/prebuilts/unaudited/airdrop/Airdrop.sol:403:5:
24-
 |
25-
403 | function _getClaimHashERC1155(address _receiver, address _token, uint256 _tokenId) private view returns (bytes32) {
26-
 | ^ (Relevant source part starts here and spans across multiple lines).
1+
No files changed, compilation skipped
272

3+
Ran 2 tests for src/test/benchmark/MultiwrapBenchmark.t.sol:MultiwrapBenchmarkTest
4+
[PASS] test_benchmark_multiwrap_unwrap() (gas: 152040)
5+
[PASS] test_benchmark_multiwrap_wrap() (gas: 480722)
6+
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 665.73ms (584.46µs CPU time)
287

298
Ran 5 tests for src/test/benchmark/SignatureDropBenchmark.t.sol:SignatureDropBenchmarkTest
309
[PASS] test_benchmark_signatureDrop_claim_five_tokens() (gas: 185688)
3110
[PASS] test_benchmark_signatureDrop_lazyMint() (gas: 147153)
3211
[PASS] test_benchmark_signatureDrop_lazyMint_for_delayed_reveal() (gas: 249057)
3312
[PASS] test_benchmark_signatureDrop_reveal() (gas: 49802)
3413
[PASS] test_benchmark_signatureDrop_setClaimConditions() (gas: 100719)
35-
Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 777.81ms (1.16ms CPU time)
14+
Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 665.92ms (942.96µs CPU time)
3615

3716
Ran 3 tests for src/test/benchmark/EditionStakeBenchmark.t.sol:EditionStakeBenchmarkTest
3817
[PASS] test_benchmark_editionStake_claimRewards() (gas: 98765)
3918
[PASS] test_benchmark_editionStake_stake() (gas: 203676)
4019
[PASS] test_benchmark_editionStake_withdraw() (gas: 94296)
41-
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 777.55ms (1.41ms CPU time)
20+
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 666.96ms (533.96µs CPU time)
4221

4322
Ran 3 tests for src/test/benchmark/NFTStakeBenchmark.t.sol:NFTStakeBenchmarkTest
4423
[PASS] test_benchmark_nftStake_claimRewards() (gas: 99831)
4524
[PASS] test_benchmark_nftStake_stake_five_tokens() (gas: 553577)
4625
[PASS] test_benchmark_nftStake_withdraw() (gas: 96144)
47-
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 781.23ms (899.88µs CPU time)
48-
49-
Ran 3 tests for src/test/benchmark/PackBenchmark.t.sol:PackBenchmarkTest
50-
[PASS] test_benchmark_pack_addPackContents() (gas: 312595)
51-
[PASS] test_benchmark_pack_createPack() (gas: 1419379)
52-
[PASS] test_benchmark_pack_openPack() (gas: 302612)
53-
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 783.66ms (3.44ms CPU time)
26+
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 669.06ms (710.17µs CPU time)
5427

5528
Ran 1 test for src/test/smart-wallet/utils/AABenchmarkPrepare.sol:AABenchmarkPrepare
5629
[PASS] test_prepareBenchmarkFile() (gas: 2955770)
57-
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 797.24ms (20.05ms CPU time)
58-
59-
Ran 14 tests for src/test/benchmark/AccountBenchmark.t.sol:AccountBenchmarkTest
60-
[PASS] test_state_accountReceivesNativeTokens() (gas: 34537)
61-
[PASS] test_state_addAndWithdrawDeposit() (gas: 148780)
62-
[PASS] test_state_contractMetadata() (gas: 114307)
63-
[PASS] test_state_createAccount_viaEntrypoint() (gas: 458192)
64-
[PASS] test_state_createAccount_viaFactory() (gas: 355822)
65-
[PASS] test_state_executeBatchTransaction() (gas: 76066)
66-
[PASS] test_state_executeBatchTransaction_viaAccountSigner() (gas: 488470)
67-
[PASS] test_state_executeBatchTransaction_viaEntrypoint() (gas: 138443)
68-
[PASS] test_state_executeTransaction() (gas: 68891)
69-
[PASS] test_state_executeTransaction_viaAccountSigner() (gas: 471272)
70-
[PASS] test_state_executeTransaction_viaEntrypoint() (gas: 128073)
71-
[PASS] test_state_receiveERC1155NFT() (gas: 66043)
72-
[PASS] test_state_receiveERC721NFT() (gas: 100196)
73-
[PASS] test_state_transferOutsNativeTokens() (gas: 133673)
74-
Suite result: ok. 14 passed; 0 failed; 0 skipped; finished in 798.25ms (21.10ms CPU time)
30+
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 677.51ms (13.32ms CPU time)
7531

7632
Ran 1 test for src/test/benchmark/AirdropERC20Benchmark.t.sol:AirdropERC20BenchmarkTest
7733
[PASS] test_benchmark_airdropERC20_airdrop() (gas: 32443785)
78-
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 809.63ms (27.77ms CPU time)
34+
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 688.85ms (17.93ms CPU time)
7935

8036
Ran 1 test for src/test/benchmark/AirdropERC721Benchmark.t.sol:AirdropERC721BenchmarkTest
8137
[PASS] test_benchmark_airdropERC721_airdrop() (gas: 42241588)
82-
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 818.36ms (26.52ms CPU time)
38+
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 701.05ms (26.57ms CPU time)
8339

84-
Ran 3 tests for src/test/benchmark/PackVRFDirectBenchmark.t.sol:PackVRFDirectBenchmarkTest
85-
[PASS] test_benchmark_packvrf_createPack() (gas: 1392387)
86-
[PASS] test_benchmark_packvrf_openPack() (gas: 150677)
87-
[PASS] test_benchmark_packvrf_openPackAndClaimRewards() (gas: 3621)
88-
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 232.32ms (1.99ms CPU time)
89-
90-
Ran 4 tests for src/test/benchmark/TokenERC1155Benchmark.t.sol:TokenERC1155BenchmarkTest
91-
[PASS] test_benchmark_tokenERC1155_burn() (gas: 30352)
92-
[PASS] test_benchmark_tokenERC1155_mintTo() (gas: 144229)
93-
[PASS] test_benchmark_tokenERC1155_mintWithSignature_pay_with_ERC20() (gas: 307291)
94-
[PASS] test_benchmark_tokenERC1155_mintWithSignature_pay_with_native_token() (gas: 318712)
95-
Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 297.36ms (1.61ms CPU time)
96-
97-
Ran 2 tests for src/test/benchmark/MultiwrapBenchmark.t.sol:MultiwrapBenchmarkTest
98-
[PASS] test_benchmark_multiwrap_unwrap() (gas: 152040)
99-
[PASS] test_benchmark_multiwrap_wrap() (gas: 480722)
100-
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 310.37ms (726.13µs CPU time)
101-
102-
Ran 1 test for src/test/benchmark/AirdropERC1155Benchmark.t.sol:AirdropERC1155BenchmarkTest
103-
[PASS] test_benchmark_airdropERC1155_airdrop() (gas: 38536544)
104-
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 273.46ms (21.60ms CPU time)
40+
Ran 3 tests for src/test/benchmark/PackBenchmark.t.sol:PackBenchmarkTest
41+
[PASS] test_benchmark_pack_addPackContents() (gas: 312595)
42+
[PASS] test_benchmark_pack_createPack() (gas: 1419379)
43+
[PASS] test_benchmark_pack_openPack() (gas: 302612)
44+
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 181.87ms (2.36ms CPU time)
10545

10646
Ran 3 tests for src/test/benchmark/TokenERC20Benchmark.t.sol:TokenERC20BenchmarkTest
10747
[PASS] test_benchmark_tokenERC20_mintTo() (gas: 139513)
10848
[PASS] test_benchmark_tokenERC20_mintWithSignature_pay_with_ERC20() (gas: 221724)
10949
[PASS] test_benchmark_tokenERC20_mintWithSignature_pay_with_native_token() (gas: 228786)
110-
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 339.25ms (3.19ms CPU time)
50+
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 188.65ms (1.10ms CPU time)
11151

11252
Ran 4 tests for src/test/benchmark/TokenERC721Benchmark.t.sol:TokenERC721BenchmarkTest
11353
[PASS] test_benchmark_tokenERC721_burn() (gas: 40392)
11454
[PASS] test_benchmark_tokenERC721_mintTo() (gas: 172834)
11555
[PASS] test_benchmark_tokenERC721_mintWithSignature_pay_with_ERC20() (gas: 301844)
11656
[PASS] test_benchmark_tokenERC721_mintWithSignature_pay_with_native_token() (gas: 308814)
117-
Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 311.67ms (1.86ms CPU time)
57+
Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 192.61ms (1.31ms CPU time)
58+
59+
Ran 4 tests for src/test/benchmark/TokenERC1155Benchmark.t.sol:TokenERC1155BenchmarkTest
60+
[PASS] test_benchmark_tokenERC1155_burn() (gas: 30352)
61+
[PASS] test_benchmark_tokenERC1155_mintTo() (gas: 144229)
62+
[PASS] test_benchmark_tokenERC1155_mintWithSignature_pay_with_ERC20() (gas: 307291)
63+
[PASS] test_benchmark_tokenERC1155_mintWithSignature_pay_with_native_token() (gas: 318712)
64+
Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 225.96ms (1.36ms CPU time)
11865

11966
Ran 3 tests for src/test/benchmark/TokenStakeBenchmark.t.sol:TokenStakeBenchmarkTest
12067
[PASS] test_benchmark_tokenStake_claimRewards() (gas: 101098)
12168
[PASS] test_benchmark_tokenStake_stake() (gas: 195556)
12269
[PASS] test_benchmark_tokenStake_withdraw() (gas: 104792)
123-
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 194.65ms (694.21µs CPU time)
124-
125-
Ran 21 tests for src/test/benchmark/AirdropBenchmark.t.sol:AirdropBenchmarkTest
126-
[PASS] test_benchmark_airdropClaim_erc1155() (gas: 103870)
127-
[PASS] test_benchmark_airdropClaim_erc20() (gas: 108214)
128-
[PASS] test_benchmark_airdropClaim_erc721() (gas: 107404)
129-
[PASS] test_benchmark_airdropPush_erc1155_10() (gas: 366803)
130-
[PASS] test_benchmark_airdropPush_erc1155_100() (gas: 3262938)
131-
[PASS] test_benchmark_airdropPush_erc1155_1000() (gas: 32344939)
132-
[PASS] test_benchmark_airdropPush_erc20_10() (gas: 342387)
133-
[PASS] test_benchmark_airdropPush_erc20_100() (gas: 2972974)
134-
[PASS] test_benchmark_airdropPush_erc20_1000() (gas: 29348844)
135-
[PASS] test_benchmark_airdropPush_erc721_10() (gas: 423239)
136-
[PASS] test_benchmark_airdropPush_erc721_100() (gas: 3833903)
137-
[PASS] test_benchmark_airdropPush_erc721_1000() (gas: 38104588)
138-
[PASS] test_benchmark_airdropSignature_erc115_10() (gas: 415414)
139-
[PASS] test_benchmark_airdropSignature_erc115_100() (gas: 3456815)
140-
[PASS] test_benchmark_airdropSignature_erc115_1000() (gas: 34332958)
141-
[PASS] test_benchmark_airdropSignature_erc20_10() (gas: 388010)
142-
[PASS] test_benchmark_airdropSignature_erc20_100() (gas: 3137606)
143-
[PASS] test_benchmark_airdropSignature_erc20_1000() (gas: 30935300)
144-
[PASS] test_benchmark_airdropSignature_erc721_10() (gas: 468925)
145-
[PASS] test_benchmark_airdropSignature_erc721_100() (gas: 4008367)
146-
[PASS] test_benchmark_airdropSignature_erc721_1000() (gas: 39690834)
147-
Suite result: ok. 21 passed; 0 failed; 0 skipped; finished in 1.63s (1.75s CPU time)
148-
149-
Ran 21 tests for src/test/benchmark/AirdropBenchmarkAlt.t.sol:AirdropBenchmarkAltTest
150-
[PASS] test_benchmark_airdropClaim_erc1155() (gas: 103870)
151-
[PASS] test_benchmark_airdropClaim_erc20() (gas: 108214)
152-
[PASS] test_benchmark_airdropClaim_erc721() (gas: 107404)
153-
[PASS] test_benchmark_airdropPush_erc1155_10() (gas: 366803)
154-
[PASS] test_benchmark_airdropPush_erc1155_100() (gas: 3262938)
155-
[PASS] test_benchmark_airdropPush_erc1155_1000() (gas: 32344939)
156-
[PASS] test_benchmark_airdropPush_erc20_10() (gas: 342387)
157-
[PASS] test_benchmark_airdropPush_erc20_100() (gas: 2972974)
158-
[PASS] test_benchmark_airdropPush_erc20_1000() (gas: 29348844)
159-
[PASS] test_benchmark_airdropPush_erc721_10() (gas: 423239)
160-
[PASS] test_benchmark_airdropPush_erc721_100() (gas: 3833903)
161-
[PASS] test_benchmark_airdropPush_erc721_1000() (gas: 38104588)
162-
[PASS] test_benchmark_airdropSignature_erc115_10() (gas: 415414)
163-
[PASS] test_benchmark_airdropSignature_erc115_100() (gas: 3456815)
164-
[PASS] test_benchmark_airdropSignature_erc115_1000() (gas: 34332958)
165-
[PASS] test_benchmark_airdropSignature_erc20_10() (gas: 388010)
166-
[PASS] test_benchmark_airdropSignature_erc20_100() (gas: 3137606)
167-
[PASS] test_benchmark_airdropSignature_erc20_1000() (gas: 30935300)
168-
[PASS] test_benchmark_airdropSignature_erc721_10() (gas: 468925)
169-
[PASS] test_benchmark_airdropSignature_erc721_100() (gas: 4008367)
170-
[PASS] test_benchmark_airdropSignature_erc721_1000() (gas: 39690834)
171-
Suite result: ok. 21 passed; 0 failed; 0 skipped; finished in 866.76ms (1.41s CPU time)
70+
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 196.93ms (479.46µs CPU time)
71+
72+
Ran 1 test for src/test/benchmark/AirdropERC1155Benchmark.t.sol:AirdropERC1155BenchmarkTest
73+
[PASS] test_benchmark_airdropERC1155_airdrop() (gas: 38536544)
74+
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 341.15ms (19.66ms CPU time)
75+
76+
Ran 3 tests for src/test/benchmark/PackVRFDirectBenchmark.t.sol:PackVRFDirectBenchmarkTest
77+
[PASS] test_benchmark_packvrf_createPack() (gas: 1392387)
78+
[PASS] test_benchmark_packvrf_openPack() (gas: 150677)
79+
[PASS] test_benchmark_packvrf_openPackAndClaimRewards() (gas: 3621)
80+
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 201.65ms (2.27ms CPU time)
81+
82+
Ran 3 tests for src/test/benchmark/DropERC1155Benchmark.t.sol:DropERC1155BenchmarkTest
83+
[PASS] test_benchmark_dropERC1155_claim() (gas: 245552)
84+
[PASS] test_benchmark_dropERC1155_lazyMint() (gas: 146425)
85+
[PASS] test_benchmark_dropERC1155_setClaimConditions_five_conditions() (gas: 525725)
86+
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.20s (706.01ms CPU time)
17287

17388
Ran 2 tests for src/test/benchmark/DropERC20Benchmark.t.sol:DropERC20BenchmarkTest
17489
[PASS] test_benchmark_dropERC20_claim() (gas: 291508)
17590
[PASS] test_benchmark_dropERC20_setClaimConditions_five_conditions() (gas: 530026)
176-
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 915.15ms (767.18ms CPU time)
91+
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 510.75ms (589.86ms CPU time)
92+
93+
Ran 23 tests for src/test/benchmark/AirdropBenchmark.t.sol:AirdropBenchmarkTest
94+
[PASS] test_benchmark_airdropClaim_erc1155() (gas: 105358)
95+
[PASS] test_benchmark_airdropClaim_erc20() (gas: 109724)
96+
[PASS] test_benchmark_airdropClaim_erc721() (gas: 108870)
97+
[PASS] test_benchmark_airdropPush_erc1155ReceiverCompliant() (gas: 82427)
98+
[PASS] test_benchmark_airdropPush_erc1155_10() (gas: 370062)
99+
[PASS] test_benchmark_airdropPush_erc1155_100() (gas: 3266571)
100+
[PASS] test_benchmark_airdropPush_erc1155_1000() (gas: 32348198)
101+
[PASS] test_benchmark_airdropPush_erc20_10() (gas: 345649)
102+
[PASS] test_benchmark_airdropPush_erc20_100() (gas: 2976236)
103+
[PASS] test_benchmark_airdropPush_erc20_1000() (gas: 29352084)
104+
[PASS] test_benchmark_airdropPush_erc721ReceiverCompliant() (gas: 86434)
105+
[PASS] test_benchmark_airdropPush_erc721_10() (gas: 426498)
106+
[PASS] test_benchmark_airdropPush_erc721_100() (gas: 3837162)
107+
[PASS] test_benchmark_airdropPush_erc721_1000() (gas: 38107847)
108+
[PASS] test_benchmark_airdropSignature_erc115_10() (gas: 416712)
109+
[PASS] test_benchmark_airdropSignature_erc115_100() (gas: 3458091)
110+
[PASS] test_benchmark_airdropSignature_erc115_1000() (gas: 34334256)
111+
[PASS] test_benchmark_airdropSignature_erc20_10() (gas: 389286)
112+
[PASS] test_benchmark_airdropSignature_erc20_100() (gas: 3138882)
113+
[PASS] test_benchmark_airdropSignature_erc20_1000() (gas: 30936576)
114+
[PASS] test_benchmark_airdropSignature_erc721_10() (gas: 470201)
115+
[PASS] test_benchmark_airdropSignature_erc721_100() (gas: 4009643)
116+
[PASS] test_benchmark_airdropSignature_erc721_1000() (gas: 39692110)
117+
Suite result: ok. 23 passed; 0 failed; 0 skipped; finished in 1.21s (1.25s CPU time)
177118

178119
Ran 5 tests for src/test/benchmark/DropERC721Benchmark.t.sol:DropERC721BenchmarkTest
179120
[PASS] test_benchmark_dropERC721_claim_five_tokens() (gas: 273303)
180121
[PASS] test_benchmark_dropERC721_lazyMint() (gas: 147052)
181122
[PASS] test_benchmark_dropERC721_lazyMint_for_delayed_reveal() (gas: 248985)
182123
[PASS] test_benchmark_dropERC721_reveal() (gas: 49433)
183124
[PASS] test_benchmark_dropERC721_setClaimConditions_five_conditions() (gas: 529470)
184-
Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 462.45ms (550.25ms CPU time)
125+
Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 466.63ms (512.92ms CPU time)
185126

186-
Ran 3 tests for src/test/benchmark/DropERC1155Benchmark.t.sol:DropERC1155BenchmarkTest
187-
[PASS] test_benchmark_dropERC1155_claim() (gas: 245552)
188-
[PASS] test_benchmark_dropERC1155_lazyMint() (gas: 146425)
189-
[PASS] test_benchmark_dropERC1155_setClaimConditions_five_conditions() (gas: 525725)
190-
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.79s (1.05s CPU time)
127+
Ran 14 tests for src/test/benchmark/AccountBenchmark.t.sol:AccountBenchmarkTest
128+
[PASS] test_state_accountReceivesNativeTokens() (gas: 34537)
129+
[PASS] test_state_addAndWithdrawDeposit() (gas: 148780)
130+
[PASS] test_state_contractMetadata() (gas: 114307)
131+
[PASS] test_state_createAccount_viaEntrypoint() (gas: 458192)
132+
[PASS] test_state_createAccount_viaFactory() (gas: 355822)
133+
[PASS] test_state_executeBatchTransaction() (gas: 76066)
134+
[PASS] test_state_executeBatchTransaction_viaAccountSigner() (gas: 488470)
135+
[PASS] test_state_executeBatchTransaction_viaEntrypoint() (gas: 138443)
136+
[PASS] test_state_executeTransaction() (gas: 68891)
137+
[PASS] test_state_executeTransaction_viaAccountSigner() (gas: 471272)
138+
[PASS] test_state_executeTransaction_viaEntrypoint() (gas: 128073)
139+
[PASS] test_state_receiveERC1155NFT() (gas: 66043)
140+
[PASS] test_state_receiveERC721NFT() (gas: 100196)
141+
[PASS] test_state_transferOutsNativeTokens() (gas: 133673)
142+
Suite result: ok. 14 passed; 0 failed; 0 skipped; finished in 1.32s (26.86ms CPU time)
191143

192144

193-
Ran 20 test suites in 2.00s (13.97s CPU time): 103 tests passed, 0 failed, 0 skipped (103 total tests)
145+
Ran 19 test suites in 1.45s (10.97s CPU time): 84 tests passed, 0 failed, 0 skipped (84 total tests)
194146
test_benchmark_packvrf_openPackAndClaimRewards() (gas: 0 (0.000%))
195147
test_benchmark_pack_createPack() (gas: 6511 (0.461%))
196148
test_benchmark_airdropERC721_airdrop() (gas: 329052 (0.785%))

Diff for: ‎src/test/airdrop/Airdrop.t.sol

+202-12
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,50 @@
11
// SPDX-License-Identifier: Apache-2.0
22
pragma solidity ^0.8.0;
33

4-
import { Airdrop } from "contracts/prebuilts/unaudited/airdrop/Airdrop.sol";
4+
import { Airdrop, SafeTransferLib, ECDSA } from "contracts/prebuilts/unaudited/airdrop/Airdrop.sol";
55

66
// Test imports
77
import { TWProxy } from "contracts/infra/TWProxy.sol";
88
import "../utils/BaseTest.sol";
99

10+
contract MockSmartWallet {
11+
using ECDSA for bytes32;
12+
13+
bytes4 private constant EIP1271_MAGIC_VALUE = 0x1626ba7e;
14+
address private admin;
15+
16+
constructor(address _admin) {
17+
admin = _admin;
18+
}
19+
20+
function isValidSignature(bytes32 _hash, bytes memory _signature) public view returns (bytes4) {
21+
if (_hash.recover(_signature) == admin) {
22+
return EIP1271_MAGIC_VALUE;
23+
}
24+
}
25+
26+
function onERC721Received(address, address, uint256, bytes memory) external pure returns (bytes4) {
27+
return this.onERC721Received.selector;
28+
}
29+
30+
function onERC1155Received(address, address, uint256, uint256, bytes memory) external pure returns (bytes4) {
31+
return this.onERC1155Received.selector;
32+
}
33+
34+
function onERC1155BatchReceived(
35+
address,
36+
address,
37+
uint256[] memory,
38+
uint256[] memory,
39+
bytes memory
40+
) external pure returns (bytes4) {
41+
return this.onERC1155BatchReceived.selector;
42+
}
43+
}
44+
1045
contract AirdropTest is BaseTest {
1146
Airdrop internal airdrop;
47+
MockSmartWallet internal mockSmartWallet;
1248

1349
bytes32 private constant CONTENT_TYPEHASH_ERC20 =
1450
keccak256("AirdropContentERC20(address recipient,uint256 amount)");
@@ -48,6 +84,8 @@ contract AirdropTest is BaseTest {
4884
domainSeparator = keccak256(
4985
abi.encode(TYPE_HASH_EIP712, NAME_HASH, VERSION_HASH, block.chainid, address(airdrop))
5086
);
87+
88+
mockSmartWallet = new MockSmartWallet(signer);
5189
}
5290

5391
function _getContentsERC20(uint256 length) internal pure returns (Airdrop.AirdropContentERC20[] memory contents) {
@@ -228,18 +266,8 @@ contract AirdropTest is BaseTest {
228266
Airdrop.AirdropContentERC20[] memory contents = _getContentsERC20(10);
229267

230268
vm.prank(signer);
231-
vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropFailed.selector));
232-
airdrop.airdropNativeToken{ value: 0 }(contents);
233-
234-
// add some balance to airdrop contract, which it will try sending to recipeints when msg.value zero
235-
vm.deal(address(airdrop), 50 ether);
236-
// should revert
237-
vm.prank(signer);
238-
vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropValueMismatch.selector));
269+
vm.expectRevert(abi.encodeWithSelector(SafeTransferLib.ETHTransferFailed.selector));
239270
airdrop.airdropNativeToken{ value: 0 }(contents);
240-
241-
// contract balance should remain untouched
242-
assertEq(address(airdrop).balance, 50 ether);
243271
}
244272

245273
/*///////////////////////////////////////////////////////////////
@@ -272,6 +300,62 @@ contract AirdropTest is BaseTest {
272300
assertEq(erc20.balanceOf(signer), 100 ether - totalAmount);
273301
}
274302

303+
function test_state_airdropSignature_erc20_eip1271() public {
304+
// set mockSmartWallet as contract owner
305+
vm.prank(signer);
306+
airdrop.setOwner(address(mockSmartWallet));
307+
308+
// mint tokens to mockSmartWallet
309+
erc20.mint(address(mockSmartWallet), 100 ether);
310+
vm.prank(address(mockSmartWallet));
311+
erc20.approve(address(airdrop), 100 ether);
312+
313+
Airdrop.AirdropContentERC20[] memory contents = _getContentsERC20(10);
314+
Airdrop.AirdropRequestERC20 memory req = Airdrop.AirdropRequestERC20({
315+
uid: bytes32(uint256(1)),
316+
tokenAddress: address(erc20),
317+
expirationTimestamp: 1000,
318+
contents: contents
319+
});
320+
321+
// sign with original EOA signer private key
322+
bytes memory signature = _signReqERC20(req, privateKey);
323+
324+
airdrop.airdropERC20WithSignature(req, signature);
325+
326+
uint256 totalAmount;
327+
for (uint256 i = 0; i < contents.length; i++) {
328+
totalAmount += contents[i].amount;
329+
assertEq(erc20.balanceOf(contents[i].recipient), contents[i].amount);
330+
}
331+
assertEq(erc20.balanceOf(address(mockSmartWallet)), 100 ether - totalAmount);
332+
}
333+
334+
function test_revert_airdropSignature_erc20_eip1271_invalidSignature() public {
335+
// set mockSmartWallet as contract owner
336+
vm.prank(signer);
337+
airdrop.setOwner(address(mockSmartWallet));
338+
339+
// mint tokens to mockSmartWallet
340+
erc20.mint(address(mockSmartWallet), 100 ether);
341+
vm.prank(address(mockSmartWallet));
342+
erc20.approve(address(airdrop), 100 ether);
343+
344+
Airdrop.AirdropContentERC20[] memory contents = _getContentsERC20(10);
345+
Airdrop.AirdropRequestERC20 memory req = Airdrop.AirdropRequestERC20({
346+
uid: bytes32(uint256(1)),
347+
tokenAddress: address(erc20),
348+
expirationTimestamp: 1000,
349+
contents: contents
350+
});
351+
352+
// sign with random private key
353+
bytes memory signature = _signReqERC20(req, 123);
354+
355+
vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropRequestInvalidSigner.selector));
356+
airdrop.airdropERC20WithSignature(req, signature);
357+
}
358+
275359
function test_revert_airdropSignature_erc20_expired() public {
276360
erc20.mint(signer, 100 ether);
277361
vm.prank(signer);
@@ -490,6 +574,59 @@ contract AirdropTest is BaseTest {
490574
}
491575
}
492576

577+
function test_state_airdropSignature_erc721_eip1271() public {
578+
// set mockSmartWallet as contract owner
579+
vm.prank(signer);
580+
airdrop.setOwner(address(mockSmartWallet));
581+
582+
// mint tokens to mockSmartWallet
583+
erc721.mint(address(mockSmartWallet), 1000);
584+
vm.prank(address(mockSmartWallet));
585+
erc721.setApprovalForAll(address(airdrop), true);
586+
587+
Airdrop.AirdropContentERC721[] memory contents = _getContentsERC721(10);
588+
Airdrop.AirdropRequestERC721 memory req = Airdrop.AirdropRequestERC721({
589+
uid: bytes32(uint256(1)),
590+
tokenAddress: address(erc721),
591+
expirationTimestamp: 1000,
592+
contents: contents
593+
});
594+
595+
// sign with original EOA signer private key
596+
bytes memory signature = _signReqERC721(req, privateKey);
597+
598+
airdrop.airdropERC721WithSignature(req, signature);
599+
600+
for (uint256 i = 0; i < contents.length; i++) {
601+
assertEq(erc721.ownerOf(contents[i].tokenId), contents[i].recipient);
602+
}
603+
}
604+
605+
function test_revert_airdropSignature_erc721_eip1271_invalidSignature() public {
606+
// set mockSmartWallet as contract owner
607+
vm.prank(signer);
608+
airdrop.setOwner(address(mockSmartWallet));
609+
610+
// mint tokens to mockSmartWallet
611+
erc721.mint(address(mockSmartWallet), 1000);
612+
vm.prank(address(mockSmartWallet));
613+
erc721.setApprovalForAll(address(airdrop), true);
614+
615+
Airdrop.AirdropContentERC721[] memory contents = _getContentsERC721(10);
616+
Airdrop.AirdropRequestERC721 memory req = Airdrop.AirdropRequestERC721({
617+
uid: bytes32(uint256(1)),
618+
tokenAddress: address(erc721),
619+
expirationTimestamp: 1000,
620+
contents: contents
621+
});
622+
623+
// sign with random private key
624+
bytes memory signature = _signReqERC721(req, 123);
625+
626+
vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropRequestInvalidSigner.selector));
627+
airdrop.airdropERC721WithSignature(req, signature);
628+
}
629+
493630
function test_revert_airdropSignature_erc721_expired() public {
494631
erc721.mint(signer, 1000);
495632
vm.prank(signer);
@@ -704,6 +841,59 @@ contract AirdropTest is BaseTest {
704841
}
705842
}
706843

844+
function test_state_airdropSignature_erc1155_eip1271() public {
845+
// set mockSmartWallet as contract owner
846+
vm.prank(signer);
847+
airdrop.setOwner(address(mockSmartWallet));
848+
849+
// mint tokens to mockSmartWallet
850+
erc1155.mint(address(mockSmartWallet), 0, 100 ether);
851+
vm.prank(address(mockSmartWallet));
852+
erc1155.setApprovalForAll(address(airdrop), true);
853+
854+
Airdrop.AirdropContentERC1155[] memory contents = _getContentsERC1155(10);
855+
Airdrop.AirdropRequestERC1155 memory req = Airdrop.AirdropRequestERC1155({
856+
uid: bytes32(uint256(1)),
857+
tokenAddress: address(erc1155),
858+
expirationTimestamp: 1000,
859+
contents: contents
860+
});
861+
862+
// sign with original EOA signer private key
863+
bytes memory signature = _signReqERC1155(req, privateKey);
864+
865+
airdrop.airdropERC1155WithSignature(req, signature);
866+
867+
for (uint256 i = 0; i < contents.length; i++) {
868+
assertEq(erc1155.balanceOf(contents[i].recipient, contents[i].tokenId), contents[i].amount);
869+
}
870+
}
871+
872+
function test_revert_airdropSignature_erc1155_eip1271_invalidSignature() public {
873+
// set mockSmartWallet as contract owner
874+
vm.prank(signer);
875+
airdrop.setOwner(address(mockSmartWallet));
876+
877+
// mint tokens to mockSmartWallet
878+
erc1155.mint(address(mockSmartWallet), 0, 100 ether);
879+
vm.prank(address(mockSmartWallet));
880+
erc1155.setApprovalForAll(address(airdrop), true);
881+
882+
Airdrop.AirdropContentERC1155[] memory contents = _getContentsERC1155(10);
883+
Airdrop.AirdropRequestERC1155 memory req = Airdrop.AirdropRequestERC1155({
884+
uid: bytes32(uint256(1)),
885+
tokenAddress: address(erc1155),
886+
expirationTimestamp: 1000,
887+
contents: contents
888+
});
889+
890+
// sign with random private key
891+
bytes memory signature = _signReqERC1155(req, 123);
892+
893+
vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropRequestInvalidSigner.selector));
894+
airdrop.airdropERC1155WithSignature(req, signature);
895+
}
896+
707897
function test_revert_airdropSignature_erc115_expired() public {
708898
erc1155.mint(signer, 0, 100 ether);
709899
vm.prank(signer);

Diff for: ‎src/test/benchmark/AirdropBenchmark.t.sol

+70
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,41 @@ import { Airdrop } from "contracts/prebuilts/unaudited/airdrop/Airdrop.sol";
77
import { TWProxy } from "contracts/infra/TWProxy.sol";
88
import "../utils/BaseTest.sol";
99

10+
contract ERC721ReceiverCompliant is IERC721Receiver {
11+
function onERC721Received(
12+
address,
13+
address,
14+
uint256,
15+
bytes calldata
16+
) external view virtual override returns (bytes4) {
17+
return this.onERC721Received.selector;
18+
}
19+
}
20+
21+
contract ERC1155ReceiverCompliant is IERC1155Receiver {
22+
function onERC1155Received(
23+
address operator,
24+
address from,
25+
uint256 id,
26+
uint256 value,
27+
bytes calldata data
28+
) external view virtual override returns (bytes4) {
29+
return this.onERC1155Received.selector;
30+
}
31+
32+
function onERC1155BatchReceived(
33+
address operator,
34+
address from,
35+
uint256[] calldata ids,
36+
uint256[] calldata values,
37+
bytes calldata data
38+
) external returns (bytes4) {
39+
return this.onERC1155BatchReceived.selector;
40+
}
41+
42+
function supportsInterface(bytes4 interfaceId) external view returns (bool) {}
43+
}
44+
1045
contract AirdropBenchmarkTest is BaseTest {
1146
Airdrop internal airdrop;
1247

@@ -372,6 +407,23 @@ contract AirdropBenchmarkTest is BaseTest {
372407
airdrop.airdropERC721(address(erc721), contents);
373408
}
374409

410+
function test_benchmark_airdropPush_erc721ReceiverCompliant() public {
411+
vm.pauseGasMetering();
412+
413+
erc721.mint(signer, 100);
414+
vm.prank(signer);
415+
erc721.setApprovalForAll(address(airdrop), true);
416+
417+
Airdrop.AirdropContentERC721[] memory contents = new Airdrop.AirdropContentERC721[](1);
418+
419+
contents[0].recipient = address(new ERC721ReceiverCompliant());
420+
contents[0].tokenId = 0;
421+
422+
vm.prank(signer);
423+
vm.resumeGasMetering();
424+
airdrop.airdropERC721(address(erc721), contents);
425+
}
426+
375427
/*///////////////////////////////////////////////////////////////
376428
Benchmark: Airdrop Signature ERC721
377429
//////////////////////////////////////////////////////////////*/
@@ -520,6 +572,24 @@ contract AirdropBenchmarkTest is BaseTest {
520572
airdrop.airdropERC1155(address(erc1155), contents);
521573
}
522574

575+
function test_benchmark_airdropPush_erc1155ReceiverCompliant() public {
576+
vm.pauseGasMetering();
577+
578+
erc1155.mint(signer, 0, 100 ether);
579+
vm.prank(signer);
580+
erc1155.setApprovalForAll(address(airdrop), true);
581+
582+
Airdrop.AirdropContentERC1155[] memory contents = new Airdrop.AirdropContentERC1155[](1);
583+
584+
contents[0].recipient = address(new ERC1155ReceiverCompliant());
585+
contents[0].tokenId = 0;
586+
contents[0].amount = 100;
587+
588+
vm.prank(signer);
589+
vm.resumeGasMetering();
590+
airdrop.airdropERC1155(address(erc1155), contents);
591+
}
592+
523593
/*///////////////////////////////////////////////////////////////
524594
Benchmark: Airdrop Signature ERC1155
525595
//////////////////////////////////////////////////////////////*/

0 commit comments

Comments
 (0)
Please sign in to comment.