diff --git a/.gitmodules b/.gitmodules index 7872f5e50..6cf023db0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "lib/dynamic-contracts"] path = lib/dynamic-contracts url = https://github.com/thirdweb-dev/dynamic-contracts +[submodule "lib/solady"] + path = lib/solady + url = https://github.com/vectorized/solady diff --git a/contracts/prebuilts/unaudited/airdrop/Airdrop.sol b/contracts/prebuilts/unaudited/airdrop/Airdrop.sol new file mode 100644 index 000000000..9fba1dae6 --- /dev/null +++ b/contracts/prebuilts/unaudited/airdrop/Airdrop.sol @@ -0,0 +1,519 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.11; + +/// @author thirdweb + +// $$\ $$\ $$\ $$\ $$\ +// $$ | $$ | \__| $$ | $$ | +// $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$$$$$$ |$$\ $$\ $$\ $$$$$$\ $$$$$$$\ +// \_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$ |$$ | $$ | $$ |$$ __$$\ $$ __$$\ +// $$ | $$ | $$ |$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ | +// $$ |$$\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ | +// \$$$$ |$$ | $$ |$$ |$$ | \$$$$$$$ |\$$$$$\$$$$ |\$$$$$$$\ $$$$$$$ | +// \____/ \__| \__|\__|\__| \_______| \_____\____/ \_______|\_______/ + +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 { Initializable } from "../../../extension/Initializable.sol"; +import { Ownable } from "../../../extension/Ownable.sol"; + +import "../../../eip/interface/IERC20.sol"; +import "../../../eip/interface/IERC721.sol"; +import "../../../eip/interface/IERC1155.sol"; + +contract Airdrop is EIP712, Initializable, Ownable { + using ECDSA for bytes32; + + /*/////////////////////////////////////////////////////////////// + State, constants & structs + //////////////////////////////////////////////////////////////*/ + + /// @dev token contract address => conditionId + mapping(address => uint256) public tokenConditionId; + /// @dev token contract address => merkle root + mapping(address => bytes32) public tokenMerkleRoot; + /// @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; + + struct AirdropContentERC20 { + address recipient; + uint256 amount; + } + + struct AirdropContentERC721 { + address recipient; + uint256 tokenId; + } + + struct AirdropContentERC1155 { + address recipient; + uint256 tokenId; + uint256 amount; + } + + struct AirdropRequestERC20 { + bytes32 uid; + address tokenAddress; + uint256 expirationTimestamp; + AirdropContentERC20[] contents; + } + + struct AirdropRequestERC721 { + bytes32 uid; + address tokenAddress; + uint256 expirationTimestamp; + AirdropContentERC721[] contents; + } + + struct AirdropRequestERC1155 { + bytes32 uid; + address tokenAddress; + uint256 expirationTimestamp; + AirdropContentERC1155[] contents; + } + + bytes32 private constant CONTENT_TYPEHASH_ERC20 = + keccak256("AirdropContentERC20(address recipient,uint256 amount)"); + bytes32 private constant REQUEST_TYPEHASH_ERC20 = + keccak256( + "AirdropRequestERC20(bytes32 uid,address tokenAddress,uint256 expirationTimestamp,AirdropContentERC20[] contents)AirdropContentERC20(address recipient,uint256 amount)" + ); + + bytes32 private constant CONTENT_TYPEHASH_ERC721 = + keccak256("AirdropContentERC721(address recipient,uint256 tokenId)"); + bytes32 private constant REQUEST_TYPEHASH_ERC721 = + keccak256( + "AirdropRequestERC721(bytes32 uid,address tokenAddress,uint256 expirationTimestamp,AirdropContentERC721[] contents)AirdropContentERC721(address recipient,uint256 tokenId)" + ); + + bytes32 private constant CONTENT_TYPEHASH_ERC1155 = + keccak256("AirdropContentERC1155(address recipient,uint256 tokenId,uint256 amount)"); + bytes32 private constant REQUEST_TYPEHASH_ERC1155 = + keccak256( + "AirdropRequestERC1155(bytes32 uid,address tokenAddress,uint256 expirationTimestamp,AirdropContentERC1155[] contents)AirdropContentERC1155(address recipient,uint256 tokenId,uint256 amount)" + ); + + address private constant NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + + /*/////////////////////////////////////////////////////////////// + Errors + //////////////////////////////////////////////////////////////*/ + + 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 AirdropClaimed(address token, address receiver); + + /*/////////////////////////////////////////////////////////////// + Constructor + //////////////////////////////////////////////////////////////*/ + + constructor() { + _disableInitializers(); + } + + function initialize(address _defaultAdmin) external initializer { + _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 + //////////////////////////////////////////////////////////////*/ + + 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(); + } + } + + if (nativeTokenAmount != msg.value) { + revert AirdropValueMismatch(); + } + + emit Airdrop(NATIVE_TOKEN_ADDRESS); + } + + function airdropERC20(address _tokenAddress, AirdropContentERC20[] calldata _contents) external onlyOwner { + uint256 len = _contents.length; + + for (uint256 i = 0; i < len; i++) { + SafeTransferLib.safeTransferFrom(_tokenAddress, msg.sender, _contents[i].recipient, _contents[i].amount); + } + + emit Airdrop(_tokenAddress); + } + + function airdropERC721(address _tokenAddress, AirdropContentERC721[] calldata _contents) external onlyOwner { + uint256 len = _contents.length; + + for (uint256 i = 0; i < len; i++) { + IERC721(_tokenAddress).safeTransferFrom(msg.sender, _contents[i].recipient, _contents[i].tokenId); + } + + emit Airdrop(_tokenAddress); + } + + function airdropERC1155(address _tokenAddress, AirdropContentERC1155[] calldata _contents) external onlyOwner { + uint256 len = _contents.length; + + for (uint256 i = 0; i < len; i++) { + IERC1155(_tokenAddress).safeTransferFrom( + msg.sender, + _contents[i].recipient, + _contents[i].tokenId, + _contents[i].amount, + "" + ); + } + + emit Airdrop(_tokenAddress); + } + + /*/////////////////////////////////////////////////////////////// + Airdrop With Signature + //////////////////////////////////////////////////////////////*/ + + function airdropERC20WithSignature(AirdropRequestERC20 calldata req, bytes calldata signature) external { + // verify expiration timestamp + if (req.expirationTimestamp < block.timestamp) { + revert AirdropRequestExpired(req.expirationTimestamp); + } + + if (processed[req.uid]) { + revert AirdropRequestAlreadyProcessed(); + } + + // verify data + if (!_verifyRequestSignerERC20(req, signature)) { + revert AirdropRequestInvalidSigner(); + } + + processed[req.uid] = true; + + uint256 len = req.contents.length; + address _from = owner(); + + for (uint256 i = 0; i < len; i++) { + SafeTransferLib.safeTransferFrom( + req.tokenAddress, + _from, + req.contents[i].recipient, + req.contents[i].amount + ); + } + + emit Airdrop(req.tokenAddress); + } + + function airdropERC721WithSignature(AirdropRequestERC721 calldata req, bytes calldata signature) external { + // verify expiration timestamp + if (req.expirationTimestamp < block.timestamp) { + revert AirdropRequestExpired(req.expirationTimestamp); + } + + if (processed[req.uid]) { + revert AirdropRequestAlreadyProcessed(); + } + + // verify data + if (!_verifyRequestSignerERC721(req, signature)) { + revert AirdropRequestInvalidSigner(); + } + + processed[req.uid] = true; + + address _from = owner(); + uint256 len = req.contents.length; + + for (uint256 i = 0; i < len; i++) { + IERC721(req.tokenAddress).safeTransferFrom(_from, req.contents[i].recipient, req.contents[i].tokenId); + } + + emit Airdrop(req.tokenAddress); + } + + function airdropERC1155WithSignature(AirdropRequestERC1155 calldata req, bytes calldata signature) external { + // verify expiration timestamp + if (req.expirationTimestamp < block.timestamp) { + revert AirdropRequestExpired(req.expirationTimestamp); + } + + if (processed[req.uid]) { + revert AirdropRequestAlreadyProcessed(); + } + + // verify data + if (!_verifyRequestSignerERC1155(req, signature)) { + revert AirdropRequestInvalidSigner(); + } + + processed[req.uid] = true; + + address _from = owner(); + uint256 len = req.contents.length; + + for (uint256 i = 0; i < len; i++) { + IERC1155(req.tokenAddress).safeTransferFrom( + _from, + req.contents[i].recipient, + req.contents[i].tokenId, + req.contents[i].amount, + "" + ); + } + + emit Airdrop(req.tokenAddress); + } + + /*/////////////////////////////////////////////////////////////// + Airdrop Claimable + //////////////////////////////////////////////////////////////*/ + + function claimERC20(address _token, address _receiver, uint256 _quantity, bytes32[] calldata _proofs) external { + bytes32 claimHash = _getClaimHashERC20(_receiver, _token); + uint256 conditionId = tokenConditionId[_token]; + + if (claimed[conditionId][claimHash]) { + revert AirdropAlreadyClaimed(); + } + + bytes32 _tokenMerkleRoot = tokenMerkleRoot[_token]; + if (_tokenMerkleRoot == bytes32(0)) { + revert AirdropNoMerkleRoot(); + } + + bool valid = MerkleProofLib.verify( + _proofs, + _tokenMerkleRoot, + keccak256(abi.encodePacked(_receiver, _quantity)) + ); + if (!valid) { + revert AirdropInvalidProof(); + } + + claimed[conditionId][claimHash] = true; + + SafeTransferLib.safeTransferFrom(_token, owner(), _receiver, _quantity); + + emit AirdropClaimed(_token, _receiver); + } + + function claimERC721(address _token, address _receiver, uint256 _tokenId, bytes32[] calldata _proofs) external { + bytes32 claimHash = _getClaimHashERC721(_receiver, _token, _tokenId); + uint256 conditionId = tokenConditionId[_token]; + + if (claimed[conditionId][claimHash]) { + revert AirdropAlreadyClaimed(); + } + + bytes32 _tokenMerkleRoot = tokenMerkleRoot[_token]; + if (_tokenMerkleRoot == bytes32(0)) { + revert AirdropNoMerkleRoot(); + } + + bool valid = MerkleProofLib.verify(_proofs, _tokenMerkleRoot, keccak256(abi.encodePacked(_receiver, _tokenId))); + if (!valid) { + revert AirdropInvalidProof(); + } + + claimed[conditionId][claimHash] = true; + + IERC721(_token).safeTransferFrom(owner(), _receiver, _tokenId); + + emit AirdropClaimed(_token, _receiver); + } + + function claimERC1155( + address _token, + address _receiver, + uint256 _tokenId, + uint256 _quantity, + bytes32[] calldata _proofs + ) external { + bytes32 claimHash = _getClaimHashERC1155(_receiver, _token, _tokenId); + uint256 conditionId = tokenConditionId[_token]; + + if (claimed[conditionId][claimHash]) { + revert AirdropAlreadyClaimed(); + } + + bytes32 _tokenMerkleRoot = tokenMerkleRoot[_token]; + if (_tokenMerkleRoot == bytes32(0)) { + revert AirdropNoMerkleRoot(); + } + + bool valid = MerkleProofLib.verify( + _proofs, + _tokenMerkleRoot, + keccak256(abi.encodePacked(_receiver, _tokenId, _quantity)) + ); + if (!valid) { + revert AirdropInvalidProof(); + } + + claimed[conditionId][claimHash] = true; + + IERC1155(_token).safeTransferFrom(owner(), _receiver, _tokenId, _quantity, ""); + + emit AirdropClaimed(_token, _receiver); + } + + /*/////////////////////////////////////////////////////////////// + Setter functions + //////////////////////////////////////////////////////////////*/ + + function setMerkleRoot(address _token, bytes32 _tokenMerkleRoot, bool _resetClaimStatus) external onlyOwner { + if (_resetClaimStatus || tokenConditionId[_token] == 0) { + tokenConditionId[_token] += 1; + } + tokenMerkleRoot[_token] = _tokenMerkleRoot; + } + + /*/////////////////////////////////////////////////////////////// + Miscellaneous + //////////////////////////////////////////////////////////////*/ + + function isClaimed(address _receiver, address _token, uint256 _tokenId) external view returns (bool) { + uint256 _conditionId = tokenConditionId[_token]; + + bytes32 claimHash = keccak256(abi.encodePacked(_receiver, _token, _tokenId)); + if (claimed[_conditionId][claimHash]) { + return true; + } + + claimHash = keccak256(abi.encodePacked(_receiver, _token)); + if (claimed[_conditionId][claimHash]) { + return true; + } + + return false; + } + + function _canSetOwner() internal view virtual override returns (bool) { + return msg.sender == owner(); + } + + function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) { + name = "Airdrop"; + version = "1"; + } + + function _getClaimHashERC20(address _receiver, address _token) private view returns (bytes32) { + return keccak256(abi.encodePacked(_receiver, _token)); + } + + function _getClaimHashERC721(address _receiver, address _token, uint256 _tokenId) private view returns (bytes32) { + return keccak256(abi.encodePacked(_receiver, _token, _tokenId)); + } + + function _getClaimHashERC1155(address _receiver, address _token, uint256 _tokenId) private view returns (bytes32) { + return keccak256(abi.encodePacked(_receiver, _token, _tokenId)); + } + + function _hashContentInfoERC20(AirdropContentERC20[] calldata contents) private pure returns (bytes32) { + bytes32[] memory contentHashes = new bytes32[](contents.length); + for (uint256 i = 0; i < contents.length; i++) { + contentHashes[i] = keccak256(abi.encode(CONTENT_TYPEHASH_ERC20, contents[i].recipient, contents[i].amount)); + } + return keccak256(abi.encodePacked(contentHashes)); + } + + function _hashContentInfoERC721(AirdropContentERC721[] calldata contents) private pure returns (bytes32) { + bytes32[] memory contentHashes = new bytes32[](contents.length); + for (uint256 i = 0; i < contents.length; i++) { + contentHashes[i] = keccak256( + abi.encode(CONTENT_TYPEHASH_ERC721, contents[i].recipient, contents[i].tokenId) + ); + } + return keccak256(abi.encodePacked(contentHashes)); + } + + function _hashContentInfoERC1155(AirdropContentERC1155[] calldata contents) private pure returns (bytes32) { + bytes32[] memory contentHashes = new bytes32[](contents.length); + for (uint256 i = 0; i < contents.length; i++) { + contentHashes[i] = keccak256( + abi.encode(CONTENT_TYPEHASH_ERC1155, contents[i].recipient, contents[i].tokenId, contents[i].amount) + ); + } + return keccak256(abi.encodePacked(contentHashes)); + } + + function _verifyRequestSignerERC20( + AirdropRequestERC20 calldata req, + bytes calldata signature + ) private view returns (bool) { + bytes32 contentHash = _hashContentInfoERC20(req.contents); + bytes32 structHash = keccak256( + abi.encode(REQUEST_TYPEHASH_ERC20, req.uid, req.tokenAddress, req.expirationTimestamp, contentHash) + ); + + bytes32 digest = _hashTypedData(structHash); + address recovered = digest.recover(signature); + return recovered == owner(); + } + + function _verifyRequestSignerERC721( + AirdropRequestERC721 calldata req, + bytes calldata signature + ) private view returns (bool) { + bytes32 contentHash = _hashContentInfoERC721(req.contents); + bytes32 structHash = keccak256( + abi.encode(REQUEST_TYPEHASH_ERC721, req.uid, req.tokenAddress, req.expirationTimestamp, contentHash) + ); + + bytes32 digest = _hashTypedData(structHash); + address recovered = digest.recover(signature); + return recovered == owner(); + } + + function _verifyRequestSignerERC1155( + AirdropRequestERC1155 calldata req, + bytes calldata signature + ) private view returns (bool) { + bytes32 contentHash = _hashContentInfoERC1155(req.contents); + bytes32 structHash = keccak256( + abi.encode(REQUEST_TYPEHASH_ERC1155, req.uid, req.tokenAddress, req.expirationTimestamp, contentHash) + ); + + bytes32 digest = _hashTypedData(structHash); + address recovered = digest.recover(signature); + return recovered == owner(); + } +} diff --git a/foundry.toml b/foundry.toml index a86fc9b13..1512dadaa 100644 --- a/foundry.toml +++ b/foundry.toml @@ -39,6 +39,7 @@ remappings = [ 'erc721a/=lib/ERC721A/', '@thirdweb-dev/dynamic-contracts/=lib/dynamic-contracts/', 'lib/sstore2=lib/dynamic-contracts/lib/sstore2/', + '@solady/=lib/solady/', ] fs_permissions = [{ access = "read-write", path = "./src/test/smart-wallet/utils"}] src = 'contracts' diff --git a/gasreport.txt b/gasreport.txt index ea8d57ffc..5cd46c496 100644 --- a/gasreport.txt +++ b/gasreport.txt @@ -1,181 +1,255 @@ -No files changed, compilation skipped - -Running 2 tests for src/test/benchmark/MultiwrapBenchmark.t.sol:MultiwrapBenchmarkTest -[PASS] test_benchmark_multiwrap_unwrap() (gas: 88950) -[PASS] test_benchmark_multiwrap_wrap() (gas: 473462) -Test result: ok. 2 passed; 0 failed; 0 skipped; finished in 236.08ms - -Running 3 tests for src/test/benchmark/EditionStakeBenchmark.t.sol:EditionStakeBenchmarkTest -[PASS] test_benchmark_editionStake_claimRewards() (gas: 65081) -[PASS] test_benchmark_editionStake_stake() (gas: 185144) -[PASS] test_benchmark_editionStake_withdraw() (gas: 46364) -Test result: ok. 3 passed; 0 failed; 0 skipped; finished in 238.67ms - -Running 3 tests for src/test/benchmark/TokenStakeBenchmark.t.sol:TokenStakeBenchmarkTest -[PASS] test_benchmark_tokenStake_claimRewards() (gas: 67554) -[PASS] test_benchmark_tokenStake_stake() (gas: 177180) -[PASS] test_benchmark_tokenStake_withdraw() (gas: 47396) -Test result: ok. 3 passed; 0 failed; 0 skipped; finished in 241.12ms - -Running 4 tests for src/test/benchmark/TokenERC1155Benchmark.t.sol:TokenERC1155BenchmarkTest -[PASS] test_benchmark_tokenERC1155_burn() (gas: 5728) -[PASS] test_benchmark_tokenERC1155_mintTo() (gas: 122286) -[PASS] test_benchmark_tokenERC1155_mintWithSignature_pay_with_ERC20() (gas: 267175) -[PASS] test_benchmark_tokenERC1155_mintWithSignature_pay_with_native_token() (gas: 296172) -Test result: ok. 4 passed; 0 failed; 0 skipped; finished in 240.92ms - -Running 1 test for src/test/benchmark/AirdropERC1155Benchmark.t.sol:AirdropERC1155BenchmarkTest -[PASS] test_benchmark_airdropERC1155_airdrop() (gas: 38083572) -Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 269.27ms - -Running 1 test for src/test/benchmark/AirdropERC20Benchmark.t.sol:AirdropERC20BenchmarkTest -[PASS] test_benchmark_airdropERC20_airdrop() (gas: 32068413) -Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 270.94ms - -Running 1 test for src/test/smart-wallet/utils/AABenchmarkPrepare.sol:AABenchmarkPrepare -[PASS] test_prepareBenchmarkFile() (gas: 2926370) -Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 272.43ms - -Running 1 test for src/test/benchmark/AirdropERC721Benchmark.t.sol:AirdropERC721BenchmarkTest -[PASS] test_benchmark_airdropERC721_airdrop() (gas: 41912536) -Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 297.69ms - -Running 4 tests for src/test/benchmark/TokenERC721Benchmark.t.sol:TokenERC721BenchmarkTest -[PASS] test_benchmark_tokenERC721_burn() (gas: 8954) -[PASS] test_benchmark_tokenERC721_mintTo() (gas: 151552) -[PASS] test_benchmark_tokenERC721_mintWithSignature_pay_with_ERC20() (gas: 262344) -[PASS] test_benchmark_tokenERC721_mintWithSignature_pay_with_native_token() (gas: 286914) -Test result: ok. 4 passed; 0 failed; 0 skipped; finished in 242.17ms - -Running 3 tests for src/test/benchmark/NFTStakeBenchmark.t.sol:NFTStakeBenchmarkTest -[PASS] test_benchmark_nftStake_claimRewards() (gas: 68287) -[PASS] test_benchmark_nftStake_stake_five_tokens() (gas: 539145) -[PASS] test_benchmark_nftStake_withdraw() (gas: 38076) -Test result: ok. 3 passed; 0 failed; 0 skipped; finished in 279.71ms - -Running 5 tests for src/test/benchmark/SignatureDropBenchmark.t.sol:SignatureDropBenchmarkTest -[PASS] test_benchmark_signatureDrop_claim_five_tokens() (gas: 140517) -[PASS] test_benchmark_signatureDrop_lazyMint() (gas: 124311) -[PASS] test_benchmark_signatureDrop_lazyMint_for_delayed_reveal() (gas: 225891) -[PASS] test_benchmark_signatureDrop_reveal() (gas: 10647) -[PASS] test_benchmark_signatureDrop_setClaimConditions() (gas: 73699) -Test result: ok. 5 passed; 0 failed; 0 skipped; finished in 251.39ms - -Running 3 tests for src/test/benchmark/TokenERC20Benchmark.t.sol:TokenERC20BenchmarkTest -[PASS] test_benchmark_tokenERC20_mintTo() (gas: 118586) -[PASS] test_benchmark_tokenERC20_mintWithSignature_pay_with_ERC20() (gas: 183032) -[PASS] test_benchmark_tokenERC20_mintWithSignature_pay_with_native_token() (gas: 207694) -Test result: ok. 3 passed; 0 failed; 0 skipped; finished in 287.73ms - -Running 14 tests for src/test/benchmark/AccountBenchmark.t.sol:AccountBenchmarkTest -[PASS] test_state_accountReceivesNativeTokens() (gas: 11037) -[PASS] test_state_addAndWithdrawDeposit() (gas: 83332) -[PASS] test_state_contractMetadata() (gas: 56507) -[PASS] test_state_createAccount_viaEntrypoint() (gas: 432040) -[PASS] test_state_createAccount_viaFactory() (gas: 334122) -[PASS] test_state_executeBatchTransaction() (gas: 39874) -[PASS] test_state_executeBatchTransaction_viaAccountSigner() (gas: 392782) -[PASS] test_state_executeBatchTransaction_viaEntrypoint() (gas: 82915) -[PASS] test_state_executeTransaction() (gas: 35735) -[PASS] test_state_executeTransaction_viaAccountSigner() (gas: 378632) -[PASS] test_state_executeTransaction_viaEntrypoint() (gas: 75593) -[PASS] test_state_receiveERC1155NFT() (gas: 39343) -[PASS] test_state_receiveERC721NFT() (gas: 78624) -[PASS] test_state_transferOutsNativeTokens() (gas: 81713) -Test result: ok. 14 passed; 0 failed; 0 skipped; finished in 364.96ms - -Running 3 tests for src/test/benchmark/PackBenchmark.t.sol:PackBenchmarkTest -[PASS] test_benchmark_pack_addPackContents() (gas: 219188) -[PASS] test_benchmark_pack_createPack() (gas: 1412868) -[PASS] test_benchmark_pack_openPack() (gas: 141860) -Test result: ok. 3 passed; 0 failed; 0 skipped; finished in 258.39ms - -Running 3 tests for src/test/benchmark/PackVRFDirectBenchmark.t.sol:PackVRFDirectBenchmarkTest -[PASS] test_benchmark_packvrf_createPack() (gas: 1379604) -[PASS] test_benchmark_packvrf_openPack() (gas: 119953) +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). + + +Ran 5 tests for src/test/benchmark/SignatureDropBenchmark.t.sol:SignatureDropBenchmarkTest +[PASS] test_benchmark_signatureDrop_claim_five_tokens() (gas: 185688) +[PASS] test_benchmark_signatureDrop_lazyMint() (gas: 147153) +[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) + +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) + +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) + +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) + +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) + +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) + +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) -Test result: ok. 3 passed; 0 failed; 0 skipped; finished in 285.64ms - -Running 3 tests for src/test/benchmark/DropERC1155Benchmark.t.sol:DropERC1155BenchmarkTest -[PASS] test_benchmark_dropERC1155_claim() (gas: 185032) -[PASS] test_benchmark_dropERC1155_lazyMint() (gas: 123913) -[PASS] test_benchmark_dropERC1155_setClaimConditions_five_conditions() (gas: 492121) -Test result: ok. 3 passed; 0 failed; 0 skipped; finished in 735.56ms - -Running 5 tests for src/test/benchmark/DropERC721Benchmark.t.sol:DropERC721BenchmarkTest -[PASS] test_benchmark_dropERC721_claim_five_tokens() (gas: 210967) -[PASS] test_benchmark_dropERC721_lazyMint() (gas: 124540) -[PASS] test_benchmark_dropERC721_lazyMint_for_delayed_reveal() (gas: 226149) -[PASS] test_benchmark_dropERC721_reveal() (gas: 13732) -[PASS] test_benchmark_dropERC721_setClaimConditions_five_conditions() (gas: 500494) -Test result: ok. 5 passed; 0 failed; 0 skipped; finished in 742.03ms - -Running 2 tests for src/test/benchmark/DropERC20Benchmark.t.sol:DropERC20BenchmarkTest -[PASS] test_benchmark_dropERC20_claim() (gas: 230505) -[PASS] test_benchmark_dropERC20_setClaimConditions_five_conditions() (gas: 500858) -Test result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.12s - - -Ran 18 test suites: 61 tests passed, 0 failed, 0 skipped (61 total tests) -test_state_accountReceivesNativeTokens() (gas: 0 (0.000%)) -test_state_addAndWithdrawDeposit() (gas: 0 (0.000%)) -test_state_contractMetadata() (gas: 0 (0.000%)) -test_state_createAccount_viaEntrypoint() (gas: 0 (0.000%)) -test_state_createAccount_viaFactory() (gas: 0 (0.000%)) -test_state_executeBatchTransaction() (gas: 0 (0.000%)) -test_state_executeBatchTransaction_viaAccountSigner() (gas: 0 (0.000%)) -test_state_executeBatchTransaction_viaEntrypoint() (gas: 0 (0.000%)) -test_state_executeTransaction() (gas: 0 (0.000%)) -test_state_executeTransaction_viaAccountSigner() (gas: 0 (0.000%)) -test_state_executeTransaction_viaEntrypoint() (gas: 0 (0.000%)) -test_state_receiveERC1155NFT() (gas: 0 (0.000%)) -test_state_receiveERC721NFT() (gas: 0 (0.000%)) -test_state_transferOutsNativeTokens() (gas: 0 (0.000%)) -test_benchmark_airdropERC1155_airdrop() (gas: 0 (0.000%)) -test_benchmark_airdropERC20_airdrop() (gas: 0 (0.000%)) -test_benchmark_airdropERC721_airdrop() (gas: 0 (0.000%)) -test_benchmark_dropERC1155_claim() (gas: 0 (0.000%)) -test_benchmark_dropERC1155_lazyMint() (gas: 0 (0.000%)) -test_benchmark_dropERC1155_setClaimConditions_five_conditions() (gas: 0 (0.000%)) -test_benchmark_dropERC20_claim() (gas: 0 (0.000%)) -test_benchmark_dropERC20_setClaimConditions_five_conditions() (gas: 0 (0.000%)) -test_benchmark_dropERC721_claim_five_tokens() (gas: 0 (0.000%)) -test_benchmark_dropERC721_lazyMint() (gas: 0 (0.000%)) -test_benchmark_dropERC721_lazyMint_for_delayed_reveal() (gas: 0 (0.000%)) -test_benchmark_dropERC721_reveal() (gas: 0 (0.000%)) -test_benchmark_dropERC721_setClaimConditions_five_conditions() (gas: 0 (0.000%)) -test_benchmark_editionStake_claimRewards() (gas: 0 (0.000%)) -test_benchmark_editionStake_stake() (gas: 0 (0.000%)) -test_benchmark_editionStake_withdraw() (gas: 0 (0.000%)) -test_benchmark_multiwrap_unwrap() (gas: 0 (0.000%)) -test_benchmark_multiwrap_wrap() (gas: 0 (0.000%)) -test_benchmark_nftStake_claimRewards() (gas: 0 (0.000%)) -test_benchmark_nftStake_stake_five_tokens() (gas: 0 (0.000%)) -test_benchmark_nftStake_withdraw() (gas: 0 (0.000%)) -test_benchmark_pack_addPackContents() (gas: 0 (0.000%)) -test_benchmark_pack_createPack() (gas: 0 (0.000%)) -test_benchmark_pack_openPack() (gas: 0 (0.000%)) -test_benchmark_packvrf_createPack() (gas: 0 (0.000%)) -test_benchmark_packvrf_openPack() (gas: 0 (0.000%)) +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/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) + +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) + +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) + +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) + +Ran 5 tests for src/test/benchmark/DropERC721Benchmark.t.sol:DropERC721BenchmarkTest +[PASS] test_benchmark_dropERC721_claim_five_tokens() (gas: 273303) +[PASS] test_benchmark_dropERC721_lazyMint() (gas: 147052) +[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) + +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 20 test suites in 2.00s (13.97s CPU time): 103 tests passed, 0 failed, 0 skipped (103 total tests) test_benchmark_packvrf_openPackAndClaimRewards() (gas: 0 (0.000%)) -test_benchmark_signatureDrop_claim_five_tokens() (gas: 0 (0.000%)) -test_benchmark_signatureDrop_lazyMint() (gas: 0 (0.000%)) -test_benchmark_signatureDrop_lazyMint_for_delayed_reveal() (gas: 0 (0.000%)) -test_benchmark_signatureDrop_reveal() (gas: 0 (0.000%)) -test_benchmark_signatureDrop_setClaimConditions() (gas: 0 (0.000%)) -test_benchmark_tokenERC1155_burn() (gas: 0 (0.000%)) -test_benchmark_tokenERC1155_mintTo() (gas: 0 (0.000%)) -test_benchmark_tokenERC1155_mintWithSignature_pay_with_ERC20() (gas: 0 (0.000%)) -test_benchmark_tokenERC1155_mintWithSignature_pay_with_native_token() (gas: 0 (0.000%)) -test_benchmark_tokenERC20_mintTo() (gas: 0 (0.000%)) -test_benchmark_tokenERC20_mintWithSignature_pay_with_ERC20() (gas: 0 (0.000%)) -test_benchmark_tokenERC20_mintWithSignature_pay_with_native_token() (gas: 0 (0.000%)) -test_benchmark_tokenERC721_burn() (gas: 0 (0.000%)) -test_benchmark_tokenERC721_mintTo() (gas: 0 (0.000%)) -test_benchmark_tokenERC721_mintWithSignature_pay_with_ERC20() (gas: 0 (0.000%)) -test_benchmark_tokenERC721_mintWithSignature_pay_with_native_token() (gas: 0 (0.000%)) -test_benchmark_tokenStake_claimRewards() (gas: 0 (0.000%)) -test_benchmark_tokenStake_stake() (gas: 0 (0.000%)) -test_benchmark_tokenStake_withdraw() (gas: 0 (0.000%)) -test_prepareBenchmarkFile() (gas: 0 (0.000%)) -Overall gas change: 0 (0.000%) +test_benchmark_pack_createPack() (gas: 6511 (0.461%)) +test_benchmark_airdropERC721_airdrop() (gas: 329052 (0.785%)) +test_benchmark_packvrf_createPack() (gas: 12783 (0.927%)) +test_prepareBenchmarkFile() (gas: 29400 (1.005%)) +test_benchmark_airdropERC20_airdrop() (gas: 375372 (1.171%)) +test_benchmark_airdropERC1155_airdrop() (gas: 452972 (1.189%)) +test_benchmark_multiwrap_wrap() (gas: 7260 (1.533%)) +test_benchmark_nftStake_stake_five_tokens() (gas: 14432 (2.677%)) +test_benchmark_dropERC721_setClaimConditions_five_conditions() (gas: 28976 (5.789%)) +test_benchmark_dropERC20_setClaimConditions_five_conditions() (gas: 29168 (5.824%)) +test_state_createAccount_viaEntrypoint() (gas: 26152 (6.053%)) +test_state_createAccount_viaFactory() (gas: 21700 (6.495%)) +test_benchmark_dropERC1155_setClaimConditions_five_conditions() (gas: 33604 (6.828%)) +test_benchmark_tokenERC1155_mintWithSignature_pay_with_native_token() (gas: 22540 (7.610%)) +test_benchmark_tokenERC721_mintWithSignature_pay_with_native_token() (gas: 21900 (7.633%)) +test_benchmark_editionStake_stake() (gas: 18532 (10.010%)) +test_benchmark_dropERC721_lazyMint_for_delayed_reveal() (gas: 22836 (10.098%)) +test_benchmark_tokenERC20_mintWithSignature_pay_with_native_token() (gas: 21092 (10.155%)) +test_benchmark_signatureDrop_lazyMint_for_delayed_reveal() (gas: 23166 (10.255%)) +test_benchmark_tokenStake_stake() (gas: 18376 (10.371%)) +test_benchmark_tokenERC721_mintTo() (gas: 21282 (14.043%)) +test_benchmark_tokenERC1155_mintWithSignature_pay_with_ERC20() (gas: 40116 (15.015%)) +test_benchmark_tokenERC721_mintWithSignature_pay_with_ERC20() (gas: 39500 (15.057%)) +test_benchmark_tokenERC20_mintTo() (gas: 20927 (17.647%)) +test_benchmark_tokenERC1155_mintTo() (gas: 21943 (17.944%)) +test_benchmark_dropERC721_lazyMint() (gas: 22512 (18.076%)) +test_benchmark_dropERC1155_lazyMint() (gas: 22512 (18.168%)) +test_benchmark_signatureDrop_lazyMint() (gas: 22842 (18.375%)) +test_benchmark_tokenERC20_mintWithSignature_pay_with_ERC20() (gas: 38692 (21.139%)) +test_state_executeBatchTransaction_viaAccountSigner() (gas: 95688 (24.362%)) +test_state_executeTransaction_viaAccountSigner() (gas: 92640 (24.467%)) +test_benchmark_packvrf_openPack() (gas: 30724 (25.613%)) +test_benchmark_dropERC20_claim() (gas: 61003 (26.465%)) +test_state_receiveERC721NFT() (gas: 21572 (27.437%)) +test_benchmark_dropERC721_claim_five_tokens() (gas: 62336 (29.548%)) +test_benchmark_signatureDrop_claim_five_tokens() (gas: 45171 (32.146%)) +test_benchmark_dropERC1155_claim() (gas: 60520 (32.708%)) +test_benchmark_signatureDrop_setClaimConditions() (gas: 27020 (36.663%)) +test_benchmark_pack_addPackContents() (gas: 93407 (42.615%)) +test_benchmark_nftStake_claimRewards() (gas: 31544 (46.193%)) +test_benchmark_tokenStake_claimRewards() (gas: 33544 (49.655%)) +test_benchmark_editionStake_claimRewards() (gas: 33684 (51.757%)) +test_state_transferOutsNativeTokens() (gas: 51960 (63.588%)) +test_state_executeBatchTransaction_viaEntrypoint() (gas: 55528 (66.970%)) +test_state_receiveERC1155NFT() (gas: 26700 (67.865%)) +test_state_executeTransaction_viaEntrypoint() (gas: 52480 (69.424%)) +test_benchmark_multiwrap_unwrap() (gas: 63090 (70.927%)) +test_state_addAndWithdrawDeposit() (gas: 65448 (78.539%)) +test_state_executeBatchTransaction() (gas: 36192 (90.766%)) +test_state_executeTransaction() (gas: 33156 (92.783%)) +test_state_contractMetadata() (gas: 57800 (102.288%)) +test_benchmark_editionStake_withdraw() (gas: 47932 (103.382%)) +test_benchmark_pack_openPack() (gas: 160752 (113.317%)) +test_benchmark_tokenStake_withdraw() (gas: 57396 (121.099%)) +test_benchmark_nftStake_withdraw() (gas: 58068 (152.506%)) +test_state_accountReceivesNativeTokens() (gas: 23500 (212.920%)) +test_benchmark_dropERC721_reveal() (gas: 35701 (259.984%)) +test_benchmark_tokenERC721_burn() (gas: 31438 (351.106%)) +test_benchmark_signatureDrop_reveal() (gas: 39155 (367.756%)) +test_benchmark_tokenERC1155_burn() (gas: 24624 (429.888%)) +Overall gas change: 3375923 (2.652%) diff --git a/lib/solady b/lib/solady new file mode 160000 index 000000000..c6738e402 --- /dev/null +++ b/lib/solady @@ -0,0 +1 @@ +Subproject commit c6738e40225288842ce890cd265a305684e52c3d diff --git a/package.json b/package.json index 1867ecd21..5f73d961b 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "build": "yarn clean && yarn compile", "forge:build": "forge build", "forge:test": "forge test", - "gas": "forge snapshot --mc Benchmark --gas-report --diff .gas-snapshot > gasreport.txt", + "gas": "forge snapshot --isolate --mc Benchmark --gas-report --diff .gas-snapshot > gasreport.txt", "forge:snapshot": "forge snapshot --check", "aabenchmark": "forge test --mc AABenchmarkPrepare && forge test --mc ProfileThirdwebAccount -vvv" } diff --git a/src/test/airdrop/Airdrop.t.sol b/src/test/airdrop/Airdrop.t.sol new file mode 100644 index 000000000..3eef9d519 --- /dev/null +++ b/src/test/airdrop/Airdrop.t.sol @@ -0,0 +1,875 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import { Airdrop } from "contracts/prebuilts/unaudited/airdrop/Airdrop.sol"; + +// Test imports +import { TWProxy } from "contracts/infra/TWProxy.sol"; +import "../utils/BaseTest.sol"; + +contract AirdropTest is BaseTest { + Airdrop internal airdrop; + + bytes32 private constant CONTENT_TYPEHASH_ERC20 = + keccak256("AirdropContentERC20(address recipient,uint256 amount)"); + bytes32 private constant REQUEST_TYPEHASH_ERC20 = + keccak256( + "AirdropRequestERC20(bytes32 uid,address tokenAddress,uint256 expirationTimestamp,AirdropContentERC20[] contents)AirdropContentERC20(address recipient,uint256 amount)" + ); + + bytes32 private constant CONTENT_TYPEHASH_ERC721 = + keccak256("AirdropContentERC721(address recipient,uint256 tokenId)"); + bytes32 private constant REQUEST_TYPEHASH_ERC721 = + keccak256( + "AirdropRequestERC721(bytes32 uid,address tokenAddress,uint256 expirationTimestamp,AirdropContentERC721[] contents)AirdropContentERC721(address recipient,uint256 tokenId)" + ); + + bytes32 private constant CONTENT_TYPEHASH_ERC1155 = + keccak256("AirdropContentERC1155(address recipient,uint256 tokenId,uint256 amount)"); + bytes32 private constant REQUEST_TYPEHASH_ERC1155 = + keccak256( + "AirdropRequestERC1155(bytes32 uid,address tokenAddress,uint256 expirationTimestamp,AirdropContentERC1155[] contents)AirdropContentERC1155(address recipient,uint256 tokenId,uint256 amount)" + ); + + bytes32 private constant NAME_HASH = keccak256(bytes("Airdrop")); + bytes32 private constant VERSION_HASH = keccak256(bytes("1")); + bytes32 private constant TYPE_HASH_EIP712 = + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + + bytes32 internal domainSeparator; + + function setUp() public override { + super.setUp(); + + address impl = address(new Airdrop()); + + airdrop = Airdrop(payable(address(new TWProxy(impl, abi.encodeCall(Airdrop.initialize, (signer)))))); + + domainSeparator = keccak256( + abi.encode(TYPE_HASH_EIP712, NAME_HASH, VERSION_HASH, block.chainid, address(airdrop)) + ); + } + + function _getContentsERC20(uint256 length) internal pure returns (Airdrop.AirdropContentERC20[] memory contents) { + contents = new Airdrop.AirdropContentERC20[](length); + for (uint256 i = 0; i < length; i++) { + contents[i].recipient = address(uint160(i + 10)); + contents[i].amount = i + 10; + } + } + + function _getContentsERC721(uint256 length) internal pure returns (Airdrop.AirdropContentERC721[] memory contents) { + contents = new Airdrop.AirdropContentERC721[](length); + for (uint256 i = 0; i < length; i++) { + contents[i].recipient = address(uint160(i + 10)); + contents[i].tokenId = i; + } + } + + function _getContentsERC1155( + uint256 length + ) internal pure returns (Airdrop.AirdropContentERC1155[] memory contents) { + contents = new Airdrop.AirdropContentERC1155[](length); + for (uint256 i = 0; i < length; i++) { + contents[i].recipient = address(uint160(i + 10)); + contents[i].tokenId = 0; + contents[i].amount = i + 10; + } + } + + function _signReqERC20( + Airdrop.AirdropRequestERC20 memory req, + uint256 privateKey + ) internal view returns (bytes memory signature) { + bytes32[] memory contentHashes = new bytes32[](req.contents.length); + for (uint i = 0; i < req.contents.length; i++) { + contentHashes[i] = keccak256( + abi.encode(CONTENT_TYPEHASH_ERC20, req.contents[i].recipient, req.contents[i].amount) + ); + } + bytes32 contentHash = keccak256(abi.encodePacked(contentHashes)); + + bytes memory dataToHash; + { + dataToHash = abi.encode( + REQUEST_TYPEHASH_ERC20, + req.uid, + req.tokenAddress, + req.expirationTimestamp, + contentHash + ); + } + + { + bytes32 _structHash = keccak256(dataToHash); + bytes32 typedDataHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator, _structHash)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, typedDataHash); + + signature = abi.encodePacked(r, s, v); + } + } + + function _signReqERC721( + Airdrop.AirdropRequestERC721 memory req, + uint256 privateKey + ) internal view returns (bytes memory signature) { + bytes32[] memory contentHashes = new bytes32[](req.contents.length); + for (uint i = 0; i < req.contents.length; i++) { + contentHashes[i] = keccak256( + abi.encode(CONTENT_TYPEHASH_ERC721, req.contents[i].recipient, req.contents[i].tokenId) + ); + } + bytes32 contentHash = keccak256(abi.encodePacked(contentHashes)); + + bytes memory dataToHash; + { + dataToHash = abi.encode( + REQUEST_TYPEHASH_ERC721, + req.uid, + req.tokenAddress, + req.expirationTimestamp, + contentHash + ); + } + + { + bytes32 _structHash = keccak256(dataToHash); + bytes32 typedDataHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator, _structHash)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, typedDataHash); + + signature = abi.encodePacked(r, s, v); + } + } + + function _signReqERC1155( + Airdrop.AirdropRequestERC1155 memory req, + uint256 privateKey + ) internal view returns (bytes memory signature) { + bytes32[] memory contentHashes = new bytes32[](req.contents.length); + for (uint i = 0; i < req.contents.length; i++) { + contentHashes[i] = keccak256( + abi.encode( + CONTENT_TYPEHASH_ERC1155, + req.contents[i].recipient, + req.contents[i].tokenId, + req.contents[i].amount + ) + ); + } + bytes32 contentHash = keccak256(abi.encodePacked(contentHashes)); + + bytes memory dataToHash; + { + dataToHash = abi.encode( + REQUEST_TYPEHASH_ERC1155, + req.uid, + req.tokenAddress, + req.expirationTimestamp, + contentHash + ); + } + + { + bytes32 _structHash = keccak256(dataToHash); + bytes32 typedDataHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator, _structHash)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, typedDataHash); + + signature = abi.encodePacked(r, s, v); + } + } + + /*/////////////////////////////////////////////////////////////// + Benchmark: Airdrop Push ERC20 + //////////////////////////////////////////////////////////////*/ + + function test_state_airdropPush_erc20() public { + erc20.mint(signer, 100 ether); + vm.prank(signer); + erc20.approve(address(airdrop), 100 ether); + + Airdrop.AirdropContentERC20[] memory contents = _getContentsERC20(10); + + vm.prank(signer); + + airdrop.airdropERC20(address(erc20), contents); + + 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(signer), 100 ether - totalAmount); + } + + function test_state_airdropPush_nativeToken() public { + vm.deal(signer, 100 ether); + + Airdrop.AirdropContentERC20[] memory contents = _getContentsERC20(10); + + uint256 totalAmount; + for (uint256 i = 0; i < contents.length; i++) { + totalAmount += contents[i].amount; + assertEq(contents[i].recipient.balance, 0); + } + + vm.prank(signer); + airdrop.airdropNativeToken{ value: totalAmount }(contents); + + for (uint256 i = 0; i < contents.length; i++) { + assertEq(contents[i].recipient.balance, contents[i].amount); + } + + assertEq(signer.balance, 100 ether - totalAmount); + } + + function test_revert_airdropPush_nativeToken() public { + vm.deal(signer, 100 ether); + + 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)); + airdrop.airdropNativeToken{ value: 0 }(contents); + + // contract balance should remain untouched + assertEq(address(airdrop).balance, 50 ether); + } + + /*/////////////////////////////////////////////////////////////// + Benchmark: Airdrop Signature ERC20 + //////////////////////////////////////////////////////////////*/ + + function test_state_airdropSignature_erc20() public { + erc20.mint(signer, 100 ether); + vm.prank(signer); + 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 + }); + bytes memory signature = _signReqERC20(req, privateKey); + + vm.prank(signer); + + 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(signer), 100 ether - totalAmount); + } + + function test_revert_airdropSignature_erc20_expired() public { + erc20.mint(signer, 100 ether); + vm.prank(signer); + 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 + }); + bytes memory signature = _signReqERC20(req, privateKey); + + vm.warp(1001); + + vm.prank(signer); + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropRequestExpired.selector, req.expirationTimestamp)); + airdrop.airdropERC20WithSignature(req, signature); + } + + function test_revert_airdropSignature_erc20_alreadyProcessed() public { + erc20.mint(signer, 100 ether); + vm.prank(signer); + 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 + }); + bytes memory signature = _signReqERC20(req, privateKey); + + vm.prank(signer); + + airdrop.airdropERC20WithSignature(req, signature); + + // try re-sending same request/signature + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropRequestAlreadyProcessed.selector)); + airdrop.airdropERC20WithSignature(req, signature); + } + + function test_revert_airdropSignature_erc20_invalidSigner() public { + erc20.mint(signer, 100 ether); + vm.prank(signer); + 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 + }); + bytes memory signature = _signReqERC20(req, 123); + + vm.prank(signer); + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropRequestInvalidSigner.selector)); + airdrop.airdropERC20WithSignature(req, signature); + } + + /*/////////////////////////////////////////////////////////////// + Benchmark: Airdrop Claim ERC20 + //////////////////////////////////////////////////////////////*/ + + function test_state_airdropClaim_erc20() public { + erc20.mint(signer, 100 ether); + vm.prank(signer); + erc20.approve(address(airdrop), 100 ether); + + string[] memory inputs = new string[](3); + inputs[0] = "node"; + inputs[1] = "src/test/scripts/generateRootAirdrop.ts"; + inputs[2] = Strings.toString(uint256(5)); + bytes memory result = vm.ffi(inputs); + bytes32 root = abi.decode(result, (bytes32)); + + // set merkle root + vm.prank(signer); + airdrop.setMerkleRoot(address(erc20), root, true); + + // generate proof + inputs[1] = "src/test/scripts/getProofAirdrop.ts"; + result = vm.ffi(inputs); + bytes32[] memory proofs = abi.decode(result, (bytes32[])); + + address receiver = address(0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd); + uint256 quantity = 5; + + airdrop.claimERC20(address(erc20), receiver, quantity, proofs); + + assertEq(erc20.balanceOf(receiver), quantity); + assertEq(erc20.balanceOf(signer), 100 ether - quantity); + } + + function test_revert_airdropClaim_erc20_alreadyClaimed() public { + erc20.mint(signer, 100 ether); + vm.prank(signer); + erc20.approve(address(airdrop), 100 ether); + + string[] memory inputs = new string[](3); + inputs[0] = "node"; + inputs[1] = "src/test/scripts/generateRootAirdrop.ts"; + inputs[2] = Strings.toString(uint256(5)); + bytes memory result = vm.ffi(inputs); + bytes32 root = abi.decode(result, (bytes32)); + + // set merkle root + vm.prank(signer); + airdrop.setMerkleRoot(address(erc20), root, true); + + // generate proof + inputs[1] = "src/test/scripts/getProofAirdrop.ts"; + result = vm.ffi(inputs); + bytes32[] memory proofs = abi.decode(result, (bytes32[])); + + address receiver = address(0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd); + uint256 quantity = 5; + + airdrop.claimERC20(address(erc20), receiver, quantity, proofs); + + // revert when claiming again + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropAlreadyClaimed.selector)); + airdrop.claimERC20(address(erc20), receiver, quantity, proofs); + } + + function test_revert_airdropClaim_erc20_noMerkleRoot() public { + erc20.mint(signer, 100 ether); + vm.prank(signer); + erc20.approve(address(airdrop), 100 ether); + + bytes32[] memory proofs; + + address receiver = address(0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd); + uint256 quantity = 5; + + // revert when claiming again + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropNoMerkleRoot.selector)); + airdrop.claimERC20(address(erc20), receiver, quantity, proofs); + } + + function test_revert_airdropClaim_erc20_invalidProof() public { + erc20.mint(signer, 100 ether); + vm.prank(signer); + erc20.approve(address(airdrop), 100 ether); + + string[] memory inputs = new string[](3); + inputs[0] = "node"; + inputs[1] = "src/test/scripts/generateRootAirdrop.ts"; + inputs[2] = Strings.toString(uint256(5)); + bytes memory result = vm.ffi(inputs); + bytes32 root = abi.decode(result, (bytes32)); + + // set merkle root + vm.prank(signer); + airdrop.setMerkleRoot(address(erc20), root, true); + + // generate proof + inputs[1] = "src/test/scripts/getProofAirdrop.ts"; + result = vm.ffi(inputs); + bytes32[] memory proofs = abi.decode(result, (bytes32[])); + + address receiver = address(0x12345); + uint256 quantity = 5; + + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropInvalidProof.selector)); + airdrop.claimERC20(address(erc20), receiver, quantity, proofs); + } + + /*/////////////////////////////////////////////////////////////// + Benchmark: Airdrop Push ERC721 + //////////////////////////////////////////////////////////////*/ + + function test_state_airdropPush_erc721() public { + erc721.mint(signer, 100); + vm.prank(signer); + erc721.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC721[] memory contents = _getContentsERC721(10); + + vm.prank(signer); + + airdrop.airdropERC721(address(erc721), contents); + + for (uint256 i = 0; i < contents.length; i++) { + assertEq(erc721.ownerOf(contents[i].tokenId), contents[i].recipient); + } + } + + /*/////////////////////////////////////////////////////////////// + Benchmark: Airdrop Signature ERC721 + //////////////////////////////////////////////////////////////*/ + + function test_state_airdropSignature_erc721() public { + erc721.mint(signer, 1000); + vm.prank(signer); + 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 + }); + bytes memory signature = _signReqERC721(req, privateKey); + + vm.prank(signer); + + 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_expired() public { + erc721.mint(signer, 1000); + vm.prank(signer); + 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 + }); + bytes memory signature = _signReqERC721(req, privateKey); + + vm.warp(1001); + + vm.prank(signer); + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropRequestExpired.selector, req.expirationTimestamp)); + airdrop.airdropERC721WithSignature(req, signature); + } + + function test_revert_airdropSignature_erc721_alreadyProcessed() public { + erc721.mint(signer, 1000); + vm.prank(signer); + 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 + }); + bytes memory signature = _signReqERC721(req, privateKey); + + vm.prank(signer); + airdrop.airdropERC721WithSignature(req, signature); + + // send it again + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropRequestAlreadyProcessed.selector)); + airdrop.airdropERC721WithSignature(req, signature); + } + + function test_revert_airdropSignature_erc721_invalidSigner() public { + erc721.mint(signer, 1000); + vm.prank(signer); + 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 + }); + bytes memory signature = _signReqERC721(req, 123); + + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropRequestInvalidSigner.selector)); + airdrop.airdropERC721WithSignature(req, signature); + } + + /*/////////////////////////////////////////////////////////////// + Benchmark: Airdrop Claim ERC721 + //////////////////////////////////////////////////////////////*/ + + function test_state_airdropClaim_erc721() public { + erc721.mint(signer, 100); + vm.prank(signer); + erc721.setApprovalForAll(address(airdrop), true); + + string[] memory inputs = new string[](3); + inputs[0] = "node"; + inputs[1] = "src/test/scripts/generateRootAirdrop.ts"; + inputs[2] = Strings.toString(uint256(5)); + bytes memory result = vm.ffi(inputs); + bytes32 root = abi.decode(result, (bytes32)); + + // set merkle root + vm.prank(signer); + airdrop.setMerkleRoot(address(erc721), root, true); + + // generate proof + inputs[1] = "src/test/scripts/getProofAirdrop.ts"; + result = vm.ffi(inputs); + bytes32[] memory proofs = abi.decode(result, (bytes32[])); + + address receiver = address(0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd); + uint256 tokenId = 5; + + airdrop.claimERC721(address(erc721), receiver, tokenId, proofs); + + assertEq(erc721.ownerOf(tokenId), receiver); + } + + function test_revert_airdropClaim_erc721_alreadyClaimed() public { + erc721.mint(signer, 100); + vm.prank(signer); + erc721.setApprovalForAll(address(airdrop), true); + + string[] memory inputs = new string[](3); + inputs[0] = "node"; + inputs[1] = "src/test/scripts/generateRootAirdrop.ts"; + inputs[2] = Strings.toString(uint256(5)); + bytes memory result = vm.ffi(inputs); + bytes32 root = abi.decode(result, (bytes32)); + + // set merkle root + vm.prank(signer); + airdrop.setMerkleRoot(address(erc721), root, true); + + // generate proof + inputs[1] = "src/test/scripts/getProofAirdrop.ts"; + result = vm.ffi(inputs); + bytes32[] memory proofs = abi.decode(result, (bytes32[])); + + address receiver = address(0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd); + uint256 tokenId = 5; + + airdrop.claimERC721(address(erc721), receiver, tokenId, proofs); + + // revert when claiming again + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropAlreadyClaimed.selector)); + airdrop.claimERC721(address(erc721), receiver, tokenId, proofs); + } + + function test_revert_airdropClaim_erc721_noMerkleRoot() public { + erc721.mint(signer, 100); + vm.prank(signer); + erc721.setApprovalForAll(address(airdrop), true); + + bytes32[] memory proofs; + + address receiver = address(0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd); + uint256 tokenId = 5; + + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropNoMerkleRoot.selector)); + airdrop.claimERC721(address(erc721), receiver, tokenId, proofs); + } + + function test_revert_airdropClaim_erc721_invalidProof() public { + erc721.mint(signer, 100); + vm.prank(signer); + erc721.setApprovalForAll(address(airdrop), true); + + string[] memory inputs = new string[](3); + inputs[0] = "node"; + inputs[1] = "src/test/scripts/generateRootAirdrop.ts"; + inputs[2] = Strings.toString(uint256(5)); + bytes memory result = vm.ffi(inputs); + bytes32 root = abi.decode(result, (bytes32)); + + // set merkle root + vm.prank(signer); + airdrop.setMerkleRoot(address(erc721), root, true); + + // generate proof + inputs[1] = "src/test/scripts/getProofAirdrop.ts"; + result = vm.ffi(inputs); + bytes32[] memory proofs = abi.decode(result, (bytes32[])); + + address receiver = address(0x12345); + uint256 tokenId = 5; + + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropInvalidProof.selector)); + airdrop.claimERC721(address(erc721), receiver, tokenId, proofs); + } + + /*/////////////////////////////////////////////////////////////// + Benchmark: Airdrop Push ERC1155 + //////////////////////////////////////////////////////////////*/ + + function test_state_airdropPush_erc1155() public { + erc1155.mint(signer, 0, 100 ether); + vm.prank(signer); + erc1155.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC1155[] memory contents = _getContentsERC1155(10); + + vm.prank(signer); + + airdrop.airdropERC1155(address(erc1155), contents); + + for (uint256 i = 0; i < contents.length; i++) { + assertEq(erc1155.balanceOf(contents[i].recipient, contents[i].tokenId), contents[i].amount); + } + } + + /*/////////////////////////////////////////////////////////////// + Benchmark: Airdrop Signature ERC1155 + //////////////////////////////////////////////////////////////*/ + + function test_state_airdropSignature_erc115() public { + erc1155.mint(signer, 0, 100 ether); + vm.prank(signer); + 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 + }); + bytes memory signature = _signReqERC1155(req, privateKey); + + vm.prank(signer); + + 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_erc115_expired() public { + erc1155.mint(signer, 0, 100 ether); + vm.prank(signer); + 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 + }); + bytes memory signature = _signReqERC1155(req, privateKey); + + vm.warp(1001); + + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropRequestExpired.selector, req.expirationTimestamp)); + airdrop.airdropERC1155WithSignature(req, signature); + } + + function test_revert_airdropSignature_erc115_alreadyProcessed() public { + erc1155.mint(signer, 0, 100 ether); + vm.prank(signer); + 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 + }); + bytes memory signature = _signReqERC1155(req, privateKey); + + airdrop.airdropERC1155WithSignature(req, signature); + + // send it again + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropRequestAlreadyProcessed.selector)); + airdrop.airdropERC1155WithSignature(req, signature); + } + + function test_revert_airdropSignature_erc115_invalidSigner() public { + erc1155.mint(signer, 0, 100 ether); + vm.prank(signer); + 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 + }); + bytes memory signature = _signReqERC1155(req, 123); + + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropRequestInvalidSigner.selector)); + airdrop.airdropERC1155WithSignature(req, signature); + } + /*/////////////////////////////////////////////////////////////// + Benchmark: Airdrop Claim ERC1155 + //////////////////////////////////////////////////////////////*/ + + function test_state_airdropClaim_erc1155() public { + erc1155.mint(signer, 0, 100 ether); + vm.prank(signer); + erc1155.setApprovalForAll(address(airdrop), true); + + string[] memory inputs = new string[](4); + inputs[0] = "node"; + inputs[1] = "src/test/scripts/generateRootAirdrop1155.ts"; + inputs[2] = Strings.toString(uint256(0)); + inputs[3] = Strings.toString(uint256(5)); + bytes memory result = vm.ffi(inputs); + bytes32 root = abi.decode(result, (bytes32)); + + // set merkle root + vm.prank(signer); + airdrop.setMerkleRoot(address(erc1155), root, true); + + // generate proof + inputs[1] = "src/test/scripts/getProofAirdrop1155.ts"; + result = vm.ffi(inputs); + bytes32[] memory proofs = abi.decode(result, (bytes32[])); + + address receiver = address(0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd); + uint256 quantity = 5; + + airdrop.claimERC1155(address(erc1155), receiver, 0, quantity, proofs); + + assertEq(erc1155.balanceOf(receiver, 0), quantity); + assertEq(erc1155.balanceOf(signer, 0), 100 ether - quantity); + } + + function test_revert_airdropClaim_erc1155_alreadyClaimed() public { + erc1155.mint(signer, 0, 100 ether); + vm.prank(signer); + erc1155.setApprovalForAll(address(airdrop), true); + + string[] memory inputs = new string[](4); + inputs[0] = "node"; + inputs[1] = "src/test/scripts/generateRootAirdrop1155.ts"; + inputs[2] = Strings.toString(uint256(0)); + inputs[3] = Strings.toString(uint256(5)); + bytes memory result = vm.ffi(inputs); + bytes32 root = abi.decode(result, (bytes32)); + + // set merkle root + vm.prank(signer); + airdrop.setMerkleRoot(address(erc1155), root, true); + + // generate proof + inputs[1] = "src/test/scripts/getProofAirdrop1155.ts"; + result = vm.ffi(inputs); + bytes32[] memory proofs = abi.decode(result, (bytes32[])); + + address receiver = address(0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd); + uint256 quantity = 5; + + airdrop.claimERC1155(address(erc1155), receiver, 0, quantity, proofs); + + // revert when claiming again + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropAlreadyClaimed.selector)); + airdrop.claimERC1155(address(erc1155), receiver, 0, quantity, proofs); + } + + function test_revert_airdropClaim_erc1155_noMerkleRoot() public { + erc1155.mint(signer, 0, 100 ether); + vm.prank(signer); + erc1155.setApprovalForAll(address(airdrop), true); + + // generate proof + bytes32[] memory proofs; + + address receiver = address(0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd); + uint256 quantity = 5; + + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropNoMerkleRoot.selector)); + airdrop.claimERC1155(address(erc1155), receiver, 0, quantity, proofs); + } + + function test_revert_airdropClaim_erc1155_invalidProof() public { + erc1155.mint(signer, 0, 100 ether); + vm.prank(signer); + erc1155.setApprovalForAll(address(airdrop), true); + + string[] memory inputs = new string[](4); + inputs[0] = "node"; + inputs[1] = "src/test/scripts/generateRootAirdrop1155.ts"; + inputs[2] = Strings.toString(uint256(0)); + inputs[3] = Strings.toString(uint256(5)); + bytes memory result = vm.ffi(inputs); + bytes32 root = abi.decode(result, (bytes32)); + + // set merkle root + vm.prank(signer); + airdrop.setMerkleRoot(address(erc1155), root, true); + + // generate proof + inputs[1] = "src/test/scripts/getProofAirdrop1155.ts"; + result = vm.ffi(inputs); + bytes32[] memory proofs = abi.decode(result, (bytes32[])); + + address receiver = address(0x12345); + uint256 quantity = 5; + + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropInvalidProof.selector)); + airdrop.claimERC1155(address(erc1155), receiver, 0, quantity, proofs); + } +} diff --git a/src/test/benchmark/AirdropBenchmark.t.sol b/src/test/benchmark/AirdropBenchmark.t.sol new file mode 100644 index 000000000..6686a1ba0 --- /dev/null +++ b/src/test/benchmark/AirdropBenchmark.t.sol @@ -0,0 +1,625 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import { Airdrop } from "contracts/prebuilts/unaudited/airdrop/Airdrop.sol"; + +// Test imports +import { TWProxy } from "contracts/infra/TWProxy.sol"; +import "../utils/BaseTest.sol"; + +contract AirdropBenchmarkTest is BaseTest { + Airdrop internal airdrop; + + bytes32 private constant CONTENT_TYPEHASH_ERC20 = + keccak256("AirdropContentERC20(address recipient,uint256 amount)"); + bytes32 private constant REQUEST_TYPEHASH_ERC20 = + keccak256( + "AirdropRequestERC20(bytes32 uid,address tokenAddress,uint256 expirationTimestamp,AirdropContentERC20[] contents)AirdropContentERC20(address recipient,uint256 amount)" + ); + + bytes32 private constant CONTENT_TYPEHASH_ERC721 = + keccak256("AirdropContentERC721(address recipient,uint256 tokenId)"); + bytes32 private constant REQUEST_TYPEHASH_ERC721 = + keccak256( + "AirdropRequestERC721(bytes32 uid,address tokenAddress,uint256 expirationTimestamp,AirdropContentERC721[] contents)AirdropContentERC721(address recipient,uint256 tokenId)" + ); + + bytes32 private constant CONTENT_TYPEHASH_ERC1155 = + keccak256("AirdropContentERC1155(address recipient,uint256 tokenId,uint256 amount)"); + bytes32 private constant REQUEST_TYPEHASH_ERC1155 = + keccak256( + "AirdropRequestERC1155(bytes32 uid,address tokenAddress,uint256 expirationTimestamp,AirdropContentERC1155[] contents)AirdropContentERC1155(address recipient,uint256 tokenId,uint256 amount)" + ); + + bytes32 private constant NAME_HASH = keccak256(bytes("Airdrop")); + bytes32 private constant VERSION_HASH = keccak256(bytes("1")); + bytes32 private constant TYPE_HASH_EIP712 = + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + + bytes32 internal domainSeparator; + + function setUp() public override { + super.setUp(); + + address impl = address(new Airdrop()); + + airdrop = Airdrop(payable(address(new TWProxy(impl, abi.encodeCall(Airdrop.initialize, (signer)))))); + + domainSeparator = keccak256( + abi.encode(TYPE_HASH_EIP712, NAME_HASH, VERSION_HASH, block.chainid, address(airdrop)) + ); + } + + function _getContentsERC20(uint256 length) internal pure returns (Airdrop.AirdropContentERC20[] memory contents) { + contents = new Airdrop.AirdropContentERC20[](length); + for (uint256 i = 0; i < length; i++) { + contents[i].recipient = address(uint160(i + 10)); + contents[i].amount = i + 10; + } + } + + function _getContentsERC721(uint256 length) internal pure returns (Airdrop.AirdropContentERC721[] memory contents) { + contents = new Airdrop.AirdropContentERC721[](length); + for (uint256 i = 0; i < length; i++) { + contents[i].recipient = address(uint160(i + 10)); + contents[i].tokenId = i; + } + } + + function _getContentsERC1155( + uint256 length + ) internal pure returns (Airdrop.AirdropContentERC1155[] memory contents) { + contents = new Airdrop.AirdropContentERC1155[](length); + for (uint256 i = 0; i < length; i++) { + contents[i].recipient = address(uint160(i + 10)); + contents[i].tokenId = 0; + contents[i].amount = i + 10; + } + } + + function _signReqERC20( + Airdrop.AirdropRequestERC20 memory req, + uint256 privateKey + ) internal view returns (bytes memory signature) { + bytes32[] memory contentHashes = new bytes32[](req.contents.length); + for (uint i = 0; i < req.contents.length; i++) { + contentHashes[i] = keccak256( + abi.encode(CONTENT_TYPEHASH_ERC20, req.contents[i].recipient, req.contents[i].amount) + ); + } + bytes32 contentHash = keccak256(abi.encodePacked(contentHashes)); + + bytes memory dataToHash; + { + dataToHash = abi.encode( + REQUEST_TYPEHASH_ERC20, + req.uid, + req.tokenAddress, + req.expirationTimestamp, + contentHash + ); + } + + { + bytes32 _structHash = keccak256(dataToHash); + bytes32 typedDataHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator, _structHash)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, typedDataHash); + + signature = abi.encodePacked(r, s, v); + } + } + + function _signReqERC721( + Airdrop.AirdropRequestERC721 memory req, + uint256 privateKey + ) internal view returns (bytes memory signature) { + bytes32[] memory contentHashes = new bytes32[](req.contents.length); + for (uint i = 0; i < req.contents.length; i++) { + contentHashes[i] = keccak256( + abi.encode(CONTENT_TYPEHASH_ERC721, req.contents[i].recipient, req.contents[i].tokenId) + ); + } + bytes32 contentHash = keccak256(abi.encodePacked(contentHashes)); + + bytes memory dataToHash; + { + dataToHash = abi.encode( + REQUEST_TYPEHASH_ERC721, + req.uid, + req.tokenAddress, + req.expirationTimestamp, + contentHash + ); + } + + { + bytes32 _structHash = keccak256(dataToHash); + bytes32 typedDataHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator, _structHash)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, typedDataHash); + + signature = abi.encodePacked(r, s, v); + } + } + + function _signReqERC1155( + Airdrop.AirdropRequestERC1155 memory req, + uint256 privateKey + ) internal view returns (bytes memory signature) { + bytes32[] memory contentHashes = new bytes32[](req.contents.length); + for (uint i = 0; i < req.contents.length; i++) { + contentHashes[i] = keccak256( + abi.encode( + CONTENT_TYPEHASH_ERC1155, + req.contents[i].recipient, + req.contents[i].tokenId, + req.contents[i].amount + ) + ); + } + bytes32 contentHash = keccak256(abi.encodePacked(contentHashes)); + + bytes memory dataToHash; + { + dataToHash = abi.encode( + REQUEST_TYPEHASH_ERC1155, + req.uid, + req.tokenAddress, + req.expirationTimestamp, + contentHash + ); + } + + { + bytes32 _structHash = keccak256(dataToHash); + bytes32 typedDataHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator, _structHash)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, typedDataHash); + + signature = abi.encodePacked(r, s, v); + } + } + + /*/////////////////////////////////////////////////////////////// + Benchmark: Airdrop Push ERC20 + //////////////////////////////////////////////////////////////*/ + + function test_benchmark_airdropPush_erc20_10() public { + vm.pauseGasMetering(); + + erc20.mint(signer, 100 ether); + vm.prank(signer); + erc20.approve(address(airdrop), 100 ether); + + Airdrop.AirdropContentERC20[] memory contents = _getContentsERC20(10); + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC20(address(erc20), contents); + } + + function test_benchmark_airdropPush_erc20_100() public { + vm.pauseGasMetering(); + + erc20.mint(signer, 100 ether); + vm.prank(signer); + erc20.approve(address(airdrop), 100 ether); + + Airdrop.AirdropContentERC20[] memory contents = _getContentsERC20(100); + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC20(address(erc20), contents); + } + + function test_benchmark_airdropPush_erc20_1000() public { + vm.pauseGasMetering(); + + erc20.mint(signer, 100 ether); + vm.prank(signer); + erc20.approve(address(airdrop), 100 ether); + + Airdrop.AirdropContentERC20[] memory contents = _getContentsERC20(1000); + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC20(address(erc20), contents); + } + + /*/////////////////////////////////////////////////////////////// + Benchmark: Airdrop Signature ERC20 + //////////////////////////////////////////////////////////////*/ + + function test_benchmark_airdropSignature_erc20_10() public { + vm.pauseGasMetering(); + + erc20.mint(signer, 100 ether); + vm.prank(signer); + 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 + }); + bytes memory signature = _signReqERC20(req, privateKey); + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC20WithSignature(req, signature); + } + + function test_benchmark_airdropSignature_erc20_100() public { + vm.pauseGasMetering(); + + erc20.mint(signer, 100 ether); + vm.prank(signer); + erc20.approve(address(airdrop), 100 ether); + + Airdrop.AirdropContentERC20[] memory contents = _getContentsERC20(100); + Airdrop.AirdropRequestERC20 memory req = Airdrop.AirdropRequestERC20({ + uid: bytes32(uint256(1)), + tokenAddress: address(erc20), + expirationTimestamp: 1000, + contents: contents + }); + bytes memory signature = _signReqERC20(req, privateKey); + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC20WithSignature(req, signature); + } + + function test_benchmark_airdropSignature_erc20_1000() public { + vm.pauseGasMetering(); + + erc20.mint(signer, 100 ether); + vm.prank(signer); + erc20.approve(address(airdrop), 100 ether); + + Airdrop.AirdropContentERC20[] memory contents = _getContentsERC20(1000); + Airdrop.AirdropRequestERC20 memory req = Airdrop.AirdropRequestERC20({ + uid: bytes32(uint256(1)), + tokenAddress: address(erc20), + expirationTimestamp: 1000, + contents: contents + }); + bytes memory signature = _signReqERC20(req, privateKey); + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC20WithSignature(req, signature); + } + + /*/////////////////////////////////////////////////////////////// + Benchmark: Airdrop Claim ERC20 + //////////////////////////////////////////////////////////////*/ + + function test_benchmark_airdropClaim_erc20() public { + vm.pauseGasMetering(); + + erc20.mint(signer, 100 ether); + vm.prank(signer); + erc20.approve(address(airdrop), 100 ether); + + string[] memory inputs = new string[](3); + inputs[0] = "node"; + inputs[1] = "src/test/scripts/generateRootAirdrop.ts"; + inputs[2] = Strings.toString(uint256(5)); + bytes memory result = vm.ffi(inputs); + bytes32 root = abi.decode(result, (bytes32)); + + // set merkle root + vm.prank(signer); + airdrop.setMerkleRoot(address(erc20), root, true); + + // generate proof + inputs[1] = "src/test/scripts/getProofAirdrop.ts"; + result = vm.ffi(inputs); + bytes32[] memory proofs = abi.decode(result, (bytes32[])); + + address receiver = address(0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd); + uint256 quantity = 5; + + vm.prank(receiver); + vm.resumeGasMetering(); + airdrop.claimERC20(address(erc20), receiver, quantity, proofs); + } + + /*/////////////////////////////////////////////////////////////// + Benchmark: Airdrop Push ERC721 + //////////////////////////////////////////////////////////////*/ + + function test_benchmark_airdropPush_erc721_10() public { + vm.pauseGasMetering(); + + erc721.mint(signer, 100); + vm.prank(signer); + erc721.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC721[] memory contents = _getContentsERC721(10); + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC721(address(erc721), contents); + } + + function test_benchmark_airdropPush_erc721_100() public { + vm.pauseGasMetering(); + + erc721.mint(signer, 100); + vm.prank(signer); + erc721.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC721[] memory contents = _getContentsERC721(100); + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC721(address(erc721), contents); + } + + function test_benchmark_airdropPush_erc721_1000() public { + vm.pauseGasMetering(); + + erc721.mint(signer, 1000); + vm.prank(signer); + erc721.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC721[] memory contents = _getContentsERC721(1000); + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC721(address(erc721), contents); + } + + /*/////////////////////////////////////////////////////////////// + Benchmark: Airdrop Signature ERC721 + //////////////////////////////////////////////////////////////*/ + + function test_benchmark_airdropSignature_erc721_10() public { + vm.pauseGasMetering(); + + erc721.mint(signer, 1000); + vm.prank(signer); + 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 + }); + bytes memory signature = _signReqERC721(req, privateKey); + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC721WithSignature(req, signature); + } + + function test_benchmark_airdropSignature_erc721_100() public { + vm.pauseGasMetering(); + + erc721.mint(signer, 1000); + vm.prank(signer); + erc721.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC721[] memory contents = _getContentsERC721(100); + Airdrop.AirdropRequestERC721 memory req = Airdrop.AirdropRequestERC721({ + uid: bytes32(uint256(1)), + tokenAddress: address(erc721), + expirationTimestamp: 1000, + contents: contents + }); + bytes memory signature = _signReqERC721(req, privateKey); + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC721WithSignature(req, signature); + } + + function test_benchmark_airdropSignature_erc721_1000() public { + vm.pauseGasMetering(); + + erc721.mint(signer, 1000); + vm.prank(signer); + erc721.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC721[] memory contents = _getContentsERC721(1000); + Airdrop.AirdropRequestERC721 memory req = Airdrop.AirdropRequestERC721({ + uid: bytes32(uint256(1)), + tokenAddress: address(erc721), + expirationTimestamp: 1000, + contents: contents + }); + bytes memory signature = _signReqERC721(req, privateKey); + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC721WithSignature(req, signature); + } + + /*/////////////////////////////////////////////////////////////// + Benchmark: Airdrop Claim ERC721 + //////////////////////////////////////////////////////////////*/ + + function test_benchmark_airdropClaim_erc721() public { + vm.pauseGasMetering(); + + erc721.mint(signer, 100); + vm.prank(signer); + erc721.setApprovalForAll(address(airdrop), true); + + string[] memory inputs = new string[](3); + inputs[0] = "node"; + inputs[1] = "src/test/scripts/generateRootAirdrop.ts"; + inputs[2] = Strings.toString(uint256(5)); + bytes memory result = vm.ffi(inputs); + bytes32 root = abi.decode(result, (bytes32)); + + // set merkle root + vm.prank(signer); + airdrop.setMerkleRoot(address(erc721), root, true); + + // generate proof + inputs[1] = "src/test/scripts/getProofAirdrop.ts"; + result = vm.ffi(inputs); + bytes32[] memory proofs = abi.decode(result, (bytes32[])); + + address receiver = address(0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd); + uint256 tokenId = 5; + + vm.prank(receiver); + vm.resumeGasMetering(); + airdrop.claimERC721(address(erc721), receiver, tokenId, proofs); + } + + /*/////////////////////////////////////////////////////////////// + Benchmark: Airdrop Push ERC1155 + //////////////////////////////////////////////////////////////*/ + + function test_benchmark_airdropPush_erc1155_10() public { + vm.pauseGasMetering(); + + erc1155.mint(signer, 0, 100 ether); + vm.prank(signer); + erc1155.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC1155[] memory contents = _getContentsERC1155(10); + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC1155(address(erc1155), contents); + } + + function test_benchmark_airdropPush_erc1155_100() public { + vm.pauseGasMetering(); + + erc1155.mint(signer, 0, 100 ether); + vm.prank(signer); + erc1155.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC1155[] memory contents = _getContentsERC1155(100); + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC1155(address(erc1155), contents); + } + + function test_benchmark_airdropPush_erc1155_1000() public { + vm.pauseGasMetering(); + + erc1155.mint(signer, 0, 100 ether); + vm.prank(signer); + erc1155.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC1155[] memory contents = _getContentsERC1155(1000); + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC1155(address(erc1155), contents); + } + + /*/////////////////////////////////////////////////////////////// + Benchmark: Airdrop Signature ERC1155 + //////////////////////////////////////////////////////////////*/ + + function test_benchmark_airdropSignature_erc115_10() public { + vm.pauseGasMetering(); + + erc1155.mint(signer, 0, 100 ether); + vm.prank(signer); + 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 + }); + bytes memory signature = _signReqERC1155(req, privateKey); + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC1155WithSignature(req, signature); + } + + function test_benchmark_airdropSignature_erc115_100() public { + vm.pauseGasMetering(); + + erc1155.mint(signer, 0, 100 ether); + vm.prank(signer); + erc1155.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC1155[] memory contents = _getContentsERC1155(100); + Airdrop.AirdropRequestERC1155 memory req = Airdrop.AirdropRequestERC1155({ + uid: bytes32(uint256(1)), + tokenAddress: address(erc1155), + expirationTimestamp: 1000, + contents: contents + }); + bytes memory signature = _signReqERC1155(req, privateKey); + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC1155WithSignature(req, signature); + } + + function test_benchmark_airdropSignature_erc115_1000() public { + vm.pauseGasMetering(); + + erc1155.mint(signer, 0, 100 ether); + vm.prank(signer); + erc1155.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC1155[] memory contents = _getContentsERC1155(1000); + Airdrop.AirdropRequestERC1155 memory req = Airdrop.AirdropRequestERC1155({ + uid: bytes32(uint256(1)), + tokenAddress: address(erc1155), + expirationTimestamp: 1000, + contents: contents + }); + bytes memory signature = _signReqERC1155(req, privateKey); + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC1155WithSignature(req, signature); + } + + /*/////////////////////////////////////////////////////////////// + Benchmark: Airdrop Claim ERC1155 + //////////////////////////////////////////////////////////////*/ + + function test_benchmark_airdropClaim_erc1155() public { + vm.pauseGasMetering(); + + erc1155.mint(signer, 0, 100 ether); + vm.prank(signer); + erc1155.setApprovalForAll(address(airdrop), true); + + string[] memory inputs = new string[](4); + inputs[0] = "node"; + inputs[1] = "src/test/scripts/generateRootAirdrop1155.ts"; + inputs[2] = Strings.toString(uint256(0)); + inputs[3] = Strings.toString(uint256(5)); + bytes memory result = vm.ffi(inputs); + bytes32 root = abi.decode(result, (bytes32)); + + // set merkle root + vm.prank(signer); + airdrop.setMerkleRoot(address(erc1155), root, true); + + // generate proof + inputs[1] = "src/test/scripts/getProofAirdrop1155.ts"; + result = vm.ffi(inputs); + bytes32[] memory proofs = abi.decode(result, (bytes32[])); + + address receiver = address(0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd); + uint256 quantity = 5; + + vm.prank(receiver); + vm.resumeGasMetering(); + airdrop.claimERC1155(address(erc1155), receiver, 0, quantity, proofs); + } +} diff --git a/src/test/scripts/generateRootAirdrop1155.ts b/src/test/scripts/generateRootAirdrop1155.ts new file mode 100644 index 000000000..d76990fad --- /dev/null +++ b/src/test/scripts/generateRootAirdrop1155.ts @@ -0,0 +1,27 @@ +const { MerkleTree } = require("@thirdweb-dev/merkletree"); + +const keccak256 = require("keccak256"); +const { ethers } = require("ethers"); + +const process = require("process"); + +const members = [ + "0x9999999999999999999999999999999999999999", + "0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd", + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", +]; + +let tokenId = process.argv[2]; +let quantity = process.argv[3]; + +const hashedLeafs = members.map(l => + ethers.utils.solidityKeccak256(["address", "uint256", "uint256"], [l, tokenId, quantity]), +); + +const tree = new MerkleTree(hashedLeafs, keccak256, { + sort: true, + sortLeaves: true, + sortPairs: true, +}); + +process.stdout.write(ethers.utils.defaultAbiCoder.encode(["bytes32"], [tree.getHexRoot()])); diff --git a/src/test/scripts/getProofAirdrop1155.ts b/src/test/scripts/getProofAirdrop1155.ts new file mode 100644 index 000000000..6701fc479 --- /dev/null +++ b/src/test/scripts/getProofAirdrop1155.ts @@ -0,0 +1,31 @@ +const { MerkleTree } = require("@thirdweb-dev/merkletree"); + +const keccak256 = require("keccak256"); +const { ethers } = require("ethers"); + +const process = require("process"); + +const members = [ + "0x9999999999999999999999999999999999999999", + "0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd", + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", +]; + +let tokenId = process.argv[2]; +let quantity = process.argv[3]; + +const hashedLeafs = members.map(l => + ethers.utils.solidityKeccak256(["address", "uint256", "uint256"], [l, tokenId, quantity]), +); + +const tree = new MerkleTree(hashedLeafs, keccak256, { + sort: true, + sortLeaves: true, + sortPairs: true, +}); + +const expectedProof = tree.getHexProof( + ethers.utils.solidityKeccak256(["address", "uint256", "uint256"], [members[1], tokenId, quantity]), +); + +process.stdout.write(ethers.utils.defaultAbiCoder.encode(["bytes32[]"], [expectedProof]));