From 2104a1a2ca4962e1195f9a62497e33a7ecd2caad Mon Sep 17 00:00:00 2001 From: Yash <72552910+kumaryash90@users.noreply.github.com> Date: Sat, 6 Apr 2024 04:32:10 +0530 Subject: [PATCH] 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 --- .../prebuilts/unaudited/airdrop/Airdrop.sol | 162 ++++++++++--- gasreport.txt | 222 +++++++----------- src/test/airdrop/Airdrop.t.sol | 214 ++++++++++++++++- src/test/benchmark/AirdropBenchmark.t.sol | 70 ++++++ 4 files changed, 485 insertions(+), 183 deletions(-) diff --git a/contracts/prebuilts/unaudited/airdrop/Airdrop.sol b/contracts/prebuilts/unaudited/airdrop/Airdrop.sol index 9fba1dae6..e7c7d5a76 100644 --- a/contracts/prebuilts/unaudited/airdrop/Airdrop.sol +++ b/contracts/prebuilts/unaudited/airdrop/Airdrop.sol @@ -16,6 +16,7 @@ import "@solady/src/utils/MerkleProofLib.sol"; import "@solady/src/utils/ECDSA.sol"; import "@solady/src/utils/EIP712.sol"; import "@solady/src/utils/SafeTransferLib.sol"; +import "@solady/src/utils/SignatureCheckerLib.sol"; import { Initializable } from "../../../extension/Initializable.sol"; import { Ownable } from "../../../extension/Ownable.sol"; @@ -25,8 +26,6 @@ import "../../../eip/interface/IERC721.sol"; import "../../../eip/interface/IERC1155.sol"; contract Airdrop is EIP712, Initializable, Ownable { - using ECDSA for bytes32; - /*/////////////////////////////////////////////////////////////// State, constants & structs //////////////////////////////////////////////////////////////*/ @@ -38,7 +37,7 @@ contract Airdrop is EIP712, Initializable, Ownable { /// @dev conditionId => hash(claimer address, token address, token id [1155]) => has claimed mapping(uint256 => mapping(bytes32 => bool)) private claimed; /// @dev Mapping from request UID => whether the request is processed. - mapping(bytes32 => bool) private processed; + mapping(bytes32 => bool) public processed; struct AirdropContentERC20 { address recipient; @@ -106,19 +105,18 @@ contract Airdrop is EIP712, Initializable, Ownable { error AirdropInvalidProof(); error AirdropAlreadyClaimed(); - error AirdropFailed(); error AirdropNoMerkleRoot(); error AirdropValueMismatch(); error AirdropRequestExpired(uint256 expirationTimestamp); error AirdropRequestAlreadyProcessed(); error AirdropRequestInvalidSigner(); - error AirdropInvalidTokenAddress(); /*/////////////////////////////////////////////////////////////// Events //////////////////////////////////////////////////////////////*/ event Airdrop(address token); + event AirdropWithSignature(address token); event AirdropClaimed(address token, address receiver); /*/////////////////////////////////////////////////////////////// @@ -133,34 +131,25 @@ contract Airdrop is EIP712, Initializable, Ownable { _setupOwner(_defaultAdmin); } - /*/////////////////////////////////////////////////////////////// - Receive and withdraw logic - //////////////////////////////////////////////////////////////*/ - - receive() external payable {} - - function withdraw(address _tokenAddress, uint256 _amount) external onlyOwner { - if (_tokenAddress == NATIVE_TOKEN_ADDRESS) { - SafeTransferLib.safeTransferETH(msg.sender, _amount); - } else { - SafeTransferLib.safeTransferFrom(_tokenAddress, address(this), msg.sender, _amount); - } - } - /*/////////////////////////////////////////////////////////////// Airdrop Push //////////////////////////////////////////////////////////////*/ + /** + * @notice Lets contract-owner send native token (eth) to a list of addresses. + * @dev Owner should send total airdrop amount as msg.value. + * Can only be called by contract owner. + * + * @param _contents List containing recipients and amounts to airdrop + */ function airdropNativeToken(AirdropContentERC20[] calldata _contents) external payable onlyOwner { uint256 len = _contents.length; uint256 nativeTokenAmount; for (uint256 i = 0; i < len; i++) { nativeTokenAmount += _contents[i].amount; - (bool success, ) = _contents[i].recipient.call{ value: _contents[i].amount }(""); - if (!success) { - revert AirdropFailed(); - } + + SafeTransferLib.safeTransferETH(_contents[i].recipient, _contents[i].amount); } if (nativeTokenAmount != msg.value) { @@ -170,6 +159,14 @@ contract Airdrop is EIP712, Initializable, Ownable { emit Airdrop(NATIVE_TOKEN_ADDRESS); } + /** + * @notice Lets contract owner send ERC20 tokens to a list of addresses. + * @dev The token-owner should approve total airdrop amount to this contract. + * Can only be called by contract owner. + * + * @param _tokenAddress Address of the ERC20 token being airdropped + * @param _contents List containing recipients and amounts to airdrop + */ function airdropERC20(address _tokenAddress, AirdropContentERC20[] calldata _contents) external onlyOwner { uint256 len = _contents.length; @@ -180,6 +177,14 @@ contract Airdrop is EIP712, Initializable, Ownable { emit Airdrop(_tokenAddress); } + /** + * @notice Lets contract owner send ERC721 tokens to a list of addresses. + * @dev The token-owner should approve airdrop tokenIds to this contract. + * Can only be called by contract owner. + * + * @param _tokenAddress Address of the ERC721 token being airdropped + * @param _contents List containing recipients and tokenIds to airdrop + */ function airdropERC721(address _tokenAddress, AirdropContentERC721[] calldata _contents) external onlyOwner { uint256 len = _contents.length; @@ -190,6 +195,14 @@ contract Airdrop is EIP712, Initializable, Ownable { emit Airdrop(_tokenAddress); } + /** + * @notice Lets contract owner send ERC1155 tokens to a list of addresses. + * @dev The token-owner should approve airdrop tokenIds and amounts to this contract. + * Can only be called by contract owner. + * + * @param _tokenAddress Address of the ERC1155 token being airdropped + * @param _contents List containing recipients, tokenIds, and amounts to airdrop + */ function airdropERC1155(address _tokenAddress, AirdropContentERC1155[] calldata _contents) external onlyOwner { uint256 len = _contents.length; @@ -210,6 +223,14 @@ contract Airdrop is EIP712, Initializable, Ownable { Airdrop With Signature //////////////////////////////////////////////////////////////*/ + /** + * @notice Lets contract owner send ERC20 tokens to a list of addresses with EIP-712 signature. + * @dev The token-owner should approve airdrop amounts to this contract. + * Signer should be the contract owner. + * + * @param req Struct containing airdrop contents, uid, and expiration timestamp + * @param signature EIP-712 signature to perform the airdrop + */ function airdropERC20WithSignature(AirdropRequestERC20 calldata req, bytes calldata signature) external { // verify expiration timestamp if (req.expirationTimestamp < block.timestamp) { @@ -239,9 +260,17 @@ contract Airdrop is EIP712, Initializable, Ownable { ); } - emit Airdrop(req.tokenAddress); + emit AirdropWithSignature(req.tokenAddress); } + /** + * @notice Lets contract owner send ERC721 tokens to a list of addresses with EIP-712 signature. + * @dev The token-owner should approve airdrop tokenIds to this contract. + * Signer should be the contract owner. + * + * @param req Struct containing airdrop contents, uid, and expiration timestamp + * @param signature EIP-712 signature to perform the airdrop + */ function airdropERC721WithSignature(AirdropRequestERC721 calldata req, bytes calldata signature) external { // verify expiration timestamp if (req.expirationTimestamp < block.timestamp) { @@ -266,9 +295,17 @@ contract Airdrop is EIP712, Initializable, Ownable { IERC721(req.tokenAddress).safeTransferFrom(_from, req.contents[i].recipient, req.contents[i].tokenId); } - emit Airdrop(req.tokenAddress); + emit AirdropWithSignature(req.tokenAddress); } + /** + * @notice Lets contract owner send ERC1155 tokens to a list of addresses with EIP-712 signature. + * @dev The token-owner should approve airdrop tokenIds and amounts to this contract. + * Signer should be the contract owner. + * + * @param req Struct containing airdrop contents, uid, and expiration timestamp + * @param signature EIP-712 signature to perform the airdrop + */ function airdropERC1155WithSignature(AirdropRequestERC1155 calldata req, bytes calldata signature) external { // verify expiration timestamp if (req.expirationTimestamp < block.timestamp) { @@ -299,13 +336,23 @@ contract Airdrop is EIP712, Initializable, Ownable { ); } - emit Airdrop(req.tokenAddress); + emit AirdropWithSignature(req.tokenAddress); } /*/////////////////////////////////////////////////////////////// Airdrop Claimable //////////////////////////////////////////////////////////////*/ + /** + * @notice Lets allowlisted addresses claim ERC20 airdrop tokens. + * @dev The token-owner should approve total airdrop amount to this contract, + * and set merkle root of allowlisted address for this airdrop. + * + * @param _token Address of ERC20 airdrop token + * @param _receiver Allowlisted address for which the token is being claimed + * @param _quantity Allowlisted quantity of tokens to claim + * @param _proofs Merkle proofs for allowlist verification + */ function claimERC20(address _token, address _receiver, uint256 _quantity, bytes32[] calldata _proofs) external { bytes32 claimHash = _getClaimHashERC20(_receiver, _token); uint256 conditionId = tokenConditionId[_token]; @@ -319,7 +366,7 @@ contract Airdrop is EIP712, Initializable, Ownable { revert AirdropNoMerkleRoot(); } - bool valid = MerkleProofLib.verify( + bool valid = MerkleProofLib.verifyCalldata( _proofs, _tokenMerkleRoot, keccak256(abi.encodePacked(_receiver, _quantity)) @@ -335,6 +382,16 @@ contract Airdrop is EIP712, Initializable, Ownable { emit AirdropClaimed(_token, _receiver); } + /** + * @notice Lets allowlisted addresses claim ERC721 airdrop tokens. + * @dev The token-owner should approve airdrop tokenIds to this contract, + * and set merkle root of allowlisted address for this airdrop. + * + * @param _token Address of ERC721 airdrop token + * @param _receiver Allowlisted address for which the token is being claimed + * @param _tokenId Allowlisted tokenId to claim + * @param _proofs Merkle proofs for allowlist verification + */ function claimERC721(address _token, address _receiver, uint256 _tokenId, bytes32[] calldata _proofs) external { bytes32 claimHash = _getClaimHashERC721(_receiver, _token, _tokenId); uint256 conditionId = tokenConditionId[_token]; @@ -348,7 +405,11 @@ contract Airdrop is EIP712, Initializable, Ownable { revert AirdropNoMerkleRoot(); } - bool valid = MerkleProofLib.verify(_proofs, _tokenMerkleRoot, keccak256(abi.encodePacked(_receiver, _tokenId))); + bool valid = MerkleProofLib.verifyCalldata( + _proofs, + _tokenMerkleRoot, + keccak256(abi.encodePacked(_receiver, _tokenId)) + ); if (!valid) { revert AirdropInvalidProof(); } @@ -360,6 +421,17 @@ contract Airdrop is EIP712, Initializable, Ownable { emit AirdropClaimed(_token, _receiver); } + /** + * @notice Lets allowlisted addresses claim ERC1155 airdrop tokens. + * @dev The token-owner should approve tokenIds and total airdrop amounts to this contract, + * and set merkle root of allowlisted address for this airdrop. + * + * @param _token Address of ERC1155 airdrop token + * @param _receiver Allowlisted address for which the token is being claimed + * @param _tokenId Allowlisted tokenId to claim + * @param _quantity Allowlisted quantity of tokens to claim + * @param _proofs Merkle proofs for allowlist verification + */ function claimERC1155( address _token, address _receiver, @@ -379,7 +451,7 @@ contract Airdrop is EIP712, Initializable, Ownable { revert AirdropNoMerkleRoot(); } - bool valid = MerkleProofLib.verify( + bool valid = MerkleProofLib.verifyCalldata( _proofs, _tokenMerkleRoot, keccak256(abi.encodePacked(_receiver, _tokenId, _quantity)) @@ -399,6 +471,13 @@ contract Airdrop is EIP712, Initializable, Ownable { Setter functions //////////////////////////////////////////////////////////////*/ + /** + * @notice Lets contract owner set merkle root (allowlist) for claim based airdrops. + * + * @param _token Address of airdrop token + * @param _tokenMerkleRoot Merkle root of allowlist + * @param _resetClaimStatus Reset claim status / amount claimed so far to zero for all recipients + */ function setMerkleRoot(address _token, bytes32 _tokenMerkleRoot, bool _resetClaimStatus) external onlyOwner { if (_resetClaimStatus || tokenConditionId[_token] == 0) { tokenConditionId[_token] += 1; @@ -410,6 +489,7 @@ contract Airdrop is EIP712, Initializable, Ownable { Miscellaneous //////////////////////////////////////////////////////////////*/ + /// @notice Returns claim status of a receiver for a claim based airdrop function isClaimed(address _receiver, address _token, uint256 _tokenId) external view returns (bool) { uint256 _conditionId = tokenConditionId[_token]; @@ -425,28 +505,33 @@ contract Airdrop is EIP712, Initializable, Ownable { return false; } - + /// @dev Checks whether contract owner can be set in the given execution context. function _canSetOwner() internal view virtual override returns (bool) { return msg.sender == owner(); } + /// @dev Domain name and version for EIP-712 function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) { name = "Airdrop"; version = "1"; } + /// @dev Keccak256 hash of receiver and token addresses, for claim based airdrop status tracking function _getClaimHashERC20(address _receiver, address _token) private view returns (bytes32) { return keccak256(abi.encodePacked(_receiver, _token)); } + /// @dev Keccak256 hash of receiver, token address and tokenId, for claim based airdrop status tracking function _getClaimHashERC721(address _receiver, address _token, uint256 _tokenId) private view returns (bytes32) { return keccak256(abi.encodePacked(_receiver, _token, _tokenId)); } + /// @dev Keccak256 hash of receiver, token address and tokenId, for claim based airdrop status tracking function _getClaimHashERC1155(address _receiver, address _token, uint256 _tokenId) private view returns (bytes32) { return keccak256(abi.encodePacked(_receiver, _token, _tokenId)); } + /// @dev Hash nested struct within AirdropRequest___ function _hashContentInfoERC20(AirdropContentERC20[] calldata contents) private pure returns (bytes32) { bytes32[] memory contentHashes = new bytes32[](contents.length); for (uint256 i = 0; i < contents.length; i++) { @@ -455,6 +540,7 @@ contract Airdrop is EIP712, Initializable, Ownable { return keccak256(abi.encodePacked(contentHashes)); } + /// @dev Hash nested struct within AirdropRequest___ function _hashContentInfoERC721(AirdropContentERC721[] calldata contents) private pure returns (bytes32) { bytes32[] memory contentHashes = new bytes32[](contents.length); for (uint256 i = 0; i < contents.length; i++) { @@ -465,6 +551,7 @@ contract Airdrop is EIP712, Initializable, Ownable { return keccak256(abi.encodePacked(contentHashes)); } + /// @dev Hash nested struct within AirdropRequest___ function _hashContentInfoERC1155(AirdropContentERC1155[] calldata contents) private pure returns (bytes32) { bytes32[] memory contentHashes = new bytes32[](contents.length); for (uint256 i = 0; i < contents.length; i++) { @@ -475,6 +562,7 @@ contract Airdrop is EIP712, Initializable, Ownable { return keccak256(abi.encodePacked(contentHashes)); } + /// @dev Verify EIP-712 signature function _verifyRequestSignerERC20( AirdropRequestERC20 calldata req, bytes calldata signature @@ -485,10 +573,11 @@ contract Airdrop is EIP712, Initializable, Ownable { ); bytes32 digest = _hashTypedData(structHash); - address recovered = digest.recover(signature); - return recovered == owner(); + + return SignatureCheckerLib.isValidSignatureNowCalldata(owner(), digest, signature); } + /// @dev Verify EIP-712 signature function _verifyRequestSignerERC721( AirdropRequestERC721 calldata req, bytes calldata signature @@ -499,10 +588,11 @@ contract Airdrop is EIP712, Initializable, Ownable { ); bytes32 digest = _hashTypedData(structHash); - address recovered = digest.recover(signature); - return recovered == owner(); + + return SignatureCheckerLib.isValidSignatureNowCalldata(owner(), digest, signature); } + /// @dev Verify EIP-712 signature function _verifyRequestSignerERC1155( AirdropRequestERC1155 calldata req, bytes calldata signature @@ -513,7 +603,7 @@ contract Airdrop is EIP712, Initializable, Ownable { ); bytes32 digest = _hashTypedData(structHash); - address recovered = digest.recover(signature); - return recovered == owner(); + + return SignatureCheckerLib.isValidSignatureNowCalldata(owner(), digest, signature); } } diff --git a/gasreport.txt b/gasreport.txt index 5cd46c496..ce02c209e 100644 --- a/gasreport.txt +++ b/gasreport.txt @@ -1,30 +1,9 @@ -Compiling 1 files with 0.8.23 -Solc 0.8.23 finished in 22.27s -Compiler run successful with warnings: -Warning (5667): Unused function parameter. Remove or comment out the variable name to silence this warning. - --> contracts/prebuilts/pack/Pack.sol:101:9: - | -101 | address[] memory _trustedForwarders, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Warning (2018): Function state mutability can be restricted to pure - --> contracts/prebuilts/unaudited/airdrop/Airdrop.sol:395:5: - | -395 | function _getClaimHashERC20(address _receiver, address _token) private view returns (bytes32) { - | ^ (Relevant source part starts here and spans across multiple lines). - -Warning (2018): Function state mutability can be restricted to pure - --> contracts/prebuilts/unaudited/airdrop/Airdrop.sol:399:5: - | -399 | function _getClaimHashERC721(address _receiver, address _token, uint256 _tokenId) private view returns (bytes32) { - | ^ (Relevant source part starts here and spans across multiple lines). - -Warning (2018): Function state mutability can be restricted to pure - --> contracts/prebuilts/unaudited/airdrop/Airdrop.sol:403:5: - | -403 | function _getClaimHashERC1155(address _receiver, address _token, uint256 _tokenId) private view returns (bytes32) { - | ^ (Relevant source part starts here and spans across multiple lines). +No files changed, compilation skipped +Ran 2 tests for src/test/benchmark/MultiwrapBenchmark.t.sol:MultiwrapBenchmarkTest +[PASS] test_benchmark_multiwrap_unwrap() (gas: 152040) +[PASS] test_benchmark_multiwrap_wrap() (gas: 480722) +Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 665.73ms (584.46µs CPU time) Ran 5 tests for src/test/benchmark/SignatureDropBenchmark.t.sol:SignatureDropBenchmarkTest [PASS] test_benchmark_signatureDrop_claim_five_tokens() (gas: 185688) @@ -32,148 +11,110 @@ Ran 5 tests for src/test/benchmark/SignatureDropBenchmark.t.sol:SignatureDropBen [PASS] test_benchmark_signatureDrop_lazyMint_for_delayed_reveal() (gas: 249057) [PASS] test_benchmark_signatureDrop_reveal() (gas: 49802) [PASS] test_benchmark_signatureDrop_setClaimConditions() (gas: 100719) -Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 777.81ms (1.16ms CPU time) +Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 665.92ms (942.96µs CPU time) Ran 3 tests for src/test/benchmark/EditionStakeBenchmark.t.sol:EditionStakeBenchmarkTest [PASS] test_benchmark_editionStake_claimRewards() (gas: 98765) [PASS] test_benchmark_editionStake_stake() (gas: 203676) [PASS] test_benchmark_editionStake_withdraw() (gas: 94296) -Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 777.55ms (1.41ms CPU time) +Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 666.96ms (533.96µs CPU time) Ran 3 tests for src/test/benchmark/NFTStakeBenchmark.t.sol:NFTStakeBenchmarkTest [PASS] test_benchmark_nftStake_claimRewards() (gas: 99831) [PASS] test_benchmark_nftStake_stake_five_tokens() (gas: 553577) [PASS] test_benchmark_nftStake_withdraw() (gas: 96144) -Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 781.23ms (899.88µs CPU time) - -Ran 3 tests for src/test/benchmark/PackBenchmark.t.sol:PackBenchmarkTest -[PASS] test_benchmark_pack_addPackContents() (gas: 312595) -[PASS] test_benchmark_pack_createPack() (gas: 1419379) -[PASS] test_benchmark_pack_openPack() (gas: 302612) -Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 783.66ms (3.44ms CPU time) +Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 669.06ms (710.17µs CPU time) Ran 1 test for src/test/smart-wallet/utils/AABenchmarkPrepare.sol:AABenchmarkPrepare [PASS] test_prepareBenchmarkFile() (gas: 2955770) -Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 797.24ms (20.05ms CPU time) - -Ran 14 tests for src/test/benchmark/AccountBenchmark.t.sol:AccountBenchmarkTest -[PASS] test_state_accountReceivesNativeTokens() (gas: 34537) -[PASS] test_state_addAndWithdrawDeposit() (gas: 148780) -[PASS] test_state_contractMetadata() (gas: 114307) -[PASS] test_state_createAccount_viaEntrypoint() (gas: 458192) -[PASS] test_state_createAccount_viaFactory() (gas: 355822) -[PASS] test_state_executeBatchTransaction() (gas: 76066) -[PASS] test_state_executeBatchTransaction_viaAccountSigner() (gas: 488470) -[PASS] test_state_executeBatchTransaction_viaEntrypoint() (gas: 138443) -[PASS] test_state_executeTransaction() (gas: 68891) -[PASS] test_state_executeTransaction_viaAccountSigner() (gas: 471272) -[PASS] test_state_executeTransaction_viaEntrypoint() (gas: 128073) -[PASS] test_state_receiveERC1155NFT() (gas: 66043) -[PASS] test_state_receiveERC721NFT() (gas: 100196) -[PASS] test_state_transferOutsNativeTokens() (gas: 133673) -Suite result: ok. 14 passed; 0 failed; 0 skipped; finished in 798.25ms (21.10ms CPU time) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 677.51ms (13.32ms CPU time) Ran 1 test for src/test/benchmark/AirdropERC20Benchmark.t.sol:AirdropERC20BenchmarkTest [PASS] test_benchmark_airdropERC20_airdrop() (gas: 32443785) -Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 809.63ms (27.77ms CPU time) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 688.85ms (17.93ms CPU time) Ran 1 test for src/test/benchmark/AirdropERC721Benchmark.t.sol:AirdropERC721BenchmarkTest [PASS] test_benchmark_airdropERC721_airdrop() (gas: 42241588) -Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 818.36ms (26.52ms CPU time) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 701.05ms (26.57ms CPU time) -Ran 3 tests for src/test/benchmark/PackVRFDirectBenchmark.t.sol:PackVRFDirectBenchmarkTest -[PASS] test_benchmark_packvrf_createPack() (gas: 1392387) -[PASS] test_benchmark_packvrf_openPack() (gas: 150677) -[PASS] test_benchmark_packvrf_openPackAndClaimRewards() (gas: 3621) -Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 232.32ms (1.99ms CPU time) - -Ran 4 tests for src/test/benchmark/TokenERC1155Benchmark.t.sol:TokenERC1155BenchmarkTest -[PASS] test_benchmark_tokenERC1155_burn() (gas: 30352) -[PASS] test_benchmark_tokenERC1155_mintTo() (gas: 144229) -[PASS] test_benchmark_tokenERC1155_mintWithSignature_pay_with_ERC20() (gas: 307291) -[PASS] test_benchmark_tokenERC1155_mintWithSignature_pay_with_native_token() (gas: 318712) -Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 297.36ms (1.61ms CPU time) - -Ran 2 tests for src/test/benchmark/MultiwrapBenchmark.t.sol:MultiwrapBenchmarkTest -[PASS] test_benchmark_multiwrap_unwrap() (gas: 152040) -[PASS] test_benchmark_multiwrap_wrap() (gas: 480722) -Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 310.37ms (726.13µs CPU time) - -Ran 1 test for src/test/benchmark/AirdropERC1155Benchmark.t.sol:AirdropERC1155BenchmarkTest -[PASS] test_benchmark_airdropERC1155_airdrop() (gas: 38536544) -Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 273.46ms (21.60ms CPU time) +Ran 3 tests for src/test/benchmark/PackBenchmark.t.sol:PackBenchmarkTest +[PASS] test_benchmark_pack_addPackContents() (gas: 312595) +[PASS] test_benchmark_pack_createPack() (gas: 1419379) +[PASS] test_benchmark_pack_openPack() (gas: 302612) +Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 181.87ms (2.36ms CPU time) Ran 3 tests for src/test/benchmark/TokenERC20Benchmark.t.sol:TokenERC20BenchmarkTest [PASS] test_benchmark_tokenERC20_mintTo() (gas: 139513) [PASS] test_benchmark_tokenERC20_mintWithSignature_pay_with_ERC20() (gas: 221724) [PASS] test_benchmark_tokenERC20_mintWithSignature_pay_with_native_token() (gas: 228786) -Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 339.25ms (3.19ms CPU time) +Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 188.65ms (1.10ms CPU time) Ran 4 tests for src/test/benchmark/TokenERC721Benchmark.t.sol:TokenERC721BenchmarkTest [PASS] test_benchmark_tokenERC721_burn() (gas: 40392) [PASS] test_benchmark_tokenERC721_mintTo() (gas: 172834) [PASS] test_benchmark_tokenERC721_mintWithSignature_pay_with_ERC20() (gas: 301844) [PASS] test_benchmark_tokenERC721_mintWithSignature_pay_with_native_token() (gas: 308814) -Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 311.67ms (1.86ms CPU time) +Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 192.61ms (1.31ms CPU time) + +Ran 4 tests for src/test/benchmark/TokenERC1155Benchmark.t.sol:TokenERC1155BenchmarkTest +[PASS] test_benchmark_tokenERC1155_burn() (gas: 30352) +[PASS] test_benchmark_tokenERC1155_mintTo() (gas: 144229) +[PASS] test_benchmark_tokenERC1155_mintWithSignature_pay_with_ERC20() (gas: 307291) +[PASS] test_benchmark_tokenERC1155_mintWithSignature_pay_with_native_token() (gas: 318712) +Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 225.96ms (1.36ms CPU time) Ran 3 tests for src/test/benchmark/TokenStakeBenchmark.t.sol:TokenStakeBenchmarkTest [PASS] test_benchmark_tokenStake_claimRewards() (gas: 101098) [PASS] test_benchmark_tokenStake_stake() (gas: 195556) [PASS] test_benchmark_tokenStake_withdraw() (gas: 104792) -Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 194.65ms (694.21µs CPU time) - -Ran 21 tests for src/test/benchmark/AirdropBenchmark.t.sol:AirdropBenchmarkTest -[PASS] test_benchmark_airdropClaim_erc1155() (gas: 103870) -[PASS] test_benchmark_airdropClaim_erc20() (gas: 108214) -[PASS] test_benchmark_airdropClaim_erc721() (gas: 107404) -[PASS] test_benchmark_airdropPush_erc1155_10() (gas: 366803) -[PASS] test_benchmark_airdropPush_erc1155_100() (gas: 3262938) -[PASS] test_benchmark_airdropPush_erc1155_1000() (gas: 32344939) -[PASS] test_benchmark_airdropPush_erc20_10() (gas: 342387) -[PASS] test_benchmark_airdropPush_erc20_100() (gas: 2972974) -[PASS] test_benchmark_airdropPush_erc20_1000() (gas: 29348844) -[PASS] test_benchmark_airdropPush_erc721_10() (gas: 423239) -[PASS] test_benchmark_airdropPush_erc721_100() (gas: 3833903) -[PASS] test_benchmark_airdropPush_erc721_1000() (gas: 38104588) -[PASS] test_benchmark_airdropSignature_erc115_10() (gas: 415414) -[PASS] test_benchmark_airdropSignature_erc115_100() (gas: 3456815) -[PASS] test_benchmark_airdropSignature_erc115_1000() (gas: 34332958) -[PASS] test_benchmark_airdropSignature_erc20_10() (gas: 388010) -[PASS] test_benchmark_airdropSignature_erc20_100() (gas: 3137606) -[PASS] test_benchmark_airdropSignature_erc20_1000() (gas: 30935300) -[PASS] test_benchmark_airdropSignature_erc721_10() (gas: 468925) -[PASS] test_benchmark_airdropSignature_erc721_100() (gas: 4008367) -[PASS] test_benchmark_airdropSignature_erc721_1000() (gas: 39690834) -Suite result: ok. 21 passed; 0 failed; 0 skipped; finished in 1.63s (1.75s CPU time) - -Ran 21 tests for src/test/benchmark/AirdropBenchmarkAlt.t.sol:AirdropBenchmarkAltTest -[PASS] test_benchmark_airdropClaim_erc1155() (gas: 103870) -[PASS] test_benchmark_airdropClaim_erc20() (gas: 108214) -[PASS] test_benchmark_airdropClaim_erc721() (gas: 107404) -[PASS] test_benchmark_airdropPush_erc1155_10() (gas: 366803) -[PASS] test_benchmark_airdropPush_erc1155_100() (gas: 3262938) -[PASS] test_benchmark_airdropPush_erc1155_1000() (gas: 32344939) -[PASS] test_benchmark_airdropPush_erc20_10() (gas: 342387) -[PASS] test_benchmark_airdropPush_erc20_100() (gas: 2972974) -[PASS] test_benchmark_airdropPush_erc20_1000() (gas: 29348844) -[PASS] test_benchmark_airdropPush_erc721_10() (gas: 423239) -[PASS] test_benchmark_airdropPush_erc721_100() (gas: 3833903) -[PASS] test_benchmark_airdropPush_erc721_1000() (gas: 38104588) -[PASS] test_benchmark_airdropSignature_erc115_10() (gas: 415414) -[PASS] test_benchmark_airdropSignature_erc115_100() (gas: 3456815) -[PASS] test_benchmark_airdropSignature_erc115_1000() (gas: 34332958) -[PASS] test_benchmark_airdropSignature_erc20_10() (gas: 388010) -[PASS] test_benchmark_airdropSignature_erc20_100() (gas: 3137606) -[PASS] test_benchmark_airdropSignature_erc20_1000() (gas: 30935300) -[PASS] test_benchmark_airdropSignature_erc721_10() (gas: 468925) -[PASS] test_benchmark_airdropSignature_erc721_100() (gas: 4008367) -[PASS] test_benchmark_airdropSignature_erc721_1000() (gas: 39690834) -Suite result: ok. 21 passed; 0 failed; 0 skipped; finished in 866.76ms (1.41s CPU time) +Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 196.93ms (479.46µs CPU time) + +Ran 1 test for src/test/benchmark/AirdropERC1155Benchmark.t.sol:AirdropERC1155BenchmarkTest +[PASS] test_benchmark_airdropERC1155_airdrop() (gas: 38536544) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 341.15ms (19.66ms CPU time) + +Ran 3 tests for src/test/benchmark/PackVRFDirectBenchmark.t.sol:PackVRFDirectBenchmarkTest +[PASS] test_benchmark_packvrf_createPack() (gas: 1392387) +[PASS] test_benchmark_packvrf_openPack() (gas: 150677) +[PASS] test_benchmark_packvrf_openPackAndClaimRewards() (gas: 3621) +Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 201.65ms (2.27ms CPU time) + +Ran 3 tests for src/test/benchmark/DropERC1155Benchmark.t.sol:DropERC1155BenchmarkTest +[PASS] test_benchmark_dropERC1155_claim() (gas: 245552) +[PASS] test_benchmark_dropERC1155_lazyMint() (gas: 146425) +[PASS] test_benchmark_dropERC1155_setClaimConditions_five_conditions() (gas: 525725) +Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.20s (706.01ms CPU time) Ran 2 tests for src/test/benchmark/DropERC20Benchmark.t.sol:DropERC20BenchmarkTest [PASS] test_benchmark_dropERC20_claim() (gas: 291508) [PASS] test_benchmark_dropERC20_setClaimConditions_five_conditions() (gas: 530026) -Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 915.15ms (767.18ms CPU time) +Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 510.75ms (589.86ms CPU time) + +Ran 23 tests for src/test/benchmark/AirdropBenchmark.t.sol:AirdropBenchmarkTest +[PASS] test_benchmark_airdropClaim_erc1155() (gas: 105358) +[PASS] test_benchmark_airdropClaim_erc20() (gas: 109724) +[PASS] test_benchmark_airdropClaim_erc721() (gas: 108870) +[PASS] test_benchmark_airdropPush_erc1155ReceiverCompliant() (gas: 82427) +[PASS] test_benchmark_airdropPush_erc1155_10() (gas: 370062) +[PASS] test_benchmark_airdropPush_erc1155_100() (gas: 3266571) +[PASS] test_benchmark_airdropPush_erc1155_1000() (gas: 32348198) +[PASS] test_benchmark_airdropPush_erc20_10() (gas: 345649) +[PASS] test_benchmark_airdropPush_erc20_100() (gas: 2976236) +[PASS] test_benchmark_airdropPush_erc20_1000() (gas: 29352084) +[PASS] test_benchmark_airdropPush_erc721ReceiverCompliant() (gas: 86434) +[PASS] test_benchmark_airdropPush_erc721_10() (gas: 426498) +[PASS] test_benchmark_airdropPush_erc721_100() (gas: 3837162) +[PASS] test_benchmark_airdropPush_erc721_1000() (gas: 38107847) +[PASS] test_benchmark_airdropSignature_erc115_10() (gas: 416712) +[PASS] test_benchmark_airdropSignature_erc115_100() (gas: 3458091) +[PASS] test_benchmark_airdropSignature_erc115_1000() (gas: 34334256) +[PASS] test_benchmark_airdropSignature_erc20_10() (gas: 389286) +[PASS] test_benchmark_airdropSignature_erc20_100() (gas: 3138882) +[PASS] test_benchmark_airdropSignature_erc20_1000() (gas: 30936576) +[PASS] test_benchmark_airdropSignature_erc721_10() (gas: 470201) +[PASS] test_benchmark_airdropSignature_erc721_100() (gas: 4009643) +[PASS] test_benchmark_airdropSignature_erc721_1000() (gas: 39692110) +Suite result: ok. 23 passed; 0 failed; 0 skipped; finished in 1.21s (1.25s CPU time) Ran 5 tests for src/test/benchmark/DropERC721Benchmark.t.sol:DropERC721BenchmarkTest [PASS] test_benchmark_dropERC721_claim_five_tokens() (gas: 273303) @@ -181,16 +122,27 @@ Ran 5 tests for src/test/benchmark/DropERC721Benchmark.t.sol:DropERC721Benchmark [PASS] test_benchmark_dropERC721_lazyMint_for_delayed_reveal() (gas: 248985) [PASS] test_benchmark_dropERC721_reveal() (gas: 49433) [PASS] test_benchmark_dropERC721_setClaimConditions_five_conditions() (gas: 529470) -Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 462.45ms (550.25ms CPU time) +Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 466.63ms (512.92ms CPU time) -Ran 3 tests for src/test/benchmark/DropERC1155Benchmark.t.sol:DropERC1155BenchmarkTest -[PASS] test_benchmark_dropERC1155_claim() (gas: 245552) -[PASS] test_benchmark_dropERC1155_lazyMint() (gas: 146425) -[PASS] test_benchmark_dropERC1155_setClaimConditions_five_conditions() (gas: 525725) -Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.79s (1.05s CPU time) +Ran 14 tests for src/test/benchmark/AccountBenchmark.t.sol:AccountBenchmarkTest +[PASS] test_state_accountReceivesNativeTokens() (gas: 34537) +[PASS] test_state_addAndWithdrawDeposit() (gas: 148780) +[PASS] test_state_contractMetadata() (gas: 114307) +[PASS] test_state_createAccount_viaEntrypoint() (gas: 458192) +[PASS] test_state_createAccount_viaFactory() (gas: 355822) +[PASS] test_state_executeBatchTransaction() (gas: 76066) +[PASS] test_state_executeBatchTransaction_viaAccountSigner() (gas: 488470) +[PASS] test_state_executeBatchTransaction_viaEntrypoint() (gas: 138443) +[PASS] test_state_executeTransaction() (gas: 68891) +[PASS] test_state_executeTransaction_viaAccountSigner() (gas: 471272) +[PASS] test_state_executeTransaction_viaEntrypoint() (gas: 128073) +[PASS] test_state_receiveERC1155NFT() (gas: 66043) +[PASS] test_state_receiveERC721NFT() (gas: 100196) +[PASS] test_state_transferOutsNativeTokens() (gas: 133673) +Suite result: ok. 14 passed; 0 failed; 0 skipped; finished in 1.32s (26.86ms CPU time) -Ran 20 test suites in 2.00s (13.97s CPU time): 103 tests passed, 0 failed, 0 skipped (103 total tests) +Ran 19 test suites in 1.45s (10.97s CPU time): 84 tests passed, 0 failed, 0 skipped (84 total tests) test_benchmark_packvrf_openPackAndClaimRewards() (gas: 0 (0.000%)) test_benchmark_pack_createPack() (gas: 6511 (0.461%)) test_benchmark_airdropERC721_airdrop() (gas: 329052 (0.785%)) diff --git a/src/test/airdrop/Airdrop.t.sol b/src/test/airdrop/Airdrop.t.sol index 3eef9d519..694aac542 100644 --- a/src/test/airdrop/Airdrop.t.sol +++ b/src/test/airdrop/Airdrop.t.sol @@ -1,14 +1,50 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; -import { Airdrop } from "contracts/prebuilts/unaudited/airdrop/Airdrop.sol"; +import { Airdrop, SafeTransferLib, ECDSA } from "contracts/prebuilts/unaudited/airdrop/Airdrop.sol"; // Test imports import { TWProxy } from "contracts/infra/TWProxy.sol"; import "../utils/BaseTest.sol"; +contract MockSmartWallet { + using ECDSA for bytes32; + + bytes4 private constant EIP1271_MAGIC_VALUE = 0x1626ba7e; + address private admin; + + constructor(address _admin) { + admin = _admin; + } + + function isValidSignature(bytes32 _hash, bytes memory _signature) public view returns (bytes4) { + if (_hash.recover(_signature) == admin) { + return EIP1271_MAGIC_VALUE; + } + } + + function onERC721Received(address, address, uint256, bytes memory) external pure returns (bytes4) { + return this.onERC721Received.selector; + } + + function onERC1155Received(address, address, uint256, uint256, bytes memory) external pure returns (bytes4) { + return this.onERC1155Received.selector; + } + + function onERC1155BatchReceived( + address, + address, + uint256[] memory, + uint256[] memory, + bytes memory + ) external pure returns (bytes4) { + return this.onERC1155BatchReceived.selector; + } +} + contract AirdropTest is BaseTest { Airdrop internal airdrop; + MockSmartWallet internal mockSmartWallet; bytes32 private constant CONTENT_TYPEHASH_ERC20 = keccak256("AirdropContentERC20(address recipient,uint256 amount)"); @@ -48,6 +84,8 @@ contract AirdropTest is BaseTest { domainSeparator = keccak256( abi.encode(TYPE_HASH_EIP712, NAME_HASH, VERSION_HASH, block.chainid, address(airdrop)) ); + + mockSmartWallet = new MockSmartWallet(signer); } function _getContentsERC20(uint256 length) internal pure returns (Airdrop.AirdropContentERC20[] memory contents) { @@ -228,18 +266,8 @@ contract AirdropTest is BaseTest { Airdrop.AirdropContentERC20[] memory contents = _getContentsERC20(10); vm.prank(signer); - vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropFailed.selector)); - airdrop.airdropNativeToken{ value: 0 }(contents); - - // add some balance to airdrop contract, which it will try sending to recipeints when msg.value zero - vm.deal(address(airdrop), 50 ether); - // should revert - vm.prank(signer); - vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropValueMismatch.selector)); + vm.expectRevert(abi.encodeWithSelector(SafeTransferLib.ETHTransferFailed.selector)); airdrop.airdropNativeToken{ value: 0 }(contents); - - // contract balance should remain untouched - assertEq(address(airdrop).balance, 50 ether); } /*/////////////////////////////////////////////////////////////// @@ -272,6 +300,62 @@ contract AirdropTest is BaseTest { assertEq(erc20.balanceOf(signer), 100 ether - totalAmount); } + function test_state_airdropSignature_erc20_eip1271() public { + // set mockSmartWallet as contract owner + vm.prank(signer); + airdrop.setOwner(address(mockSmartWallet)); + + // mint tokens to mockSmartWallet + erc20.mint(address(mockSmartWallet), 100 ether); + vm.prank(address(mockSmartWallet)); + erc20.approve(address(airdrop), 100 ether); + + Airdrop.AirdropContentERC20[] memory contents = _getContentsERC20(10); + Airdrop.AirdropRequestERC20 memory req = Airdrop.AirdropRequestERC20({ + uid: bytes32(uint256(1)), + tokenAddress: address(erc20), + expirationTimestamp: 1000, + contents: contents + }); + + // sign with original EOA signer private key + bytes memory signature = _signReqERC20(req, privateKey); + + airdrop.airdropERC20WithSignature(req, signature); + + uint256 totalAmount; + for (uint256 i = 0; i < contents.length; i++) { + totalAmount += contents[i].amount; + assertEq(erc20.balanceOf(contents[i].recipient), contents[i].amount); + } + assertEq(erc20.balanceOf(address(mockSmartWallet)), 100 ether - totalAmount); + } + + function test_revert_airdropSignature_erc20_eip1271_invalidSignature() public { + // set mockSmartWallet as contract owner + vm.prank(signer); + airdrop.setOwner(address(mockSmartWallet)); + + // mint tokens to mockSmartWallet + erc20.mint(address(mockSmartWallet), 100 ether); + vm.prank(address(mockSmartWallet)); + erc20.approve(address(airdrop), 100 ether); + + Airdrop.AirdropContentERC20[] memory contents = _getContentsERC20(10); + Airdrop.AirdropRequestERC20 memory req = Airdrop.AirdropRequestERC20({ + uid: bytes32(uint256(1)), + tokenAddress: address(erc20), + expirationTimestamp: 1000, + contents: contents + }); + + // sign with random private key + bytes memory signature = _signReqERC20(req, 123); + + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropRequestInvalidSigner.selector)); + airdrop.airdropERC20WithSignature(req, signature); + } + function test_revert_airdropSignature_erc20_expired() public { erc20.mint(signer, 100 ether); vm.prank(signer); @@ -490,6 +574,59 @@ contract AirdropTest is BaseTest { } } + function test_state_airdropSignature_erc721_eip1271() public { + // set mockSmartWallet as contract owner + vm.prank(signer); + airdrop.setOwner(address(mockSmartWallet)); + + // mint tokens to mockSmartWallet + erc721.mint(address(mockSmartWallet), 1000); + vm.prank(address(mockSmartWallet)); + erc721.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC721[] memory contents = _getContentsERC721(10); + Airdrop.AirdropRequestERC721 memory req = Airdrop.AirdropRequestERC721({ + uid: bytes32(uint256(1)), + tokenAddress: address(erc721), + expirationTimestamp: 1000, + contents: contents + }); + + // sign with original EOA signer private key + bytes memory signature = _signReqERC721(req, privateKey); + + airdrop.airdropERC721WithSignature(req, signature); + + for (uint256 i = 0; i < contents.length; i++) { + assertEq(erc721.ownerOf(contents[i].tokenId), contents[i].recipient); + } + } + + function test_revert_airdropSignature_erc721_eip1271_invalidSignature() public { + // set mockSmartWallet as contract owner + vm.prank(signer); + airdrop.setOwner(address(mockSmartWallet)); + + // mint tokens to mockSmartWallet + erc721.mint(address(mockSmartWallet), 1000); + vm.prank(address(mockSmartWallet)); + erc721.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC721[] memory contents = _getContentsERC721(10); + Airdrop.AirdropRequestERC721 memory req = Airdrop.AirdropRequestERC721({ + uid: bytes32(uint256(1)), + tokenAddress: address(erc721), + expirationTimestamp: 1000, + contents: contents + }); + + // sign with random private key + bytes memory signature = _signReqERC721(req, 123); + + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropRequestInvalidSigner.selector)); + airdrop.airdropERC721WithSignature(req, signature); + } + function test_revert_airdropSignature_erc721_expired() public { erc721.mint(signer, 1000); vm.prank(signer); @@ -704,6 +841,59 @@ contract AirdropTest is BaseTest { } } + function test_state_airdropSignature_erc1155_eip1271() public { + // set mockSmartWallet as contract owner + vm.prank(signer); + airdrop.setOwner(address(mockSmartWallet)); + + // mint tokens to mockSmartWallet + erc1155.mint(address(mockSmartWallet), 0, 100 ether); + vm.prank(address(mockSmartWallet)); + erc1155.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC1155[] memory contents = _getContentsERC1155(10); + Airdrop.AirdropRequestERC1155 memory req = Airdrop.AirdropRequestERC1155({ + uid: bytes32(uint256(1)), + tokenAddress: address(erc1155), + expirationTimestamp: 1000, + contents: contents + }); + + // sign with original EOA signer private key + bytes memory signature = _signReqERC1155(req, privateKey); + + airdrop.airdropERC1155WithSignature(req, signature); + + for (uint256 i = 0; i < contents.length; i++) { + assertEq(erc1155.balanceOf(contents[i].recipient, contents[i].tokenId), contents[i].amount); + } + } + + function test_revert_airdropSignature_erc1155_eip1271_invalidSignature() public { + // set mockSmartWallet as contract owner + vm.prank(signer); + airdrop.setOwner(address(mockSmartWallet)); + + // mint tokens to mockSmartWallet + erc1155.mint(address(mockSmartWallet), 0, 100 ether); + vm.prank(address(mockSmartWallet)); + erc1155.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC1155[] memory contents = _getContentsERC1155(10); + Airdrop.AirdropRequestERC1155 memory req = Airdrop.AirdropRequestERC1155({ + uid: bytes32(uint256(1)), + tokenAddress: address(erc1155), + expirationTimestamp: 1000, + contents: contents + }); + + // sign with random private key + bytes memory signature = _signReqERC1155(req, 123); + + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropRequestInvalidSigner.selector)); + airdrop.airdropERC1155WithSignature(req, signature); + } + function test_revert_airdropSignature_erc115_expired() public { erc1155.mint(signer, 0, 100 ether); vm.prank(signer); diff --git a/src/test/benchmark/AirdropBenchmark.t.sol b/src/test/benchmark/AirdropBenchmark.t.sol index 6686a1ba0..865f09c01 100644 --- a/src/test/benchmark/AirdropBenchmark.t.sol +++ b/src/test/benchmark/AirdropBenchmark.t.sol @@ -7,6 +7,41 @@ import { Airdrop } from "contracts/prebuilts/unaudited/airdrop/Airdrop.sol"; import { TWProxy } from "contracts/infra/TWProxy.sol"; import "../utils/BaseTest.sol"; +contract ERC721ReceiverCompliant is IERC721Receiver { + function onERC721Received( + address, + address, + uint256, + bytes calldata + ) external view virtual override returns (bytes4) { + return this.onERC721Received.selector; + } +} + +contract ERC1155ReceiverCompliant is IERC1155Receiver { + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external view virtual override returns (bytes4) { + return this.onERC1155Received.selector; + } + + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4) { + return this.onERC1155BatchReceived.selector; + } + + function supportsInterface(bytes4 interfaceId) external view returns (bool) {} +} + contract AirdropBenchmarkTest is BaseTest { Airdrop internal airdrop; @@ -372,6 +407,23 @@ contract AirdropBenchmarkTest is BaseTest { airdrop.airdropERC721(address(erc721), contents); } + function test_benchmark_airdropPush_erc721ReceiverCompliant() public { + vm.pauseGasMetering(); + + erc721.mint(signer, 100); + vm.prank(signer); + erc721.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC721[] memory contents = new Airdrop.AirdropContentERC721[](1); + + contents[0].recipient = address(new ERC721ReceiverCompliant()); + contents[0].tokenId = 0; + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC721(address(erc721), contents); + } + /*/////////////////////////////////////////////////////////////// Benchmark: Airdrop Signature ERC721 //////////////////////////////////////////////////////////////*/ @@ -520,6 +572,24 @@ contract AirdropBenchmarkTest is BaseTest { airdrop.airdropERC1155(address(erc1155), contents); } + function test_benchmark_airdropPush_erc1155ReceiverCompliant() public { + vm.pauseGasMetering(); + + erc1155.mint(signer, 0, 100 ether); + vm.prank(signer); + erc1155.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC1155[] memory contents = new Airdrop.AirdropContentERC1155[](1); + + contents[0].recipient = address(new ERC1155ReceiverCompliant()); + contents[0].tokenId = 0; + contents[0].amount = 100; + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC1155(address(erc1155), contents); + } + /*/////////////////////////////////////////////////////////////// Benchmark: Airdrop Signature ERC1155 //////////////////////////////////////////////////////////////*/