diff --git a/.gitmodules b/.gitmodules index 7872f5e50..c57f0eaa8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,30 @@ [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 +[submodule "lib/seaport"] + path = lib/seaport + url = https://github.com/ProjectOpenSea/seaport +[submodule "lib/murky"] + path = lib/murky + url = https://github.com/dmfxyz/murky +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/openzeppelin/openzeppelin-contracts +[submodule "lib/solmate"] + path = lib/solmate + url = https://github.com/transmissions11/solmate +[submodule "lib/solarray"] + path = lib/solarray + url = https://github.com/emo-eth/solarray +[submodule "lib/seaport-types"] + path = lib/seaport-types + url = https://github.com/projectopensea/seaport-types +[submodule "lib/seaport-core"] + path = lib/seaport-core + url = https://github.com/projectopensea/seaport-core +[submodule "lib/seaport-sol"] + path = lib/seaport-sol + url = https://github.com/projectopensea/seaport-sol diff --git a/contracts/extension/SeaportEIP1271.sol b/contracts/extension/SeaportEIP1271.sol new file mode 100644 index 000000000..711c847ce --- /dev/null +++ b/contracts/extension/SeaportEIP1271.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.11; + +import { ECDSA } from "solady/utils/ECDSA.sol"; +import { SeaportOrderParser } from "./SeaportOrderParser.sol"; +import { OrderParameters } from "seaport-types/src/lib/ConsiderationStructs.sol"; + +abstract contract SeaportEIP1271 is SeaportOrderParser { + using ECDSA for bytes32; + + bytes32 private constant ACCOUNT_MESSAGE_TYPEHASH = keccak256("AccountMessage(bytes message)"); + bytes32 private constant EIP712_TYPEHASH = + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + bytes32 private immutable HASHED_NAME; + bytes32 private immutable HASHED_VERSION; + + /// @notice The function selector of EIP1271.isValidSignature to be returned on sucessful signature verification. + bytes4 public constant MAGICVALUE = 0x1626ba7e; + + constructor(string memory _name, string memory _version) { + HASHED_NAME = keccak256(bytes(_name)); + HASHED_VERSION = keccak256(bytes(_version)); + } + + /// @notice See EIP-1271: https://eips.ethereum.org/EIPS/eip-1271 + function isValidSignature( + bytes32 _message, + bytes memory _signature + ) public view virtual returns (bytes4 magicValue) { + bytes32 targetDigest; + bytes memory targetSig; + + // Handle OpenSea bulk order signatures that are >65 bytes in length. + if (_signature.length > 65) { + // Decode packed signature and order parameters. + (bytes memory extractedPackedSig, OrderParameters memory orderParameters, uint256 counter) = abi.decode( + _signature, + (bytes, OrderParameters, uint256) + ); + + // Verify that the original digest matches the digest built with order parameters. + bytes32 domainSeparator = _buildSeaportDomainSeparator(msg.sender); + bytes32 orderHash = _deriveOrderHash(orderParameters, counter); + + require( + _deriveEIP712Digest(domainSeparator, orderHash) == _message, + "Seaport: order hash does not match the provided message." + ); + + // Build bulk signature digest + targetDigest = _deriveEIP712Digest(domainSeparator, _computeBulkOrderProof(extractedPackedSig, orderHash)); + + // Extract the signature, which is the first 65 bytes + targetSig = new bytes(65); + for (uint256 i = 0; i < 65; i++) { + targetSig[i] = extractedPackedSig[i]; + } + } else { + targetDigest = getMessageHash(_message); + targetSig = _signature; + } + + address signer = targetDigest.recover(targetSig); + + if (_isAuthorizedSigner(signer)) { + magicValue = MAGICVALUE; + } + } + + /** + * @notice Returns the hash of message that should be signed for EIP1271 verification. + * @param _hash The message hash pre replay protection. + * @return messageHash The digest (with replay protection) to sign for EIP-1271 verification. + */ + function getMessageHash(bytes32 _hash) public view returns (bytes32) { + bytes32 messageHash = keccak256(abi.encode(_hash)); + bytes32 typedDataHash = keccak256(abi.encode(ACCOUNT_MESSAGE_TYPEHASH, messageHash)); + return keccak256(abi.encodePacked("\x19\x01", _accountDomainSeparator(), typedDataHash)); + } + + /// @notice Returns the EIP712 domain separator for the contract. + function _accountDomainSeparator() private view returns (bytes32) { + return keccak256(abi.encode(EIP712_TYPEHASH, HASHED_NAME, HASHED_VERSION, block.chainid, address(this))); + } + + /// @notice Returns whether a given signer is an authorized signer for the contract. + function _isAuthorizedSigner(address _signer) internal view virtual returns (bool); +} diff --git a/contracts/extension/SeaportOrderParser.sol b/contracts/extension/SeaportOrderParser.sol new file mode 100644 index 000000000..f76e3e337 --- /dev/null +++ b/contracts/extension/SeaportOrderParser.sol @@ -0,0 +1,550 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.11; + +/* solhint-disable */ + +import { OrderParameters } from "seaport-types/src/lib/ConsiderationStructs.sol"; +import { EIP_712_PREFIX, EIP712_ConsiderationItem_size, EIP712_DigestPayload_size, EIP712_DomainSeparator_offset, EIP712_OfferItem_size, EIP712_Order_size, EIP712_OrderHash_offset, OneWord, OneWordShift, OrderParameters_consideration_head_offset, OrderParameters_counter_offset, OrderParameters_offer_head_offset, TwoWords, BulkOrderProof_keyShift, BulkOrderProof_keySize, BulkOrder_Typehash_Height_One, BulkOrder_Typehash_Height_Two, BulkOrder_Typehash_Height_Three, BulkOrder_Typehash_Height_Four, BulkOrder_Typehash_Height_Five, BulkOrder_Typehash_Height_Six, BulkOrder_Typehash_Height_Seven, BulkOrder_Typehash_Height_Eight, BulkOrder_Typehash_Height_Nine, BulkOrder_Typehash_Height_Ten, BulkOrder_Typehash_Height_Eleven, BulkOrder_Typehash_Height_Twelve, BulkOrder_Typehash_Height_Thirteen, BulkOrder_Typehash_Height_Fourteen, BulkOrder_Typehash_Height_Fifteen, BulkOrder_Typehash_Height_Sixteen, BulkOrder_Typehash_Height_Seventeen, BulkOrder_Typehash_Height_Eighteen, BulkOrder_Typehash_Height_Nineteen, BulkOrder_Typehash_Height_Twenty, BulkOrder_Typehash_Height_TwentyOne, BulkOrder_Typehash_Height_TwentyTwo, BulkOrder_Typehash_Height_TwentyThree, BulkOrder_Typehash_Height_TwentyFour, FreeMemoryPointerSlot } from "seaport-types/src/lib/ConsiderationConstants.sol"; + +contract SeaportOrderParser { + uint256 constant ECDSA_MAXLENGTH = 65; + + bytes32 private immutable _NAME_HASH; + bytes32 private immutable _VERSION_HASH; + bytes32 private immutable _EIP_712_DOMAIN_TYPEHASH; + bytes32 private immutable _OFFER_ITEM_TYPEHASH; + bytes32 private immutable _CONSIDERATION_ITEM_TYPEHASH; + bytes32 private immutable _ORDER_TYPEHASH; + + constructor() { + ( + _NAME_HASH, + _VERSION_HASH, + _EIP_712_DOMAIN_TYPEHASH, + _OFFER_ITEM_TYPEHASH, + _CONSIDERATION_ITEM_TYPEHASH, + _ORDER_TYPEHASH + ) = _deriveTypehashes(); + } + + function _nameString() internal pure virtual returns (string memory) { + // Return the name of the contract. + return "Seaport"; + } + + function _buildSeaportDomainSeparator(address _domainAddress) internal view returns (bytes32) { + return + keccak256(abi.encode(_EIP_712_DOMAIN_TYPEHASH, _NAME_HASH, _VERSION_HASH, block.chainid, _domainAddress)); + } + + function _deriveOrderHash( + OrderParameters memory orderParameters, + uint256 counter + ) internal view returns (bytes32 orderHash) { + // Get length of original consideration array and place it on the stack. + uint256 originalConsiderationLength = (orderParameters.totalOriginalConsiderationItems); + + /* + * Memory layout for an array of structs (dynamic or not) is similar + * to ABI encoding of dynamic types, with a head segment followed by + * a data segment. The main difference is that the head of an element + * is a memory pointer rather than an offset. + */ + + // Declare a variable for the derived hash of the offer array. + bytes32 offerHash; + + // Read offer item EIP-712 typehash from runtime code & place on stack. + bytes32 typeHash = _OFFER_ITEM_TYPEHASH; + + // Utilize assembly so that memory regions can be reused across hashes. + assembly { + // Retrieve the free memory pointer and place on the stack. + let hashArrPtr := mload(FreeMemoryPointerSlot) + + // Get the pointer to the offers array. + let offerArrPtr := mload(add(orderParameters, OrderParameters_offer_head_offset)) + + // Load the length. + let offerLength := mload(offerArrPtr) + + // Set the pointer to the first offer's head. + offerArrPtr := add(offerArrPtr, OneWord) + + // Iterate over the offer items. + for { + let i := 0 + } lt(i, offerLength) { + i := add(i, 1) + } { + // Read the pointer to the offer data and subtract one word + // to get typeHash pointer. + let ptr := sub(mload(offerArrPtr), OneWord) + + // Read the current value before the offer data. + let value := mload(ptr) + + // Write the type hash to the previous word. + mstore(ptr, typeHash) + + // Take the EIP712 hash and store it in the hash array. + mstore(hashArrPtr, keccak256(ptr, EIP712_OfferItem_size)) + + // Restore the previous word. + mstore(ptr, value) + + // Increment the array pointers by one word. + offerArrPtr := add(offerArrPtr, OneWord) + hashArrPtr := add(hashArrPtr, OneWord) + } + + // Derive the offer hash using the hashes of each item. + offerHash := keccak256(mload(FreeMemoryPointerSlot), shl(OneWordShift, offerLength)) + } + + // Declare a variable for the derived hash of the consideration array. + bytes32 considerationHash; + + // Read consideration item typehash from runtime code & place on stack. + typeHash = _CONSIDERATION_ITEM_TYPEHASH; + + // Utilize assembly so that memory regions can be reused across hashes. + assembly { + // Retrieve the free memory pointer and place on the stack. + let hashArrPtr := mload(FreeMemoryPointerSlot) + + // Get the pointer to the consideration array. + let considerationArrPtr := add( + mload(add(orderParameters, OrderParameters_consideration_head_offset)), + OneWord + ) + + // Iterate over the consideration items (not including tips). + for { + let i := 0 + } lt(i, originalConsiderationLength) { + i := add(i, 1) + } { + // Read the pointer to the consideration data and subtract one + // word to get typeHash pointer. + let ptr := sub(mload(considerationArrPtr), OneWord) + + // Read the current value before the consideration data. + let value := mload(ptr) + + // Write the type hash to the previous word. + mstore(ptr, typeHash) + + // Take the EIP712 hash and store it in the hash array. + mstore(hashArrPtr, keccak256(ptr, EIP712_ConsiderationItem_size)) + + // Restore the previous word. + mstore(ptr, value) + + // Increment the array pointers by one word. + considerationArrPtr := add(considerationArrPtr, OneWord) + hashArrPtr := add(hashArrPtr, OneWord) + } + + // Derive the consideration hash using the hashes of each item. + considerationHash := keccak256(mload(FreeMemoryPointerSlot), shl(OneWordShift, originalConsiderationLength)) + } + + // Read order item EIP-712 typehash from runtime code & place on stack. + typeHash = _ORDER_TYPEHASH; + + // Utilize assembly to access derived hashes & other arguments directly. + assembly { + // Retrieve pointer to the region located just behind parameters. + let typeHashPtr := sub(orderParameters, OneWord) + + // Store the value at that pointer location to restore later. + let previousValue := mload(typeHashPtr) + + // Store the order item EIP-712 typehash at the typehash location. + mstore(typeHashPtr, typeHash) + + // Retrieve the pointer for the offer array head. + let offerHeadPtr := add(orderParameters, OrderParameters_offer_head_offset) + + // Retrieve the data pointer referenced by the offer head. + let offerDataPtr := mload(offerHeadPtr) + + // Store the offer hash at the retrieved memory location. + mstore(offerHeadPtr, offerHash) + + // Retrieve the pointer for the consideration array head. + let considerationHeadPtr := add(orderParameters, OrderParameters_consideration_head_offset) + + // Retrieve the data pointer referenced by the consideration head. + let considerationDataPtr := mload(considerationHeadPtr) + + // Store the consideration hash at the retrieved memory location. + mstore(considerationHeadPtr, considerationHash) + + // Retrieve the pointer for the counter. + let counterPtr := add(orderParameters, OrderParameters_counter_offset) + + // Store the counter at the retrieved memory location. + mstore(counterPtr, counter) + + // Derive the order hash using the full range of order parameters. + orderHash := keccak256(typeHashPtr, EIP712_Order_size) + + // Restore the value previously held at typehash pointer location. + mstore(typeHashPtr, previousValue) + + // Restore offer data pointer at the offer head pointer location. + mstore(offerHeadPtr, offerDataPtr) + + // Restore consideration data pointer at the consideration head ptr. + mstore(considerationHeadPtr, considerationDataPtr) + + // Restore consideration item length at the counter pointer. + mstore(counterPtr, originalConsiderationLength) + } + } + + function _deriveTypehashes() + internal + pure + returns ( + bytes32 nameHash, + bytes32 versionHash, + bytes32 eip712DomainTypehash, + bytes32 offerItemTypehash, + bytes32 considerationItemTypehash, + bytes32 orderTypehash + ) + { + // Derive hash of the name of the contract. + nameHash = keccak256(bytes(_nameString())); + + // Derive hash of the version string of the contract. + versionHash = keccak256(bytes("1.5")); + + // Construct the OfferItem type string. + bytes memory offerItemTypeString = bytes( + "OfferItem(" + "uint8 itemType," + "address token," + "uint256 identifierOrCriteria," + "uint256 startAmount," + "uint256 endAmount" + ")" + ); + + // Construct the ConsiderationItem type string. + bytes memory considerationItemTypeString = bytes( + "ConsiderationItem(" + "uint8 itemType," + "address token," + "uint256 identifierOrCriteria," + "uint256 startAmount," + "uint256 endAmount," + "address recipient" + ")" + ); + + // Construct the OrderComponents type string, not including the above. + bytes memory orderComponentsPartialTypeString = bytes( + "OrderComponents(" + "address offerer," + "address zone," + "OfferItem[] offer," + "ConsiderationItem[] consideration," + "uint8 orderType," + "uint256 startTime," + "uint256 endTime," + "bytes32 zoneHash," + "uint256 salt," + "bytes32 conduitKey," + "uint256 counter" + ")" + ); + + // Construct the primary EIP-712 domain type string. + eip712DomainTypehash = keccak256( + bytes( + "EIP712Domain(" + "string name," + "string version," + "uint256 chainId," + "address verifyingContract" + ")" + ) + ); + + // Derive the OfferItem type hash using the corresponding type string. + offerItemTypehash = keccak256(offerItemTypeString); + + // Derive ConsiderationItem type hash using corresponding type string. + considerationItemTypehash = keccak256(considerationItemTypeString); + + bytes memory orderTypeString = bytes.concat( + orderComponentsPartialTypeString, + considerationItemTypeString, + offerItemTypeString + ); + + // Derive OrderItem type hash via combination of relevant type strings. + orderTypehash = keccak256(orderTypeString); + } + + function _computeBulkOrderProof( + bytes memory proofAndSignature, + bytes32 leaf + ) internal pure returns (bytes32 bulkOrderHash) { + // Declare arguments for the root hash and the height of the proof. + bytes32 root; + uint256 height; + + // Utilize assembly to efficiently derive the root hash using the proof. + assembly { + // Retrieve the length of the proof, key, and signature combined. + let fullLength := mload(proofAndSignature) + + // If proofAndSignature has odd length, it is a compact signature + // with 64 bytes. + let signatureLength := sub(ECDSA_MAXLENGTH, and(fullLength, 1)) + + // Derive height (or depth of tree) with signature and proof length. + height := shr(OneWordShift, sub(fullLength, signatureLength)) + + // Update the length in memory to only include the signature. + mstore(proofAndSignature, signatureLength) + + // Derive the pointer for the key using the signature length. + let keyPtr := add(proofAndSignature, add(OneWord, signatureLength)) + + // Retrieve the three-byte key using the derived pointer. + let key := shr(BulkOrderProof_keyShift, mload(keyPtr)) + + /// Retrieve pointer to first proof element by applying a constant + // for the key size to the derived key pointer. + let proof := add(keyPtr, BulkOrderProof_keySize) + + // Compute level 1. + let scratchPtr1 := shl(OneWordShift, and(key, 1)) + mstore(scratchPtr1, leaf) + mstore(xor(scratchPtr1, OneWord), mload(proof)) + + // Compute remaining proofs. + for { + let i := 1 + } lt(i, height) { + i := add(i, 1) + } { + proof := add(proof, OneWord) + let scratchPtr := shl(OneWordShift, and(shr(i, key), 1)) + mstore(scratchPtr, keccak256(0, TwoWords)) + mstore(xor(scratchPtr, OneWord), mload(proof)) + } + + // Compute root hash. + root := keccak256(0, TwoWords) + } + + // Retrieve appropriate typehash constant based on height. + bytes32 rootTypeHash = _lookupBulkOrderTypehash(height); + + // Use the typehash and the root hash to derive final bulk order hash. + assembly { + mstore(0, rootTypeHash) + mstore(OneWord, root) + bulkOrderHash := keccak256(0, TwoWords) + } + } + + function _lookupBulkOrderTypehash(uint256 _treeHeight) internal pure returns (bytes32 _typeHash) { + // Utilize assembly to efficiently retrieve correct bulk order typehash. + assembly { + // Use a Yul function to enable use of the `leave` keyword + // to stop searching once the appropriate type hash is found. + function lookupTypeHash(treeHeight) -> typeHash { + // Handle tree heights one through eight. + if lt(treeHeight, 9) { + // Handle tree heights one through four. + if lt(treeHeight, 5) { + // Handle tree heights one and two. + if lt(treeHeight, 3) { + // Utilize branchless logic to determine typehash. + typeHash := ternary( + eq(treeHeight, 1), + BulkOrder_Typehash_Height_One, + BulkOrder_Typehash_Height_Two + ) + + // Exit the function once typehash has been located. + leave + } + + // Handle height three and four via branchless logic. + typeHash := ternary( + eq(treeHeight, 3), + BulkOrder_Typehash_Height_Three, + BulkOrder_Typehash_Height_Four + ) + + // Exit the function once typehash has been located. + leave + } + + // Handle tree height five and six. + if lt(treeHeight, 7) { + // Utilize branchless logic to determine typehash. + typeHash := ternary( + eq(treeHeight, 5), + BulkOrder_Typehash_Height_Five, + BulkOrder_Typehash_Height_Six + ) + + // Exit the function once typehash has been located. + leave + } + + // Handle height seven and eight via branchless logic. + typeHash := ternary( + eq(treeHeight, 7), + BulkOrder_Typehash_Height_Seven, + BulkOrder_Typehash_Height_Eight + ) + + // Exit the function once typehash has been located. + leave + } + + // Handle tree height nine through sixteen. + if lt(treeHeight, 17) { + // Handle tree height nine through twelve. + if lt(treeHeight, 13) { + // Handle tree height nine and ten. + if lt(treeHeight, 11) { + // Utilize branchless logic to determine typehash. + typeHash := ternary( + eq(treeHeight, 9), + BulkOrder_Typehash_Height_Nine, + BulkOrder_Typehash_Height_Ten + ) + + // Exit the function once typehash has been located. + leave + } + + // Handle height eleven and twelve via branchless logic. + typeHash := ternary( + eq(treeHeight, 11), + BulkOrder_Typehash_Height_Eleven, + BulkOrder_Typehash_Height_Twelve + ) + + // Exit the function once typehash has been located. + leave + } + + // Handle tree height thirteen and fourteen. + if lt(treeHeight, 15) { + // Utilize branchless logic to determine typehash. + typeHash := ternary( + eq(treeHeight, 13), + BulkOrder_Typehash_Height_Thirteen, + BulkOrder_Typehash_Height_Fourteen + ) + + // Exit the function once typehash has been located. + leave + } + // Handle height fifteen and sixteen via branchless logic. + typeHash := ternary( + eq(treeHeight, 15), + BulkOrder_Typehash_Height_Fifteen, + BulkOrder_Typehash_Height_Sixteen + ) + + // Exit the function once typehash has been located. + leave + } + + // Handle tree height seventeen through twenty. + if lt(treeHeight, 21) { + // Handle tree height seventeen and eighteen. + if lt(treeHeight, 19) { + // Utilize branchless logic to determine typehash. + typeHash := ternary( + eq(treeHeight, 17), + BulkOrder_Typehash_Height_Seventeen, + BulkOrder_Typehash_Height_Eighteen + ) + + // Exit the function once typehash has been located. + leave + } + + // Handle height nineteen and twenty via branchless logic. + typeHash := ternary( + eq(treeHeight, 19), + BulkOrder_Typehash_Height_Nineteen, + BulkOrder_Typehash_Height_Twenty + ) + + // Exit the function once typehash has been located. + leave + } + + // Handle tree height twenty-one and twenty-two. + if lt(treeHeight, 23) { + // Utilize branchless logic to determine typehash. + typeHash := ternary( + eq(treeHeight, 21), + BulkOrder_Typehash_Height_TwentyOne, + BulkOrder_Typehash_Height_TwentyTwo + ) + + // Exit the function once typehash has been located. + leave + } + + // Handle height twenty-three & twenty-four w/ branchless logic. + typeHash := ternary( + eq(treeHeight, 23), + BulkOrder_Typehash_Height_TwentyThree, + BulkOrder_Typehash_Height_TwentyFour + ) + + // Exit the function once typehash has been located. + leave + } + + // Implement ternary conditional using branchless logic. + function ternary(cond, ifTrue, ifFalse) -> c { + c := xor(ifFalse, mul(cond, xor(ifFalse, ifTrue))) + } + + // Look up the typehash using the supplied tree height. + _typeHash := lookupTypeHash(_treeHeight) + } + } + + function _deriveEIP712Digest(bytes32 domainSeparator, bytes32 orderHash) internal pure returns (bytes32 value) { + // Leverage scratch space to perform an efficient hash. + assembly { + // Place the EIP-712 prefix at the start of scratch space. + mstore(0, EIP_712_PREFIX) + + // Place the domain separator in the next region of scratch space. + mstore(EIP712_DomainSeparator_offset, domainSeparator) + + // Place the order hash in scratch space, spilling into the first + // two bytes of the free memory pointer — this should never be set + // as memory cannot be expanded to that size, and will be zeroed out + // after the hash is performed. + mstore(EIP712_OrderHash_offset, orderHash) + + // Hash the relevant region (65 bytes). + value := keccak256(0, EIP712_DigestPayload_size) + + // Clear out the dirtied bits in the memory pointer. + mstore(EIP712_OrderHash_offset, 0) + } + } +} diff --git a/contracts/package.json b/contracts/package.json index d318f03c1..8425ae95e 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,7 +1,7 @@ { "name": "@thirdweb-dev/contracts", "description": "Collection of smart contracts deployable via the thirdweb SDK, dashboard and CLI", - "version": "3.12.0", + "version": "3.13.0", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/contracts/prebuilts/account/utils/AccountSeaportBulkSigSupport.sol b/contracts/prebuilts/account/utils/AccountSeaportBulkSigSupport.sol new file mode 100644 index 000000000..e5a1dcb7a --- /dev/null +++ b/contracts/prebuilts/account/utils/AccountSeaportBulkSigSupport.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.11; + +import { SeaportEIP1271 } from "../../../extension/SeaportEIP1271.sol"; +import { AccountPermissions, AccountPermissionsStorage } from "../../../extension/upgradeable/AccountPermissions.sol"; +import { EnumerableSet } from "../../../external-deps/openzeppelin/utils/structs/EnumerableSet.sol"; + +contract AccountSeaportBulkSigSupport is SeaportEIP1271 { + using EnumerableSet for EnumerableSet.AddressSet; + + constructor() SeaportEIP1271("Account", "1") {} + + /// @notice Returns whether a given signer is an authorized signer for the contract. + function _isAuthorizedSigner(address _signer) internal view virtual override returns (bool authorized) { + // is signer an admin? + if (AccountPermissionsStorage.data().isAdmin[_signer]) { + authorized = true; + } + + address caller = msg.sender; + EnumerableSet.AddressSet storage approvedTargets = AccountPermissionsStorage.data().approvedTargets[_signer]; + + require( + approvedTargets.contains(caller) || (approvedTargets.length() == 1 && approvedTargets.at(0) == address(0)), + "Account: caller not approved target." + ); + + // is signer an active signer of account? + AccountPermissions.SignerPermissionsStatic memory permissions = AccountPermissionsStorage + .data() + .signerPermissions[_signer]; + if ( + permissions.startTimestamp <= block.timestamp && + block.timestamp < permissions.endTimestamp && + AccountPermissionsStorage.data().approvedTargets[_signer].length() > 0 + ) { + authorized = true; + } + } +} diff --git a/contracts/prebuilts/unaudited/airdrop/Airdrop.sol b/contracts/prebuilts/unaudited/airdrop/Airdrop.sol new file mode 100644 index 000000000..e7c7d5a76 --- /dev/null +++ b/contracts/prebuilts/unaudited/airdrop/Airdrop.sol @@ -0,0 +1,609 @@ +// 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 "@solady/src/utils/SignatureCheckerLib.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 { + /*/////////////////////////////////////////////////////////////// + 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) public 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 AirdropNoMerkleRoot(); + error AirdropValueMismatch(); + error AirdropRequestExpired(uint256 expirationTimestamp); + error AirdropRequestAlreadyProcessed(); + error AirdropRequestInvalidSigner(); + + /*/////////////////////////////////////////////////////////////// + Events + //////////////////////////////////////////////////////////////*/ + + event Airdrop(address token); + event AirdropWithSignature(address token); + event AirdropClaimed(address token, address receiver); + + /*/////////////////////////////////////////////////////////////// + Constructor + //////////////////////////////////////////////////////////////*/ + + constructor() { + _disableInitializers(); + } + + function initialize(address _defaultAdmin) external initializer { + _setupOwner(_defaultAdmin); + } + + /*/////////////////////////////////////////////////////////////// + Airdrop Push + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Lets contract-owner send native token (eth) to a list of addresses. + * @dev Owner should send total airdrop amount as msg.value. + * Can only be called by contract owner. + * + * @param _contents List containing recipients and amounts to airdrop + */ + function airdropNativeToken(AirdropContentERC20[] calldata _contents) external payable onlyOwner { + uint256 len = _contents.length; + uint256 nativeTokenAmount; + + for (uint256 i = 0; i < len; i++) { + nativeTokenAmount += _contents[i].amount; + + SafeTransferLib.safeTransferETH(_contents[i].recipient, _contents[i].amount); + } + + if (nativeTokenAmount != msg.value) { + revert AirdropValueMismatch(); + } + + emit Airdrop(NATIVE_TOKEN_ADDRESS); + } + + /** + * @notice Lets contract owner send ERC20 tokens to a list of addresses. + * @dev The token-owner should approve total airdrop amount to this contract. + * Can only be called by contract owner. + * + * @param _tokenAddress Address of the ERC20 token being airdropped + * @param _contents List containing recipients and amounts to airdrop + */ + function airdropERC20(address _tokenAddress, AirdropContentERC20[] calldata _contents) external onlyOwner { + uint256 len = _contents.length; + + for (uint256 i = 0; i < len; i++) { + SafeTransferLib.safeTransferFrom(_tokenAddress, msg.sender, _contents[i].recipient, _contents[i].amount); + } + + emit Airdrop(_tokenAddress); + } + + /** + * @notice Lets contract owner send ERC721 tokens to a list of addresses. + * @dev The token-owner should approve airdrop tokenIds to this contract. + * Can only be called by contract owner. + * + * @param _tokenAddress Address of the ERC721 token being airdropped + * @param _contents List containing recipients and tokenIds to airdrop + */ + function airdropERC721(address _tokenAddress, AirdropContentERC721[] calldata _contents) external onlyOwner { + uint256 len = _contents.length; + + for (uint256 i = 0; i < len; i++) { + IERC721(_tokenAddress).safeTransferFrom(msg.sender, _contents[i].recipient, _contents[i].tokenId); + } + + emit Airdrop(_tokenAddress); + } + + /** + * @notice Lets contract owner send ERC1155 tokens to a list of addresses. + * @dev The token-owner should approve airdrop tokenIds and amounts to this contract. + * Can only be called by contract owner. + * + * @param _tokenAddress Address of the ERC1155 token being airdropped + * @param _contents List containing recipients, tokenIds, and amounts to airdrop + */ + function airdropERC1155(address _tokenAddress, AirdropContentERC1155[] calldata _contents) external onlyOwner { + uint256 len = _contents.length; + + 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 + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Lets contract owner send ERC20 tokens to a list of addresses with EIP-712 signature. + * @dev The token-owner should approve airdrop amounts to this contract. + * Signer should be the contract owner. + * + * @param req Struct containing airdrop contents, uid, and expiration timestamp + * @param signature EIP-712 signature to perform the airdrop + */ + function airdropERC20WithSignature(AirdropRequestERC20 calldata req, bytes calldata signature) external { + // verify expiration timestamp + if (req.expirationTimestamp < block.timestamp) { + 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 AirdropWithSignature(req.tokenAddress); + } + + /** + * @notice Lets contract owner send ERC721 tokens to a list of addresses with EIP-712 signature. + * @dev The token-owner should approve airdrop tokenIds to this contract. + * Signer should be the contract owner. + * + * @param req Struct containing airdrop contents, uid, and expiration timestamp + * @param signature EIP-712 signature to perform the airdrop + */ + function airdropERC721WithSignature(AirdropRequestERC721 calldata req, bytes calldata signature) external { + // verify expiration timestamp + if (req.expirationTimestamp < block.timestamp) { + 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 AirdropWithSignature(req.tokenAddress); + } + + /** + * @notice Lets contract owner send ERC1155 tokens to a list of addresses with EIP-712 signature. + * @dev The token-owner should approve airdrop tokenIds and amounts to this contract. + * Signer should be the contract owner. + * + * @param req Struct containing airdrop contents, uid, and expiration timestamp + * @param signature EIP-712 signature to perform the airdrop + */ + function airdropERC1155WithSignature(AirdropRequestERC1155 calldata req, bytes calldata signature) external { + // verify expiration timestamp + if (req.expirationTimestamp < block.timestamp) { + 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 AirdropWithSignature(req.tokenAddress); + } + + /*/////////////////////////////////////////////////////////////// + Airdrop Claimable + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Lets allowlisted addresses claim ERC20 airdrop tokens. + * @dev The token-owner should approve total airdrop amount to this contract, + * and set merkle root of allowlisted address for this airdrop. + * + * @param _token Address of ERC20 airdrop token + * @param _receiver Allowlisted address for which the token is being claimed + * @param _quantity Allowlisted quantity of tokens to claim + * @param _proofs Merkle proofs for allowlist verification + */ + function claimERC20(address _token, address _receiver, uint256 _quantity, bytes32[] calldata _proofs) external { + bytes32 claimHash = _getClaimHashERC20(_receiver, _token); + uint256 conditionId = tokenConditionId[_token]; + + if (claimed[conditionId][claimHash]) { + revert AirdropAlreadyClaimed(); + } + + bytes32 _tokenMerkleRoot = tokenMerkleRoot[_token]; + if (_tokenMerkleRoot == bytes32(0)) { + revert AirdropNoMerkleRoot(); + } + + bool valid = MerkleProofLib.verifyCalldata( + _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); + } + + /** + * @notice Lets allowlisted addresses claim ERC721 airdrop tokens. + * @dev The token-owner should approve airdrop tokenIds to this contract, + * and set merkle root of allowlisted address for this airdrop. + * + * @param _token Address of ERC721 airdrop token + * @param _receiver Allowlisted address for which the token is being claimed + * @param _tokenId Allowlisted tokenId to claim + * @param _proofs Merkle proofs for allowlist verification + */ + function claimERC721(address _token, address _receiver, uint256 _tokenId, bytes32[] calldata _proofs) external { + bytes32 claimHash = _getClaimHashERC721(_receiver, _token, _tokenId); + uint256 conditionId = tokenConditionId[_token]; + + if (claimed[conditionId][claimHash]) { + revert AirdropAlreadyClaimed(); + } + + bytes32 _tokenMerkleRoot = tokenMerkleRoot[_token]; + if (_tokenMerkleRoot == bytes32(0)) { + revert AirdropNoMerkleRoot(); + } + + bool valid = MerkleProofLib.verifyCalldata( + _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); + } + + /** + * @notice Lets allowlisted addresses claim ERC1155 airdrop tokens. + * @dev The token-owner should approve tokenIds and total airdrop amounts to this contract, + * and set merkle root of allowlisted address for this airdrop. + * + * @param _token Address of ERC1155 airdrop token + * @param _receiver Allowlisted address for which the token is being claimed + * @param _tokenId Allowlisted tokenId to claim + * @param _quantity Allowlisted quantity of tokens to claim + * @param _proofs Merkle proofs for allowlist verification + */ + function claimERC1155( + address _token, + address _receiver, + 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.verifyCalldata( + _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 + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Lets contract owner set merkle root (allowlist) for claim based airdrops. + * + * @param _token Address of airdrop token + * @param _tokenMerkleRoot Merkle root of allowlist + * @param _resetClaimStatus Reset claim status / amount claimed so far to zero for all recipients + */ + function setMerkleRoot(address _token, bytes32 _tokenMerkleRoot, bool _resetClaimStatus) external onlyOwner { + if (_resetClaimStatus || tokenConditionId[_token] == 0) { + tokenConditionId[_token] += 1; + } + tokenMerkleRoot[_token] = _tokenMerkleRoot; + } + + /*/////////////////////////////////////////////////////////////// + Miscellaneous + //////////////////////////////////////////////////////////////*/ + + /// @notice Returns claim status of a receiver for a claim based airdrop + function isClaimed(address _receiver, address _token, uint256 _tokenId) external view returns (bool) { + uint256 _conditionId = tokenConditionId[_token]; + + 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; + } + /// @dev Checks whether contract owner can be set in the given execution context. + function _canSetOwner() internal view virtual override returns (bool) { + return msg.sender == owner(); + } + + /// @dev Domain name and version for EIP-712 + function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) { + name = "Airdrop"; + version = "1"; + } + + /// @dev Keccak256 hash of receiver and token addresses, for claim based airdrop status tracking + function _getClaimHashERC20(address _receiver, address _token) private view returns (bytes32) { + return keccak256(abi.encodePacked(_receiver, _token)); + } + + /// @dev Keccak256 hash of receiver, token address and tokenId, for claim based airdrop status tracking + function _getClaimHashERC721(address _receiver, address _token, uint256 _tokenId) private view returns (bytes32) { + return keccak256(abi.encodePacked(_receiver, _token, _tokenId)); + } + + /// @dev Keccak256 hash of receiver, token address and tokenId, for claim based airdrop status tracking + function _getClaimHashERC1155(address _receiver, address _token, uint256 _tokenId) private view returns (bytes32) { + return keccak256(abi.encodePacked(_receiver, _token, _tokenId)); + } + + /// @dev Hash nested struct within AirdropRequest___ + function _hashContentInfoERC20(AirdropContentERC20[] calldata contents) private pure returns (bytes32) { + bytes32[] memory contentHashes = new bytes32[](contents.length); + for (uint256 i = 0; i < contents.length; i++) { + contentHashes[i] = keccak256(abi.encode(CONTENT_TYPEHASH_ERC20, contents[i].recipient, contents[i].amount)); + } + return keccak256(abi.encodePacked(contentHashes)); + } + + /// @dev Hash nested struct within AirdropRequest___ + function _hashContentInfoERC721(AirdropContentERC721[] calldata contents) private pure returns (bytes32) { + bytes32[] memory contentHashes = new bytes32[](contents.length); + for (uint256 i = 0; i < contents.length; i++) { + contentHashes[i] = keccak256( + abi.encode(CONTENT_TYPEHASH_ERC721, contents[i].recipient, contents[i].tokenId) + ); + } + return keccak256(abi.encodePacked(contentHashes)); + } + + /// @dev Hash nested struct within AirdropRequest___ + function _hashContentInfoERC1155(AirdropContentERC1155[] calldata contents) private pure returns (bytes32) { + bytes32[] memory contentHashes = new bytes32[](contents.length); + for (uint256 i = 0; i < contents.length; i++) { + contentHashes[i] = keccak256( + abi.encode(CONTENT_TYPEHASH_ERC1155, contents[i].recipient, contents[i].tokenId, contents[i].amount) + ); + } + return keccak256(abi.encodePacked(contentHashes)); + } + + /// @dev Verify EIP-712 signature + 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); + + return SignatureCheckerLib.isValidSignatureNowCalldata(owner(), digest, signature); + } + + /// @dev Verify EIP-712 signature + 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); + + return SignatureCheckerLib.isValidSignatureNowCalldata(owner(), digest, signature); + } + + /// @dev Verify EIP-712 signature + 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); + + return SignatureCheckerLib.isValidSignatureNowCalldata(owner(), digest, signature); + } +} diff --git a/foundry.toml b/foundry.toml index a86fc9b13..0a5adca0c 100644 --- a/foundry.toml +++ b/foundry.toml @@ -39,6 +39,10 @@ remappings = [ 'erc721a/=lib/ERC721A/', '@thirdweb-dev/dynamic-contracts/=lib/dynamic-contracts/', 'lib/sstore2=lib/dynamic-contracts/lib/sstore2/', + '@solady/=lib/solady/', + '@seaport/=lib/seaport/contracts/', + 'seaport-types/=lib/seaport/lib/seaport-types/', + 'seaport-core/=lib/seaport/lib/seaport-core/' ] fs_permissions = [{ access = "read-write", path = "./src/test/smart-wallet/utils"}] src = 'contracts' diff --git a/gasreport.txt b/gasreport.txt index ea8d57ffc..ce02c209e 100644 --- a/gasreport.txt +++ b/gasreport.txt @@ -1,181 +1,207 @@ 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) +Ran 2 tests for src/test/benchmark/MultiwrapBenchmark.t.sol:MultiwrapBenchmarkTest +[PASS] test_benchmark_multiwrap_unwrap() (gas: 152040) +[PASS] test_benchmark_multiwrap_wrap() (gas: 480722) +Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 665.73ms (584.46µs CPU time) + +Ran 5 tests for src/test/benchmark/SignatureDropBenchmark.t.sol:SignatureDropBenchmarkTest +[PASS] test_benchmark_signatureDrop_claim_five_tokens() (gas: 185688) +[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 665.92ms (942.96µs CPU time) + +Ran 3 tests for src/test/benchmark/EditionStakeBenchmark.t.sol:EditionStakeBenchmarkTest +[PASS] test_benchmark_editionStake_claimRewards() (gas: 98765) +[PASS] test_benchmark_editionStake_stake() (gas: 203676) +[PASS] test_benchmark_editionStake_withdraw() (gas: 94296) +Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 666.96ms (533.96µs CPU time) + +Ran 3 tests for src/test/benchmark/NFTStakeBenchmark.t.sol:NFTStakeBenchmarkTest +[PASS] test_benchmark_nftStake_claimRewards() (gas: 99831) +[PASS] test_benchmark_nftStake_stake_five_tokens() (gas: 553577) +[PASS] test_benchmark_nftStake_withdraw() (gas: 96144) +Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 669.06ms (710.17µs CPU time) + +Ran 1 test for src/test/smart-wallet/utils/AABenchmarkPrepare.sol:AABenchmarkPrepare +[PASS] test_prepareBenchmarkFile() (gas: 2955770) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 677.51ms (13.32ms CPU time) + +Ran 1 test for src/test/benchmark/AirdropERC20Benchmark.t.sol:AirdropERC20BenchmarkTest +[PASS] test_benchmark_airdropERC20_airdrop() (gas: 32443785) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 688.85ms (17.93ms CPU time) + +Ran 1 test for src/test/benchmark/AirdropERC721Benchmark.t.sol:AirdropERC721BenchmarkTest +[PASS] test_benchmark_airdropERC721_airdrop() (gas: 42241588) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 701.05ms (26.57ms CPU time) + +Ran 3 tests for src/test/benchmark/PackBenchmark.t.sol:PackBenchmarkTest +[PASS] test_benchmark_pack_addPackContents() (gas: 312595) +[PASS] test_benchmark_pack_createPack() (gas: 1419379) +[PASS] test_benchmark_pack_openPack() (gas: 302612) +Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 181.87ms (2.36ms CPU time) + +Ran 3 tests for src/test/benchmark/TokenERC20Benchmark.t.sol:TokenERC20BenchmarkTest +[PASS] test_benchmark_tokenERC20_mintTo() (gas: 139513) +[PASS] test_benchmark_tokenERC20_mintWithSignature_pay_with_ERC20() (gas: 221724) +[PASS] test_benchmark_tokenERC20_mintWithSignature_pay_with_native_token() (gas: 228786) +Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 188.65ms (1.10ms CPU time) + +Ran 4 tests for src/test/benchmark/TokenERC721Benchmark.t.sol:TokenERC721BenchmarkTest +[PASS] test_benchmark_tokenERC721_burn() (gas: 40392) +[PASS] test_benchmark_tokenERC721_mintTo() (gas: 172834) +[PASS] test_benchmark_tokenERC721_mintWithSignature_pay_with_ERC20() (gas: 301844) +[PASS] test_benchmark_tokenERC721_mintWithSignature_pay_with_native_token() (gas: 308814) +Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 192.61ms (1.31ms CPU time) + +Ran 4 tests for src/test/benchmark/TokenERC1155Benchmark.t.sol:TokenERC1155BenchmarkTest +[PASS] test_benchmark_tokenERC1155_burn() (gas: 30352) +[PASS] test_benchmark_tokenERC1155_mintTo() (gas: 144229) +[PASS] test_benchmark_tokenERC1155_mintWithSignature_pay_with_ERC20() (gas: 307291) +[PASS] test_benchmark_tokenERC1155_mintWithSignature_pay_with_native_token() (gas: 318712) +Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 225.96ms (1.36ms CPU time) + +Ran 3 tests for src/test/benchmark/TokenStakeBenchmark.t.sol:TokenStakeBenchmarkTest +[PASS] test_benchmark_tokenStake_claimRewards() (gas: 101098) +[PASS] test_benchmark_tokenStake_stake() (gas: 195556) +[PASS] test_benchmark_tokenStake_withdraw() (gas: 104792) +Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 196.93ms (479.46µs CPU time) + +Ran 1 test for src/test/benchmark/AirdropERC1155Benchmark.t.sol:AirdropERC1155BenchmarkTest +[PASS] test_benchmark_airdropERC1155_airdrop() (gas: 38536544) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 341.15ms (19.66ms CPU time) + +Ran 3 tests for src/test/benchmark/PackVRFDirectBenchmark.t.sol:PackVRFDirectBenchmarkTest +[PASS] test_benchmark_packvrf_createPack() (gas: 1392387) +[PASS] test_benchmark_packvrf_openPack() (gas: 150677) [PASS] test_benchmark_packvrf_openPackAndClaimRewards() (gas: 3621) -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 201.65ms (2.27ms CPU time) + +Ran 3 tests for src/test/benchmark/DropERC1155Benchmark.t.sol:DropERC1155BenchmarkTest +[PASS] test_benchmark_dropERC1155_claim() (gas: 245552) +[PASS] test_benchmark_dropERC1155_lazyMint() (gas: 146425) +[PASS] test_benchmark_dropERC1155_setClaimConditions_five_conditions() (gas: 525725) +Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.20s (706.01ms CPU time) + +Ran 2 tests for src/test/benchmark/DropERC20Benchmark.t.sol:DropERC20BenchmarkTest +[PASS] test_benchmark_dropERC20_claim() (gas: 291508) +[PASS] test_benchmark_dropERC20_setClaimConditions_five_conditions() (gas: 530026) +Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 510.75ms (589.86ms CPU time) + +Ran 23 tests for src/test/benchmark/AirdropBenchmark.t.sol:AirdropBenchmarkTest +[PASS] test_benchmark_airdropClaim_erc1155() (gas: 105358) +[PASS] test_benchmark_airdropClaim_erc20() (gas: 109724) +[PASS] test_benchmark_airdropClaim_erc721() (gas: 108870) +[PASS] test_benchmark_airdropPush_erc1155ReceiverCompliant() (gas: 82427) +[PASS] test_benchmark_airdropPush_erc1155_10() (gas: 370062) +[PASS] test_benchmark_airdropPush_erc1155_100() (gas: 3266571) +[PASS] test_benchmark_airdropPush_erc1155_1000() (gas: 32348198) +[PASS] test_benchmark_airdropPush_erc20_10() (gas: 345649) +[PASS] test_benchmark_airdropPush_erc20_100() (gas: 2976236) +[PASS] test_benchmark_airdropPush_erc20_1000() (gas: 29352084) +[PASS] test_benchmark_airdropPush_erc721ReceiverCompliant() (gas: 86434) +[PASS] test_benchmark_airdropPush_erc721_10() (gas: 426498) +[PASS] test_benchmark_airdropPush_erc721_100() (gas: 3837162) +[PASS] test_benchmark_airdropPush_erc721_1000() (gas: 38107847) +[PASS] test_benchmark_airdropSignature_erc115_10() (gas: 416712) +[PASS] test_benchmark_airdropSignature_erc115_100() (gas: 3458091) +[PASS] test_benchmark_airdropSignature_erc115_1000() (gas: 34334256) +[PASS] test_benchmark_airdropSignature_erc20_10() (gas: 389286) +[PASS] test_benchmark_airdropSignature_erc20_100() (gas: 3138882) +[PASS] test_benchmark_airdropSignature_erc20_1000() (gas: 30936576) +[PASS] test_benchmark_airdropSignature_erc721_10() (gas: 470201) +[PASS] test_benchmark_airdropSignature_erc721_100() (gas: 4009643) +[PASS] test_benchmark_airdropSignature_erc721_1000() (gas: 39692110) +Suite result: ok. 23 passed; 0 failed; 0 skipped; finished in 1.21s (1.25s CPU time) + +Ran 5 tests for src/test/benchmark/DropERC721Benchmark.t.sol:DropERC721BenchmarkTest +[PASS] test_benchmark_dropERC721_claim_five_tokens() (gas: 273303) +[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 466.63ms (512.92ms CPU time) + +Ran 14 tests for src/test/benchmark/AccountBenchmark.t.sol:AccountBenchmarkTest +[PASS] test_state_accountReceivesNativeTokens() (gas: 34537) +[PASS] test_state_addAndWithdrawDeposit() (gas: 148780) +[PASS] test_state_contractMetadata() (gas: 114307) +[PASS] test_state_createAccount_viaEntrypoint() (gas: 458192) +[PASS] test_state_createAccount_viaFactory() (gas: 355822) +[PASS] test_state_executeBatchTransaction() (gas: 76066) +[PASS] test_state_executeBatchTransaction_viaAccountSigner() (gas: 488470) +[PASS] test_state_executeBatchTransaction_viaEntrypoint() (gas: 138443) +[PASS] test_state_executeTransaction() (gas: 68891) +[PASS] test_state_executeTransaction_viaAccountSigner() (gas: 471272) +[PASS] test_state_executeTransaction_viaEntrypoint() (gas: 128073) +[PASS] test_state_receiveERC1155NFT() (gas: 66043) +[PASS] test_state_receiveERC721NFT() (gas: 100196) +[PASS] test_state_transferOutsNativeTokens() (gas: 133673) +Suite result: ok. 14 passed; 0 failed; 0 skipped; finished in 1.32s (26.86ms CPU time) + + +Ran 19 test suites in 1.45s (10.97s CPU time): 84 tests passed, 0 failed, 0 skipped (84 total tests) test_benchmark_packvrf_openPackAndClaimRewards() (gas: 0 (0.000%)) -test_benchmark_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/murky b/lib/murky new file mode 160000 index 000000000..40de6e801 --- /dev/null +++ b/lib/murky @@ -0,0 +1 @@ +Subproject commit 40de6e80117f39cda69d71b07b7c824adac91b29 diff --git a/lib/seaport b/lib/seaport new file mode 160000 index 000000000..1d12e33b7 --- /dev/null +++ b/lib/seaport @@ -0,0 +1 @@ +Subproject commit 1d12e33b71b6988cbbe955373ddbc40a87bd5b16 diff --git a/lib/seaport-core b/lib/seaport-core new file mode 160000 index 000000000..d4e8c74ad --- /dev/null +++ b/lib/seaport-core @@ -0,0 +1 @@ +Subproject commit d4e8c74adc472b311ab64b5c9f9757b5bba57a15 diff --git a/lib/seaport-sol b/lib/seaport-sol new file mode 160000 index 000000000..040d00576 --- /dev/null +++ b/lib/seaport-sol @@ -0,0 +1 @@ +Subproject commit 040d005768abafe3308b5f996aca3fd843d9c20e diff --git a/lib/seaport-types b/lib/seaport-types new file mode 160000 index 000000000..25bae8ddf --- /dev/null +++ b/lib/seaport-types @@ -0,0 +1 @@ +Subproject commit 25bae8ddfa8709e5c51ab429fe06024e46a18f15 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 d84675cfa..5f73d961b 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@openzeppelin/contracts": "^4.9.3", "@openzeppelin/contracts-upgradeable": "^4.9.3", "@thirdweb-dev/dynamic-contracts": "^1.2.4", + "@thirdweb-dev/merkletree": "^0.2.2", "@typechain/ethers-v5": "^10.2.1", "@types/fs-extra": "^9.0.13", "@types/mocha": "^9.1.1", @@ -54,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..694aac542 --- /dev/null +++ b/src/test/airdrop/Airdrop.t.sol @@ -0,0 +1,1065 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import { Airdrop, SafeTransferLib, ECDSA } from "contracts/prebuilts/unaudited/airdrop/Airdrop.sol"; + +// Test imports +import { TWProxy } from "contracts/infra/TWProxy.sol"; +import "../utils/BaseTest.sol"; + +contract MockSmartWallet { + using ECDSA for bytes32; + + bytes4 private constant EIP1271_MAGIC_VALUE = 0x1626ba7e; + address private admin; + + constructor(address _admin) { + admin = _admin; + } + + function isValidSignature(bytes32 _hash, bytes memory _signature) public view returns (bytes4) { + if (_hash.recover(_signature) == admin) { + return EIP1271_MAGIC_VALUE; + } + } + + function onERC721Received(address, address, uint256, bytes memory) external pure returns (bytes4) { + return this.onERC721Received.selector; + } + + function onERC1155Received(address, address, uint256, uint256, bytes memory) external pure returns (bytes4) { + return this.onERC1155Received.selector; + } + + function onERC1155BatchReceived( + address, + address, + uint256[] memory, + uint256[] memory, + bytes memory + ) external pure returns (bytes4) { + return this.onERC1155BatchReceived.selector; + } +} + +contract AirdropTest is BaseTest { + Airdrop internal airdrop; + MockSmartWallet internal mockSmartWallet; + + bytes32 private constant CONTENT_TYPEHASH_ERC20 = + keccak256("AirdropContentERC20(address recipient,uint256 amount)"); + 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)) + ); + + mockSmartWallet = new MockSmartWallet(signer); + } + + 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(SafeTransferLib.ETHTransferFailed.selector)); + airdrop.airdropNativeToken{ value: 0 }(contents); + } + + /*/////////////////////////////////////////////////////////////// + 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_state_airdropSignature_erc20_eip1271() public { + // set mockSmartWallet as contract owner + vm.prank(signer); + airdrop.setOwner(address(mockSmartWallet)); + + // mint tokens to mockSmartWallet + erc20.mint(address(mockSmartWallet), 100 ether); + vm.prank(address(mockSmartWallet)); + erc20.approve(address(airdrop), 100 ether); + + Airdrop.AirdropContentERC20[] memory contents = _getContentsERC20(10); + Airdrop.AirdropRequestERC20 memory req = Airdrop.AirdropRequestERC20({ + uid: bytes32(uint256(1)), + tokenAddress: address(erc20), + expirationTimestamp: 1000, + contents: contents + }); + + // sign with original EOA signer private key + bytes memory signature = _signReqERC20(req, privateKey); + + airdrop.airdropERC20WithSignature(req, signature); + + uint256 totalAmount; + for (uint256 i = 0; i < contents.length; i++) { + totalAmount += contents[i].amount; + assertEq(erc20.balanceOf(contents[i].recipient), contents[i].amount); + } + assertEq(erc20.balanceOf(address(mockSmartWallet)), 100 ether - totalAmount); + } + + function test_revert_airdropSignature_erc20_eip1271_invalidSignature() public { + // set mockSmartWallet as contract owner + vm.prank(signer); + airdrop.setOwner(address(mockSmartWallet)); + + // mint tokens to mockSmartWallet + erc20.mint(address(mockSmartWallet), 100 ether); + vm.prank(address(mockSmartWallet)); + erc20.approve(address(airdrop), 100 ether); + + Airdrop.AirdropContentERC20[] memory contents = _getContentsERC20(10); + Airdrop.AirdropRequestERC20 memory req = Airdrop.AirdropRequestERC20({ + uid: bytes32(uint256(1)), + tokenAddress: address(erc20), + expirationTimestamp: 1000, + contents: contents + }); + + // sign with random private key + bytes memory signature = _signReqERC20(req, 123); + + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropRequestInvalidSigner.selector)); + airdrop.airdropERC20WithSignature(req, signature); + } + + function test_revert_airdropSignature_erc20_expired() public { + erc20.mint(signer, 100 ether); + vm.prank(signer); + 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_state_airdropSignature_erc721_eip1271() public { + // set mockSmartWallet as contract owner + vm.prank(signer); + airdrop.setOwner(address(mockSmartWallet)); + + // mint tokens to mockSmartWallet + erc721.mint(address(mockSmartWallet), 1000); + vm.prank(address(mockSmartWallet)); + erc721.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC721[] memory contents = _getContentsERC721(10); + Airdrop.AirdropRequestERC721 memory req = Airdrop.AirdropRequestERC721({ + uid: bytes32(uint256(1)), + tokenAddress: address(erc721), + expirationTimestamp: 1000, + contents: contents + }); + + // sign with original EOA signer private key + bytes memory signature = _signReqERC721(req, privateKey); + + airdrop.airdropERC721WithSignature(req, signature); + + for (uint256 i = 0; i < contents.length; i++) { + assertEq(erc721.ownerOf(contents[i].tokenId), contents[i].recipient); + } + } + + function test_revert_airdropSignature_erc721_eip1271_invalidSignature() public { + // set mockSmartWallet as contract owner + vm.prank(signer); + airdrop.setOwner(address(mockSmartWallet)); + + // mint tokens to mockSmartWallet + erc721.mint(address(mockSmartWallet), 1000); + vm.prank(address(mockSmartWallet)); + erc721.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC721[] memory contents = _getContentsERC721(10); + Airdrop.AirdropRequestERC721 memory req = Airdrop.AirdropRequestERC721({ + uid: bytes32(uint256(1)), + tokenAddress: address(erc721), + expirationTimestamp: 1000, + contents: contents + }); + + // sign with random private key + bytes memory signature = _signReqERC721(req, 123); + + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropRequestInvalidSigner.selector)); + airdrop.airdropERC721WithSignature(req, signature); + } + + function test_revert_airdropSignature_erc721_expired() public { + erc721.mint(signer, 1000); + vm.prank(signer); + 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_state_airdropSignature_erc1155_eip1271() public { + // set mockSmartWallet as contract owner + vm.prank(signer); + airdrop.setOwner(address(mockSmartWallet)); + + // mint tokens to mockSmartWallet + erc1155.mint(address(mockSmartWallet), 0, 100 ether); + vm.prank(address(mockSmartWallet)); + erc1155.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC1155[] memory contents = _getContentsERC1155(10); + Airdrop.AirdropRequestERC1155 memory req = Airdrop.AirdropRequestERC1155({ + uid: bytes32(uint256(1)), + tokenAddress: address(erc1155), + expirationTimestamp: 1000, + contents: contents + }); + + // sign with original EOA signer private key + bytes memory signature = _signReqERC1155(req, privateKey); + + airdrop.airdropERC1155WithSignature(req, signature); + + for (uint256 i = 0; i < contents.length; i++) { + assertEq(erc1155.balanceOf(contents[i].recipient, contents[i].tokenId), contents[i].amount); + } + } + + function test_revert_airdropSignature_erc1155_eip1271_invalidSignature() public { + // set mockSmartWallet as contract owner + vm.prank(signer); + airdrop.setOwner(address(mockSmartWallet)); + + // mint tokens to mockSmartWallet + erc1155.mint(address(mockSmartWallet), 0, 100 ether); + vm.prank(address(mockSmartWallet)); + erc1155.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC1155[] memory contents = _getContentsERC1155(10); + Airdrop.AirdropRequestERC1155 memory req = Airdrop.AirdropRequestERC1155({ + uid: bytes32(uint256(1)), + tokenAddress: address(erc1155), + expirationTimestamp: 1000, + contents: contents + }); + + // sign with random private key + bytes memory signature = _signReqERC1155(req, 123); + + vm.expectRevert(abi.encodeWithSelector(Airdrop.AirdropRequestInvalidSigner.selector)); + airdrop.airdropERC1155WithSignature(req, signature); + } + + function test_revert_airdropSignature_erc115_expired() public { + erc1155.mint(signer, 0, 100 ether); + vm.prank(signer); + 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..865f09c01 --- /dev/null +++ b/src/test/benchmark/AirdropBenchmark.t.sol @@ -0,0 +1,695 @@ +// 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 ERC721ReceiverCompliant is IERC721Receiver { + function onERC721Received( + address, + address, + uint256, + bytes calldata + ) external view virtual override returns (bytes4) { + return this.onERC721Received.selector; + } +} + +contract ERC1155ReceiverCompliant is IERC1155Receiver { + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external view virtual override returns (bytes4) { + return this.onERC1155Received.selector; + } + + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4) { + return this.onERC1155BatchReceived.selector; + } + + function supportsInterface(bytes4 interfaceId) external view returns (bool) {} +} + +contract AirdropBenchmarkTest is BaseTest { + Airdrop internal airdrop; + + 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); + } + + function test_benchmark_airdropPush_erc721ReceiverCompliant() public { + vm.pauseGasMetering(); + + erc721.mint(signer, 100); + vm.prank(signer); + erc721.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC721[] memory contents = new Airdrop.AirdropContentERC721[](1); + + contents[0].recipient = address(new ERC721ReceiverCompliant()); + contents[0].tokenId = 0; + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC721(address(erc721), contents); + } + + /*/////////////////////////////////////////////////////////////// + Benchmark: Airdrop Signature ERC721 + //////////////////////////////////////////////////////////////*/ + + 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); + } + + function test_benchmark_airdropPush_erc1155ReceiverCompliant() public { + vm.pauseGasMetering(); + + erc1155.mint(signer, 0, 100 ether); + vm.prank(signer); + erc1155.setApprovalForAll(address(airdrop), true); + + Airdrop.AirdropContentERC1155[] memory contents = new Airdrop.AirdropContentERC1155[](1); + + contents[0].recipient = address(new ERC1155ReceiverCompliant()); + contents[0].tokenId = 0; + contents[0].amount = 100; + + vm.prank(signer); + vm.resumeGasMetering(); + airdrop.airdropERC1155(address(erc1155), contents); + } + + /*/////////////////////////////////////////////////////////////// + Benchmark: Airdrop Signature ERC1155 + //////////////////////////////////////////////////////////////*/ + + 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])); diff --git a/src/test/smart-wallet/utils/AABenchmarkArtifacts.sol b/src/test/smart-wallet/utils/AABenchmarkArtifacts.sol index ad27bdff0..69107bb0c 100644 --- a/src/test/smart-wallet/utils/AABenchmarkArtifacts.sol +++ b/src/test/smart-wallet/utils/AABenchmarkArtifacts.sol @@ -9,6 +9,6 @@ interface ThirdwebAccount { } address constant THIRDWEB_ACCOUNT_FACTORY_ADDRESS = 0x2e234DAe75C793f67A35089C9d99245E1C58470b; address constant THIRDWEB_ACCOUNT_IMPL_ADDRESS = 0xffD4505B3452Dc22f8473616d50503bA9E1710Ac; -bytes constant THIRDWEB_ACCOUNT_FACTORY_BYTECODE = hex"608060405234801561001057600080fd5b50600436106101285760003560e01c806308e93d0a1461012d5780630b61e12b1461014b5780630e6254fd1461016057806311464fbe14610173578063248a9ca3146101b25780632f2ff15d146101d357806336568abe146101e657806358451f97146101f957806383a03f8c146102015780638878ed33146102145780639010d07c1461022757806391d148541461023a5780639387a3801461025d578063938e3d7b14610270578063a217fddf14610283578063a32fa5b31461028b578063a65d69d41461029e578063ac9650d8146102c5578063c3c5a547146102e5578063ca15c873146102f8578063d547741f1461030b578063d8fd8f441461031e578063e68a7c3b14610331578063e8a3d48514610344575b600080fd5b610135610359565b6040516101429190611945565b60405180910390f35b61015e6101593660046119ae565b61036a565b005b61013561016e3660046119d8565b61040b565b61019a7f000000000000000000000000ffd4505b3452dc22f8473616d50503ba9e1710ac81565b6040516001600160a01b039091168152602001610142565b6101c56101c03660046119f3565b610435565b604051908152602001610142565b61015e6101e1366004611a0c565b610453565b61015e6101f4366004611a0c565b6104fd565b6101c561055c565b61015e61020f3660046119f3565b610568565b61019a610222366004611a38565b6105b6565b61019a610235366004611aba565b610630565b61024d610248366004611a0c565b61073e565b6040519015158152602001610142565b61015e61026b3660046119ae565b610772565b61015e61027e366004611af2565b610809565b6101c5600081565b61024d610299366004611a0c565b61085a565b61019a7f0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d278981565b6102d86102d3366004611ba2565b6108bd565b6040516101429190611c66565b61024d6102f33660046119d8565b610a19565b6101c56103063660046119f3565b610a25565b61015e610319366004611a0c565b610ac2565b61019a61032c366004611a38565b610acd565b61013561033f366004611aba565b610c18565b61034c610d49565b6040516101429190611cca565b60606103656000610de1565b905090565b336103758183610dee565b61039a5760405162461bcd60e51b815260040161039190611cdd565b60405180910390fd5b6001600160a01b03831660009081526002602052604081206103bc9083610e32565b9050801561040557836001600160a01b0316826001600160a01b03167f12146497b3b826918ec47f0cac7272a09ed06b30c16c030e99ec48ff5dd60b4760405160405180910390a35b50505050565b6001600160a01b038116600090815260026020526040902060609061042f90610de1565b92915050565b600061043f610e47565b600092835260010160205250604090205490565b61047761045e610e47565b6000848152600191909101602052604090205433610e6b565b61047f610e47565b6000838152602091825260408082206001600160a01b0385168352909252205460ff16156104ef5760405162461bcd60e51b815260206004820152601d60248201527f43616e206f6e6c79206772616e7420746f206e6f6e20686f6c646572730000006044820152606401610391565b6104f98282610ef0565b5050565b336001600160a01b038216146105525760405162461bcd60e51b815260206004820152601a60248201527921b0b71037b7363c903932b737bab731b2903337b91039b2b63360311b6044820152606401610391565b6104f98282610f04565b60006103656000610f18565b336105738183610dee565b61058f5760405162461bcd60e51b815260040161039190611cdd565b61059a600082610e32565b6104f95760405162461bcd60e51b815260040161039190611d14565b6000806105f98585858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610f2292505050565b90506106257f000000000000000000000000ffd4505b3452dc22f8473616d50503ba9e1710ac82610f55565b9150505b9392505050565b60008061063b610fb5565b600085815260209190915260408120549150805b82811015610735576000610661610fb5565b60008881526020918252604080822085835260010190925220546001600160a01b0316146106d9578482036106c757610698610fb5565b600087815260209182526040808220938252600190930190915220546001600160a01b0316925061042f915050565b6106d2600183611d74565b9150610723565b6106e486600061073e565b801561071057506106f3610fb5565b600087815260209182526040808220828052600201909252205481145b1561072357610720600183611d74565b91505b61072e600182611d74565b905061064f565b50505092915050565b6000610748610e47565b6000938452602090815260408085206001600160a01b039490941685529290525090205460ff1690565b3361077d8183610dee565b6107995760405162461bcd60e51b815260040161039190611cdd565b6001600160a01b03831660009081526002602052604081206107bb9083610fbf565b9050801561040557836001600160a01b0316826001600160a01b03167f98d1ebbe00ae92a5de96a0f49742a8afa89f42363592bc2e7cfaaed68b45e7a660405160405180910390a350505050565b610811610fd4565b61084e5760405162461bcd60e51b815260206004820152600e60248201526d139bdd08185d5d1a1bdc9a5e995960921b6044820152606401610391565b61085781610fe0565b50565b6000610864610e47565b600084815260209182526040808220828052909252205460ff166108b45761088a610e47565b6000848152602091825260408082206001600160a01b0386168352909252205460ff16905061042f565b50600192915050565b6060816001600160401b038111156108d7576108d7611adc565b60405190808252806020026020018201604052801561090a57816020015b60608152602001906001900390816108f55790505b509050336000805b848110156107355781156109915761096f3087878481811061093657610936611d87565b90506020028101906109489190611d9d565b8660405160200161095b93929190611dea565b6040516020818303038152906040526110c7565b84828151811061098157610981611d87565b6020026020010181905250610a11565b6109f3308787848181106109a7576109a7611d87565b90506020028101906109b99190611d9d565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506110c792505050565b848281518110610a0557610a05611d87565b60200260200101819052505b600101610912565b600061042f81836110ec565b600080610a30610fb5565b6000848152602091909152604081205491505b81811015610a9d576000610a55610fb5565b60008681526020918252604080822085835260010190925220546001600160a01b031614610a8b57610a88600184611d74565b92505b610a96600182611d74565b9050610a43565b50610aa983600061073e565b15610abc57610ab9600183611d74565b91505b50919050565b61055261045e610e47565b6000807f000000000000000000000000ffd4505b3452dc22f8473616d50503ba9e1710ac90506000610b358686868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610f2292505050565b90506000610b438383610f55565b90506001600160a01b0381163b15610b5f579250610629915050565b610b69838361110e565b9050336001600160a01b037f0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27891614610bc257610ba6600082610e32565b610bc25760405162461bcd60e51b815260040161039190611d14565b610bce818888886111a5565b866001600160a01b0316816001600160a01b03167fac631f3001b55ea1509cf3d7e74898f85392a61a76e8149181ae1259622dabc860405160405180910390a39695505050505050565b60608183108015610c325750610c2e6000610f18565b8211155b610c8a5760405162461bcd60e51b815260206004820152602360248201527f426173654163636f756e74466163746f72793a20696e76616c696420696e646960448201526263657360e81b6064820152608401610391565b6000610c968484611e0b565b9050610ca28484611e0b565b6001600160401b03811115610cb957610cb9611adc565b604051908082528060200260200182016040528015610ce2578160200160208202803683370190505b50915060005b81811015610d4157610d05610cfd8683611d74565b60009061120d565b838281518110610d1757610d17611d87565b6001600160a01b0390921660209283029190910190910152610d3a600182611d74565b9050610ce8565b505092915050565b6060610d53611219565b8054610d5e90611e1e565b80601f0160208091040260200160405190810160405280929190818152602001828054610d8a90611e1e565b8015610dd75780601f10610dac57610100808354040283529160200191610dd7565b820191906000526020600020905b815481529060010190602001808311610dba57829003601f168201915b5050505050905090565b606060006106298361123d565b600080610e1b7f000000000000000000000000ffd4505b3452dc22f8473616d50503ba9e1710ac84610f55565b6001600160a01b0385811691161491505092915050565b6000610629836001600160a01b038416611299565b7f0a7b0f5c59907924802379ebe98cdc23e2ee7820f63d30126e10b3752010e50090565b610e73610e47565b6000838152602091825260408082206001600160a01b0385168352909252205460ff166104f957610eae816001600160a01b031660146112e8565b610eb98360206112e8565b604051602001610eca929190611e52565b60408051601f198184030181529082905262461bcd60e51b825261039191600401611cca565b610efa8282611483565b6104f982826114ec565b610f0e82826115ab565b6104f98282611614565b600061042f825490565b60008282604051602001610f37929190611ebf565b60405160208183030381529060405280519060200120905092915050565b6040513060388201526f5af43d82803e903d91602b57fd5bf3ff602482015260148101839052733d602d80600a3d3981f3363d3d373d3d3d363d738152605881018290526037600c82012060788201526055604390910120600090610629565b60006103656116a3565b6000610629836001600160a01b038416611705565b6000610365813361073e565b6000610fea611219565b8054610ff590611e1e565b80601f016020809104026020016040519081016040528092919081815260200182805461102190611e1e565b801561106e5780601f106110435761010080835404028352916020019161106e565b820191906000526020600020905b81548152906001019060200180831161105157829003601f168201915b505050505090508161107e611219565b906110899082611f34565b507fc9c7c3fe08b88b4df9d4d47ef47d2c43d55c025a0ba88ca442580ed9e7348a1681836040516110bb929190611ff3565b60405180910390a15050565b606061062983836040518060600160405280602781526020016120b9602791396117f8565b6001600160a01b03811660009081526001830160205260408120541515610629565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b03811661042f5760405162461bcd60e51b8152602060048201526017602482015276115490cc4c4d8dce8818dc99585d194c8819985a5b1959604a1b6044820152606401610391565b60405163347d5e2560e21b81526001600160a01b0385169063d1f57894906111d590869086908690600401612018565b600060405180830381600087803b1580156111ef57600080fd5b505af1158015611203573d6000803e3d6000fd5b5050505050505050565b60006106298383611870565b7f4bc804ba64359c0e35e5ed5d90ee596ecaa49a3a930ddcb1470ea0dd625da90090565b60608160000180548060200260200160405190810160405280929190818152602001828054801561128d57602002820191906000526020600020905b815481526020019060010190808311611279575b50505050509050919050565b60008181526001830160205260408120546112e05750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915561042f565b50600061042f565b606060006112f7836002612058565b611302906002611d74565b6001600160401b0381111561131957611319611adc565b6040519080825280601f01601f191660200182016040528015611343576020820181803683370190505b509050600360fc1b8160008151811061135e5761135e611d87565b60200101906001600160f81b031916908160001a905350600f60fb1b8160018151811061138d5761138d611d87565b60200101906001600160f81b031916908160001a90535060006113b1846002612058565b6113bc906001611d74565b90505b6001811115611434576f181899199a1a9b1b9c1cb0b131b232b360811b85600f16601081106113f0576113f0611d87565b1a60f81b82828151811061140657611406611d87565b60200101906001600160f81b031916908160001a90535060049490941c9361142d8161206f565b90506113bf565b5083156106295760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610391565b600161148d610e47565b6000848152602091825260408082206001600160a01b0386168084529352808220805460ff1916941515949094179093559151339285917f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d9190a45050565b60006114f6610fb5565b6000848152602091909152604090205490506001611512610fb5565b6000858152602091909152604081208054909190611531908490611d74565b90915550829050611540610fb5565b6000858152602091825260408082208583526001019092522080546001600160a01b0319166001600160a01b039290921691909117905580611580610fb5565b6000948552602090815260408086206001600160a01b03909516865260029094019052919092205550565b6115b58282610e6b565b6115bd610e47565b6000838152602091825260408082206001600160a01b0385168084529352808220805460ff191690555133929185917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b600061161e610fb5565b6000848152602091825260408082206001600160a01b03861683526002019092522054905061164b610fb5565b6000848152602091825260408082208483526001019092522080546001600160a01b031916905561167a610fb5565b6000938452602090815260408085206001600160a01b0390941685526002909301905250812055565b60008060ff196116d460017f0c4ba382c0009cf238e4c1ca1a52f51c61e6248a70bdfb34e5ed49d5578a5c0c611e0b565b6040516020016116e691815260200190565b60408051601f1981840301815291905280516020909101201692915050565b600081815260018301602052604081205480156117ee576000611729600183611e0b565b855490915060009061173d90600190611e0b565b90508181146117a257600086600001828154811061175d5761175d611d87565b906000526020600020015490508087600001848154811061178057611780611d87565b6000918252602080832090910192909255918252600188019052604090208390555b85548690806117b3576117b3612086565b60019003818190600052602060002001600090559055856001016000868152602001908152602001600020600090556001935050505061042f565b600091505061042f565b6060600080856001600160a01b031685604051611815919061209c565b600060405180830381855af49150503d8060008114611850576040519150601f19603f3d011682016040523d82523d6000602084013e611855565b606091505b50915091506118668683838761189a565b9695505050505050565b600082600001828154811061188757611887611d87565b9060005260206000200154905092915050565b60608315611909578251600003611902576001600160a01b0385163b6119025760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610391565b5081611913565b611913838361191b565b949350505050565b81511561192b5781518083602001fd5b8060405162461bcd60e51b81526004016103919190611cca565b6020808252825182820181905260009190848201906040850190845b818110156119865783516001600160a01b031683529284019291840191600101611961565b50909695505050505050565b80356001600160a01b03811681146119a957600080fd5b919050565b600080604083850312156119c157600080fd5b6119ca83611992565b946020939093013593505050565b6000602082840312156119ea57600080fd5b61062982611992565b600060208284031215611a0557600080fd5b5035919050565b60008060408385031215611a1f57600080fd5b82359150611a2f60208401611992565b90509250929050565b600080600060408486031215611a4d57600080fd5b611a5684611992565b925060208401356001600160401b0380821115611a7257600080fd5b818601915086601f830112611a8657600080fd5b813581811115611a9557600080fd5b876020828501011115611aa757600080fd5b6020830194508093505050509250925092565b60008060408385031215611acd57600080fd5b50508035926020909101359150565b634e487b7160e01b600052604160045260246000fd5b600060208284031215611b0457600080fd5b81356001600160401b0380821115611b1b57600080fd5b818401915084601f830112611b2f57600080fd5b813581811115611b4157611b41611adc565b604051601f8201601f19908116603f01168101908382118183101715611b6957611b69611adc565b81604052828152876020848701011115611b8257600080fd5b826020860160208301376000928101602001929092525095945050505050565b60008060208385031215611bb557600080fd5b82356001600160401b0380821115611bcc57600080fd5b818501915085601f830112611be057600080fd5b813581811115611bef57600080fd5b8660208260051b8501011115611c0457600080fd5b60209290920196919550909350505050565b60005b83811015611c31578181015183820152602001611c19565b50506000910152565b60008151808452611c52816020860160208601611c16565b601f01601f19169290920160200192915050565b600060208083016020845280855180835260408601915060408160051b87010192506020870160005b82811015611cbd57603f19888603018452611cab858351611c3a565b94509285019290850190600101611c8f565b5092979650505050505050565b6020815260006106296020830184611c3a565b6020808252601f908201527f4163636f756e74466163746f72793a206e6f7420616e206163636f756e742e00604082015260600190565b6020808252602a908201527f4163636f756e74466163746f72793a206163636f756e7420616c7265616479206040820152691c9959da5cdd195c995960b21b606082015260800190565b634e487b7160e01b600052601160045260246000fd5b8082018082111561042f5761042f611d5e565b634e487b7160e01b600052603260045260246000fd5b6000808335601e19843603018112611db457600080fd5b8301803591506001600160401b03821115611dce57600080fd5b602001915036819003821315611de357600080fd5b9250929050565b8284823760609190911b6001600160601b0319169101908152601401919050565b8181038181111561042f5761042f611d5e565b600181811c90821680611e3257607f821691505b602082108103610abc57634e487b7160e01b600052602260045260246000fd5b7402832b936b4b9b9b4b7b7399d1030b1b1b7bab73a1605d1b815260008351611e82816015850160208801611c16565b7001034b99036b4b9b9b4b733903937b6329607d1b6015918401918201528351611eb3816026840160208801611c16565b01602601949350505050565b6001600160a01b038316815260406020820181905260009061191390830184611c3a565b601f821115611f2f576000816000526020600020601f850160051c81016020861015611f0c5750805b601f850160051c820191505b81811015611f2b57828155600101611f18565b5050505b505050565b81516001600160401b03811115611f4d57611f4d611adc565b611f6181611f5b8454611e1e565b84611ee3565b602080601f831160018114611f965760008415611f7e5750858301515b600019600386901b1c1916600185901b178555611f2b565b600085815260208120601f198616915b82811015611fc557888601518255948401946001909101908401611fa6565b5085821015611fe35787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6040815260006120066040830185611c3a565b82810360208401526106258185611c3a565b6001600160a01b03841681526040602082018190528101829052818360608301376000818301606090810191909152601f909201601f1916010192915050565b808202811582820484141761042f5761042f611d5e565b60008161207e5761207e611d5e565b506000190190565b634e487b7160e01b600052603160045260246000fd5b600082516120ae818460208701611c16565b919091019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220e6503db39467653163adea7dc14399f6675ca20cc526c098edc5f2fce3fe928064736f6c63430008170033"; -bytes constant THIRDWEB_ACCOUNT_IMPL_BYTECODE = hex"60806040526004361061014b5760003560e01c806301ffc9a714610157578063150b7a021461018c5780631626ba7e146101c55780631dd756c5146101e557806324d7806c14610205578063399b77da146102255780633a871cdd1461025357806347e1da2a146102735780634a58db19146102955780634d44560d1461029d5780635892e236146102bd5780637dff5a79146102dd5780638b52d723146102fd578063938e3d7b1461031f578063a9082d841461033f578063ac9650d81461037e578063b0d691fe146103ab578063b61d27f6146103cd578063b76464d5146103ed578063bc197c811461040d578063c45a015514610439578063d087d2881461046d578063d1f5789414610482578063d42f2f35146104a2578063e8a3d485146104b7578063e9523c97146104d9578063f15d424e146104fb578063f23a6e611461052857600080fd5b3661015257005b600080fd5b34801561016357600080fd5b50610177610172366004612d97565b610554565b60405190151581526020015b60405180910390f35b34801561019857600080fd5b506101ac6101a7366004612ea3565b61059a565b6040516001600160e01b03199091168152602001610183565b3480156101d157600080fd5b506101ac6101e0366004612f0e565b6105ab565b3480156101f157600080fd5b50610177610200366004612f6d565b6106ca565b34801561021157600080fd5b50610177610220366004612fb2565b61098e565b34801561023157600080fd5b50610245610240366004612fcf565b6109bd565b604051908152602001610183565b34801561025f57600080fd5b5061024561026e366004612fe8565b610a88565b34801561027f57600080fd5b5061029361028e366004613079565b610aae565b005b610293610c15565b3480156102a957600080fd5b506102936102b8366004613112565b610c7d565b3480156102c957600080fd5b506102936102d836600461317f565b610cf0565b3480156102e957600080fd5b506101776102f8366004612fb2565b6110ad565b34801561030957600080fd5b50610312611166565b6040516101839190613292565b34801561032b57600080fd5b5061029361033a3660046132f6565b6113ad565b34801561034b57600080fd5b5061035f61035a36600461317f565b6113fe565b6040805192151583526001600160a01b03909116602083015201610183565b34801561038a57600080fd5b5061039e61039936600461333e565b611455565b60405161018391906133cf565b3480156103b757600080fd5b506103c06115ba565b6040516101839190613426565b3480156103d957600080fd5b506102936103e836600461343a565b611603565b3480156103f957600080fd5b50610293610408366004612fb2565b611693565b34801561041957600080fd5b506101ac610428366004613527565b63bc197c8160e01b95945050505050565b34801561044557600080fd5b506103c07f0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b81565b34801561047957600080fd5b506102456116c5565b34801561048e57600080fd5b5061029361049d3660046135d4565b611745565b3480156104ae57600080fd5b506103126118fd565b3480156104c357600080fd5b506104cc611a6e565b604051610183919061361b565b3480156104e557600080fd5b506104ee611b06565b604051610183919061362e565b34801561050757600080fd5b5061051b610516366004612fb2565b611b18565b604051610183919061367b565b34801561053457600080fd5b506101ac61054336600461368e565b63f23a6e6160e01b95945050505050565b60006001600160e01b03198216630271189760e51b148061058557506001600160e01b03198216630a85bd0160e11b145b80610594575061059482611bf0565b92915050565b630a85bd0160e11b5b949350505050565b6000806105b7846109bd565b905060006105c58285611c25565b90506105d08161098e565b156105e75750630b135d3f60e11b91506105949050565b3360006105f2611c49565b6001600160a01b038416600090815260069190910160205260409020905061061a8183611c6d565b8061064a575061062981611c8f565b600114801561064a5750600061063f8282611c99565b6001600160a01b0316145b6106a75760405162461bcd60e51b8152602060048201526024808201527f4163636f756e743a2063616c6c6572206e6f7420617070726f7665642074617260448201526333b2ba1760e11b60648201526084015b60405180910390fd5b6106b0836110ad565b156106c057630b135d3f60e11b94505b5050505092915050565b60006106d4611c49565b6001600160a01b0384166000908152600491909101602052604090205460ff161561070157506001610594565b600061070b611c49565b6001600160a01b0385166000908152600591909101602090815260408083208151606081018352815481526001909101546001600160801b0380821694830194909452600160801b9004909216908201529150610766611c49565b6006016000866001600160a01b03166001600160a01b0316815260200190815260200160002090504282602001516001600160801b031611806107b6575081604001516001600160801b03164210155b806107c757506107c581611c8f565b155b156107d757600092505050610594565b60006107ee6107e960608701876136f6565b611ca5565b905060006107fb83611c8f565b600114801561081c575060006108118482611c99565b6001600160a01b0316145b90506324f16c0560e11b6001600160e01b03198316016108935760008061084e61084960608a018a6136f6565b611cdf565b9150915082610874576108618583611c6d565b6108745760009650505050505050610594565b855181111561088c5760009650505050505050610594565b5050610981565b635c0f12eb60e11b6001600160e01b0319831601610974576000806108c36108be60608a018a6136f6565b611d44565b5091509150826109235760005b8251811015610921576109058382815181106108ee576108ee61373c565b602002602001015187611c6d90919063ffffffff16565b610919576000975050505050505050610594565b6001016108d0565b505b60005b825181101561096c578181815181106109415761094161373c565b602002602001015187600001511015610964576000975050505050505050610594565b600101610926565b505050610981565b6000945050505050610594565b5060019695505050505050565b6000610998611c49565b6001600160a01b03909216600090815260049290920160205250604090205460ff1690565b600080826040516020016109d391815260200190565b60405160208183030381529060405280519060200120905060007f82cac545155fcbf147f2a9013809613677ac7d65498556e6d19ce43bcbf6c28482604051602001610a29929190918252602082015260400190565b604051602081830303815290604052805190602001209050610a49611d91565b60405161190160f01b60208201526022810191909152604281018290526062016040516020818303038152906040528051906020012092505050919050565b6000610a92611eb8565b610a9c8484611f21565b9050610aa782612066565b9392505050565b610ab66115ba565b6001600160a01b0316336001600160a01b03161480610ad95750610ad93361098e565b610af55760405162461bcd60e51b815260040161069e90613752565b610afd6120b3565b8481148015610b0b57508483145b610b575760405162461bcd60e51b815260206004820152601d60248201527f4163636f756e743a2077726f6e67206172726179206c656e677468732e000000604482015260640161069e565b60005b85811015610c0c57610c03878783818110610b7757610b7761373c565b9050602002016020810190610b8c9190612fb2565b868684818110610b9e57610b9e61373c565b90506020020135858585818110610bb757610bb761373c565b9050602002810190610bc991906136f6565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061219992505050565b50600101610b5a565b50505050505050565b610c1d6115ba565b6001600160a01b031663b760faf934306040518363ffffffff1660e01b8152600401610c499190613426565b6000604051808303818588803b158015610c6257600080fd5b505af1158015610c76573d6000803e3d6000fd5b5050505050565b610c8561220a565b610c8d6115ba565b6001600160a01b031663205c287883836040518363ffffffff1660e01b8152600401610cba929190613793565b600060405180830381600087803b158015610cd457600080fd5b505af1158015610ce8573d6000803e3d6000fd5b505050505050565b6000610cff6020850185612fb2565b905042610d1260e0860160c087016137c3565b6001600160801b031611158015610d415750610d35610100850160e086016137c3565b6001600160801b031642105b610d775760405162461bcd60e51b8152602060048201526007602482015266085c195c9a5bd960ca1b604482015260640161069e565b600080610d858686866113fe565b9150915081610dbf5760405162461bcd60e51b815260040161069e906020808252600490820152632173696760e01b604082015260600190565b6001610dc9611c49565b610100880135600090815260079190910160209081526040808320805460ff1916941515949094179093559091610e05919089019089016137ef565b60ff161115610e32576000610e2060408801602089016137ef565b60ff166001149050610c0c8482612248565b610e3b8361098e565b15610e705760405162461bcd60e51b815260206004820152600560248201526430b236b4b760d91b604482015260640161069e565b610e8583610e7c611c49565b6002019061231d565b50604051806060016040528087606001358152602001876080016020810190610eae91906137c3565b6001600160801b03168152602001610ecc60c0890160a08a016137c3565b6001600160801b03169052610edf611c49565b6001600160a01b03851660009081526005919091016020908152604080832084518155918401519301516001600160801b03908116600160801b02931692909217600190920191909155610f55610f34611c49565b6001600160a01b038616600090815260069190910160205260409020612332565b805190915060005b81811015610fbf57610fac838281518110610f7a57610f7a61373c565b6020026020010151610f8a611c49565b6001600160a01b0389166000908152600691909101602052604090209061233f565b50610fb8600182613820565b9050610f5d565b50610fcd6040890189613833565b9050905060005b8181101561104e5761103b610fec60408b018b613833565b83818110610ffc57610ffc61373c565b90506020020160208101906110119190612fb2565b611019611c49565b6001600160a01b0389166000908152600691909101602052604090209061231d565b50611047600182613820565b9050610fd4565b5061105888612354565b846001600160a01b0316836001600160a01b03167ff21d10c26e35863a8df291aca54181ee8c4a3bc8e00246c3f7a5a14b69d826a78a60405161109b919061390d565b60405180910390a35050505050505050565b6000806110b8611c49565b6001600160a01b038416600090815260059190910160209081526040918290208251606081018452815481526001909101546001600160801b03808216938301849052600160801b90910416928101929092529091504210801590611129575080604001516001600160801b031642105b8015610aa75750600061115e61113d611c49565b6001600160a01b038616600090815260069190910160205260409020611c8f565b119392505050565b6060600061117d611175611c49565b600201612332565b80519091506000805b8281101561120e576111b08482815181106111a3576111a361373c565b60200260200101516110ad565b156111c757816111bf816139f8565b9250506111fc565b60008482815181106111db576111db61373c565b60200260200101906001600160a01b031690816001600160a01b0316815250505b611207600182613820565b9050611186565b50806001600160401b0381111561122757611227612de6565b60405190808252806020026020018201604052801561126057816020015b61124d612d4d565b8152602001906001900390816112455790505b5093506000805b838110156113a55760006001600160a01b031685828151811061128c5761128c61373c565b60200260200101516001600160a01b0316146113935760008582815181106112b6576112b661373c565b6020026020010151905060006112ca611c49565b6001600160a01b038316600081815260059290920160209081526040928390208351606081018552815481526001909101546001600160801b0380821683850152600160801b9091041681850152835160a081019094529183529092508101611334610f34611c49565b81526020018260000151815260200182602001516001600160801b0316815260200182604001516001600160801b0316815250888580611373906139f8565b9650815181106113855761138561373c565b602002602001018190525050505b61139e600182613820565b9050611267565b505050505090565b6113b56123e9565b6113f25760405162461bcd60e51b815260206004820152600e60248201526d139bdd08185d5d1a1bdc9a5e995960921b604482015260640161069e565b6113fb81612401565b50565b60008061141461140d866124e8565b858561262c565b905061141e611c49565b6101008601356000908152600791909101602052604090205460ff1615801561144b575061144b8161098e565b9150935093915050565b6060816001600160401b0381111561146f5761146f612de6565b6040519080825280602002602001820160405280156114a257816020015b606081526020019060019003908161148d5790505b509050336000805b848110156115b157811561152957611507308787848181106114ce576114ce61373c565b90506020028101906114e091906136f6565b866040516020016114f393929190613a11565b60405160208183030381529060405261267e565b8482815181106115195761151961373c565b60200260200101819052506115a9565b61158b3087878481811061153f5761153f61373c565b905060200281019061155191906136f6565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061267e92505050565b84828151811061159d5761159d61373c565b60200260200101819052505b6001016114aa565b50505092915050565b6000806115c56126a3565b546001600160a01b0316905080156115dc57919050565b7f0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d278991505090565b61160b6115ba565b6001600160a01b0316336001600160a01b0316148061162e575061162e3361098e565b61164a5760405162461bcd60e51b815260040161069e90613752565b6116526120b3565b610c76848484848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061219992505050565b61169b61220a565b806116a46126a3565b80546001600160a01b0319166001600160a01b039290921691909117905550565b60006116cf6115ba565b604051631aab3f0d60e11b8152306004820152600060248201526001600160a01b0391909116906335567e1a90604401602060405180830381865afa15801561171c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117409190613a32565b905090565b600061174f6126c7565b5460ff169050600061175f6126c7565b54610100900460ff169050801580801561177c575060018360ff16105b8061179b575061178b306126eb565b15801561179b57508260ff166001145b6117fe5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161069e565b60016118086126c7565b805460ff191660ff92909216919091179055801561184157600161182a6126c7565b80549115156101000261ff00199092169190911790555b6118818686868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506126fa92505050565b6118896126a3565b6001018190555061189b866001612248565b8015610ce85760006118ab6126c7565b80549115156101000261ff0019909216919091179055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a1505050505050565b6060600061190c611175611c49565b8051909150806001600160401b0381111561192957611929612de6565b60405190808252806020026020018201604052801561196257816020015b61194f612d4d565b8152602001906001900390816119475790505b50925060005b81811015611a685760008382815181106119845761198461373c565b602002602001015190506000611998611c49565b6001600160a01b038316600081815260059290920160209081526040928390208351606081018552815481526001909101546001600160801b0380821683850152600160801b9091041681850152835160a081019094529183529092508101611a02610f34611c49565b81526020018260000151815260200182602001516001600160801b0316815260200182604001516001600160801b0316815250868481518110611a4757611a4761373c565b60200260200101819052505050600181611a619190613820565b9050611968565b50505090565b6060611a7861272d565b8054611a8390613a4b565b80601f0160208091040260200160405190810160405280929190818152602001828054611aaf90613a4b565b8015611afc5780601f10611ad157610100808354040283529160200191611afc565b820191906000526020600020905b815481529060010190602001808311611adf57829003601f168201915b5050505050905090565b6060611740611b13611c49565b612332565b611b20612d4d565b6000611b2a611c49565b6001600160a01b038416600081815260059290920160209081526040928390208351606081018552815481526001909101546001600160801b0380821683850152600160801b9091041681850152835160a081019094529183529092508101611bb5611b94611c49565b6001600160a01b038716600090815260069190910160205260409020612332565b81526020018260000151815260200182602001516001600160801b0316815260200182604001516001600160801b0316815250915050919050565b60006001600160e01b03198216630271189760e51b148061059457506301ffc9a760e01b6001600160e01b0319831614610594565b6000806000611c348585612751565b91509150611c4181612796565b509392505050565b7f3181e78fc1b109bc611fd2406150bf06e33faa75f71cba12c3e1fd670f2def0090565b6001600160a01b03811660009081526001830160205260408120541515610aa7565b6000610594825490565b6000610aa783836128db565b60006004821015611cc85760405162461bcd60e51b815260040161069e90613a7f565b611cd6600460008486613a9e565b610aa791613ac8565b6000806044831015611d035760405162461bcd60e51b815260040161069e90613a7f565b611d11602460048587613a9e565b810190611d1e9190612fb2565b9150611d2e604460248587613a9e565b810190611d3b9190612fcf565b90509250929050565b606080806064841015611d695760405162461bcd60e51b815260040161069e90613a7f565b611d768460048188613a9e565b810190611d839190613b77565b919790965090945092505050565b6000306001600160a01b037f000000000000000000000000ffd4505b3452dc22f8473616d50503ba9e1710ac16148015611dea57507f0000000000000000000000000000000000000000000000000000000000007a6946145b15611e1457507fbcdadf6444930a967ffda04923d78c49b3dd65df3ed39abb04a1e3eb1190553790565b50604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6020808301919091527ff0729608244859f656d32ae4cbc6b0367695d68d8e941a28f5e2d33c6d5182dd828401527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608301524660808301523060a0808401919091528351808403909101815260c0909201909252805191012090565b611ec06115ba565b6001600160a01b0316336001600160a01b031614611f1f5760405162461bcd60e51b815260206004820152601c60248201527b1858d8dbdd5b9d0e881b9bdd08199c9bdb48115b9d1c9e541bda5b9d60221b604482015260640161069e565b565b7b0ca2ba3432b932bab69029b4b3b732b21026b2b9b9b0b3b29d05199960211b6000908152601c829052603c81206000611f9f611f626101408701876136f6565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508693925050611c259050565b9050611fab81866106ca565b611fba57600192505050610594565b6000611fc4611c49565b6001600160a01b03929092166000908152600590920160209081526040808420815160608082018452825482526001909201546001600160801b0380821683870152600160801b8204908116928501929092528351928301845295825265ffffffffffff8087169483019490945292831691015260d09290921b6001600160d01b03191660a09290921b65ffffffffffff60a01b169190911795945050505050565b80156113fb57604051600090339060001990849084818181858888f193505050503d8060008114610c76576040519150601f19603f3d011682016040523d82523d6000602084013e610c76565b60405163c3c5a54760e01b81527f0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b906001600160a01b0382169063c3c5a54790612101903090600401613426565b602060405180830381865afa15801561211e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121429190613c5c565b6113fb57806001600160a01b03166383a03f8c61215d6126a3565b600101546040518263ffffffff1660e01b815260040161217f91815260200190565b600060405180830381600087803b158015610c6257600080fd5b60606000846001600160a01b031684846040516121b69190613c7e565b60006040518083038185875af1925050503d80600081146121f3576040519150601f19603f3d011682016040523d82523d6000602084013e6121f8565b606091505b509250905080611c4157815160208301fd5b6122133361098e565b611f1f5760405162461bcd60e51b815260206004820152600660248201526510b0b236b4b760d11b604482015260640161069e565b6122528282612905565b6001600160a01b037f0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b163b156123195780156122e1577f0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b6001600160a01b0316630b61e12b836122c06126a3565b600101546040518363ffffffff1660e01b8152600401610cba929190613793565b7f0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b6001600160a01b0316639387a380836122c06126a3565b5050565b6000610aa7836001600160a01b0384166129b4565b60606000610aa783612a03565b6000610aa7836001600160a01b038416612a5f565b6001600160a01b037f0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b163b156113fb576001600160a01b037f0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b16630b61e12b6123c06020840184612fb2565b6123c86126a3565b600101546040518363ffffffff1660e01b815260040161217f929190613793565b60006123f43361098e565b8061174057505030331490565b600061240b61272d565b805461241690613a4b565b80601f016020809104026020016040519081016040528092919081815260200182805461244290613a4b565b801561248f5780601f106124645761010080835404028352916020019161248f565b820191906000526020600020905b81548152906001019060200180831161247257829003601f168201915b505050505090508161249f61272d565b906124aa9082613ce7565b507fc9c7c3fe08b88b4df9d4d47ef47d2c43d55c025a0ba88ca442580ed9e7348a1681836040516124dc929190613da6565b60405180910390a15050565b60607f3fd4a1a1a267c84185e3b7eecd57c68783c0581d538b9d6e5f23e4670497c1e96125186020840184612fb2565b61252860408501602086016137ef565b6125356040860186613833565b604051602001612546929190613dd4565b60408051601f198184030181529190528051602090910120606086013561257360a08801608089016137c3565b61258360c0890160a08a016137c3565b61259360e08a0160c08b016137c3565b6125a46101008b0160e08c016137c3565b60408051602081019a909a526001600160a01b039098169789019790975260ff9095166060880152608087019390935260a08601919091526001600160801b0390811660c086015290811660e0850152908116610100848101919091529116610120830152830135610140820152610160016040516020818303038152906040529050919050565b60006105a383838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250508751602089012061267892509050612b52565b90611c25565b6060610aa78383604051806060016040528060278152602001613e7a60279139612b7f565b7f036f52c1827dab135f7fd44ca0bddde297e2f659c710e0ec53e975f22b54830090565b7f322cf19c484104d3b1a9c2982ebae869ede3fa5f6c4703ca41b9a48c76ee030090565b6001600160a01b03163b151590565b6000828260405160200161270f929190613e16565b60405160208183030381529060405280519060200120905092915050565b7f4bc804ba64359c0e35e5ed5d90ee596ecaa49a3a930ddcb1470ea0dd625da90090565b60008082516041036127875760208301516040840151606085015160001a61277b87828585612bf7565b9450945050505061278f565b506000905060025b9250929050565b60008160048111156127aa576127aa613e3a565b036127b25750565b60018160048111156127c6576127c6613e3a565b0361280e5760405162461bcd60e51b815260206004820152601860248201527745434453413a20696e76616c6964207369676e617475726560401b604482015260640161069e565b600281600481111561282257612822613e3a565b0361286f5760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e67746800604482015260640161069e565b600381600481111561288357612883613e3a565b036113fb5760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b606482015260840161069e565b60008260000182815481106128f2576128f261373c565b9060005260206000200154905092915050565b8061290e611c49565b6001600160a01b038416600090815260049190910160205260409020805460ff19169115159190911790558015612957576129518261294b611c49565b9061231d565b5061296b565b61296982612963611c49565b9061233f565b505b816001600160a01b03167f235bc17e7930760029e9f4d860a2a8089976de5b381cf8380fc11c1d88a11133826040516129a8911515815260200190565b60405180910390a25050565b60008181526001830160205260408120546129fb57508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610594565b506000610594565b606081600001805480602002602001604051908101604052809291908181526020018280548015612a5357602002820191906000526020600020905b815481526020019060010190808311612a3f575b50505050509050919050565b60008181526001830160205260408120548015612b48576000612a83600183613e50565b8554909150600090612a9790600190613e50565b9050818114612afc576000866000018281548110612ab757612ab761373c565b9060005260206000200154905080876000018481548110612ada57612ada61373c565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080612b0d57612b0d613e63565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610594565b6000915050610594565b6000610594612b5f611d91565b8360405161190160f01b8152600281019290925260228201526042902090565b6060600080856001600160a01b031685604051612b9c9190613c7e565b600060405180830381855af49150503d8060008114612bd7576040519150601f19603f3d011682016040523d82523d6000602084013e612bdc565b606091505b5091509150612bed86838387612cb1565b9695505050505050565b6000806fa2a8918ca85bafe22016d0b997e4df60600160ff1b03831115612c245750600090506003612ca8565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa158015612c78573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116612ca157600060019250925050612ca8565b9150600090505b94509492505050565b60608315612d1e578251600003612d1757612ccb856126eb565b612d175760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161069e565b50816105a3565b6105a38383815115612d335781518083602001fd5b8060405162461bcd60e51b815260040161069e919061361b565b6040518060a0016040528060006001600160a01b03168152602001606081526020016000815260200160006001600160801b0316815260200160006001600160801b031681525090565b600060208284031215612da957600080fd5b81356001600160e01b031981168114610aa757600080fd5b6001600160a01b03811681146113fb57600080fd5b8035612de181612dc1565b919050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b0381118282101715612e2457612e24612de6565b604052919050565b60006001600160401b03831115612e4557612e45612de6565b612e58601f8401601f1916602001612dfc565b9050828152838383011115612e6c57600080fd5b828260208301376000602084830101529392505050565b600082601f830112612e9457600080fd5b610aa783833560208501612e2c565b60008060008060808587031215612eb957600080fd5b8435612ec481612dc1565b93506020850135612ed481612dc1565b92506040850135915060608501356001600160401b03811115612ef657600080fd5b612f0287828801612e83565b91505092959194509250565b60008060408385031215612f2157600080fd5b8235915060208301356001600160401b03811115612f3e57600080fd5b612f4a85828601612e83565b9150509250929050565b60006101608284031215612f6757600080fd5b50919050565b60008060408385031215612f8057600080fd5b8235612f8b81612dc1565b915060208301356001600160401b03811115612fa657600080fd5b612f4a85828601612f54565b600060208284031215612fc457600080fd5b8135610aa781612dc1565b600060208284031215612fe157600080fd5b5035919050565b600080600060608486031215612ffd57600080fd5b83356001600160401b0381111561301357600080fd5b61301f86828701612f54565b9660208601359650604090950135949350505050565b60008083601f84011261304757600080fd5b5081356001600160401b0381111561305e57600080fd5b6020830191508360208260051b850101111561278f57600080fd5b6000806000806000806060878903121561309257600080fd5b86356001600160401b03808211156130a957600080fd5b6130b58a838b01613035565b909850965060208901359150808211156130ce57600080fd5b6130da8a838b01613035565b909650945060408901359150808211156130f357600080fd5b5061310089828a01613035565b979a9699509497509295939492505050565b6000806040838503121561312557600080fd5b823561313081612dc1565b946020939093013593505050565b60008083601f84011261315057600080fd5b5081356001600160401b0381111561316757600080fd5b60208301915083602082850101111561278f57600080fd5b60008060006040848603121561319457600080fd5b83356001600160401b03808211156131ab57600080fd5b9085019061012082880312156131c057600080fd5b909350602085013590808211156131d657600080fd5b506131e38682870161313e565b9497909650939450505050565b6001600160801b03169052565b80516001600160a01b03908116835260208083015160a082860181905281519086018190526000939183019290849060c08801905b8083101561325457855185168252948301946001929092019190830190613232565b50604087015160408901526060870151945061327360608901866131f0565b6080870151945061328760808901866131f0565b979650505050505050565b600060208083016020845280855180835260408601915060408160051b87010192506020870160005b828110156132e957603f198886030184526132d78583516131fd565b945092850192908501906001016132bb565b5092979650505050505050565b60006020828403121561330857600080fd5b81356001600160401b0381111561331e57600080fd5b8201601f8101841361332f57600080fd5b6105a384823560208401612e2c565b6000806020838503121561335157600080fd5b82356001600160401b0381111561336757600080fd5b61337385828601613035565b90969095509350505050565b60005b8381101561339a578181015183820152602001613382565b50506000910152565b600081518084526133bb81602086016020860161337f565b601f01601f19169290920160200192915050565b600060208083016020845280855180835260408601915060408160051b87010192506020870160005b828110156132e957603f198886030184526134148583516133a3565b945092850192908501906001016133f8565b6001600160a01b0391909116815260200190565b6000806000806060858703121561345057600080fd5b843561345b81612dc1565b93506020850135925060408501356001600160401b0381111561347d57600080fd5b6134898782880161313e565b95989497509550505050565b60006001600160401b038211156134ae576134ae612de6565b5060051b60200190565b600082601f8301126134c957600080fd5b813560206134de6134d983613495565b612dfc565b8083825260208201915060208460051b87010193508684111561350057600080fd5b602086015b8481101561351c5780358352918301918301613505565b509695505050505050565b600080600080600060a0868803121561353f57600080fd5b853561354a81612dc1565b9450602086013561355a81612dc1565b935060408601356001600160401b038082111561357657600080fd5b61358289838a016134b8565b9450606088013591508082111561359857600080fd5b6135a489838a016134b8565b935060808801359150808211156135ba57600080fd5b506135c788828901612e83565b9150509295509295909350565b6000806000604084860312156135e957600080fd5b83356135f481612dc1565b925060208401356001600160401b0381111561360f57600080fd5b6131e38682870161313e565b602081526000610aa760208301846133a3565b6020808252825182820181905260009190848201906040850190845b8181101561366f5783516001600160a01b03168352928401929184019160010161364a565b50909695505050505050565b602081526000610aa760208301846131fd565b600080600080600060a086880312156136a657600080fd5b85356136b181612dc1565b945060208601356136c181612dc1565b9350604086013592506060860135915060808601356001600160401b038111156136ea57600080fd5b6135c788828901612e83565b6000808335601e1984360301811261370d57600080fd5b8301803591506001600160401b0382111561372757600080fd5b60200191503681900382131561278f57600080fd5b634e487b7160e01b600052603260045260246000fd5b60208082526021908201527f4163636f756e743a206e6f742061646d696e206f7220456e747279506f696e746040820152601760f91b606082015260800190565b6001600160a01b03929092168252602082015260400190565b80356001600160801b0381168114612de157600080fd5b6000602082840312156137d557600080fd5b610aa7826137ac565b803560ff81168114612de157600080fd5b60006020828403121561380157600080fd5b610aa7826137de565b634e487b7160e01b600052601160045260246000fd5b808201808211156105945761059461380a565b6000808335601e1984360301811261384a57600080fd5b8301803591506001600160401b0382111561386457600080fd5b6020019150600581901b360382131561278f57600080fd5b6000808335601e1984360301811261389357600080fd5b83016020810192503590506001600160401b038111156138b257600080fd5b8060051b360382131561278f57600080fd5b8183526000602080850194508260005b858110156139025781356138e781612dc1565b6001600160a01b0316875295820195908201906001016138d4565b509495945050505050565b6020815261392e6020820161392184612dd6565b6001600160a01b03169052565b600061393c602084016137de565b60ff8116604084015250613953604084018461387c565b61012080606086015261396b610140860183856138c4565b925060608601356080860152613983608087016137ac565b915061399260a08601836131f0565b61399e60a087016137ac565b91506139ad60c08601836131f0565b6139b960c087016137ac565b91506139c860e08601836131f0565b6139d460e087016137ac565b91506101006139e5818701846131f0565b9590950135939094019290925250919050565b600060018201613a0a57613a0a61380a565b5060010190565b8284823760609190911b6001600160601b0319169101908152601401919050565b600060208284031215613a4457600080fd5b5051919050565b600181811c90821680613a5f57607f821691505b602082108103612f6757634e487b7160e01b600052602260045260246000fd5b602080825260059082015264214461746160d81b604082015260600190565b60008085851115613aae57600080fd5b83861115613abb57600080fd5b5050820193919092039150565b6001600160e01b03198135818116916004851015613af05780818660040360031b1b83161692505b505092915050565b600082601f830112613b0957600080fd5b81356020613b196134d983613495565b82815260059290921b84018101918181019086841115613b3857600080fd5b8286015b8481101561351c5780356001600160401b03811115613b5b5760008081fd5b613b698986838b0101612e83565b845250918301918301613b3c565b600080600060608486031215613b8c57600080fd5b83356001600160401b0380821115613ba357600080fd5b818601915086601f830112613bb757600080fd5b81356020613bc76134d983613495565b82815260059290921b8401810191818101908a841115613be657600080fd5b948201945b83861015613c0d578535613bfe81612dc1565b82529482019490820190613beb565b97505087013592505080821115613c2357600080fd5b613c2f878388016134b8565b93506040860135915080821115613c4557600080fd5b50613c5286828701613af8565b9150509250925092565b600060208284031215613c6e57600080fd5b81518015158114610aa757600080fd5b60008251613c9081846020870161337f565b9190910192915050565b601f821115613ce2576000816000526020600020601f850160051c81016020861015613cc35750805b601f850160051c820191505b81811015610ce857828155600101613ccf565b505050565b81516001600160401b03811115613d0057613d00612de6565b613d1481613d0e8454613a4b565b84613c9a565b602080601f831160018114613d495760008415613d315750858301515b600019600386901b1c1916600185901b178555610ce8565b600085815260208120601f198616915b82811015613d7857888601518255948401946001909101908401613d59565b5085821015613d965787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b604081526000613db960408301856133a3565b8281036020840152613dcb81856133a3565b95945050505050565b60008184825b85811015613e0b578135613ded81612dc1565b6001600160a01b031683526020928301929190910190600101613dda565b509095945050505050565b6001600160a01b03831681526040602082018190526000906105a3908301846133a3565b634e487b7160e01b600052602160045260246000fd5b818103818111156105945761059461380a565b634e487b7160e01b600052603160045260246000fdfe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220c6185c15509642c5dfb70425cc2b7d2b7fc952a3866c742e669947d2d95f6bdf64736f6c63430008170033"; +bytes constant THIRDWEB_ACCOUNT_FACTORY_BYTECODE = hex"608060405234801561001057600080fd5b50600436106101285760003560e01c806308e93d0a1461012d5780630b61e12b1461014b5780630e6254fd1461016057806311464fbe14610173578063248a9ca3146101b25780632f2ff15d146101d357806336568abe146101e657806358451f97146101f957806383a03f8c146102015780638878ed33146102145780639010d07c1461022757806391d148541461023a5780639387a3801461025d578063938e3d7b14610270578063a217fddf14610283578063a32fa5b31461028b578063a65d69d41461029e578063ac9650d8146102c5578063c3c5a547146102e5578063ca15c873146102f8578063d547741f1461030b578063d8fd8f441461031e578063e68a7c3b14610331578063e8a3d48514610344575b600080fd5b610135610359565b6040516101429190611945565b60405180910390f35b61015e6101593660046119ae565b61036a565b005b61013561016e3660046119d8565b61040b565b61019a7f000000000000000000000000ffd4505b3452dc22f8473616d50503ba9e1710ac81565b6040516001600160a01b039091168152602001610142565b6101c56101c03660046119f3565b610435565b604051908152602001610142565b61015e6101e1366004611a0c565b610453565b61015e6101f4366004611a0c565b6104fd565b6101c561055c565b61015e61020f3660046119f3565b610568565b61019a610222366004611a38565b6105b6565b61019a610235366004611aba565b610630565b61024d610248366004611a0c565b61073e565b6040519015158152602001610142565b61015e61026b3660046119ae565b610772565b61015e61027e366004611af2565b610809565b6101c5600081565b61024d610299366004611a0c565b61085a565b61019a7f0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d278981565b6102d86102d3366004611ba2565b6108bd565b6040516101429190611c66565b61024d6102f33660046119d8565b610a19565b6101c56103063660046119f3565b610a25565b61015e610319366004611a0c565b610ac2565b61019a61032c366004611a38565b610acd565b61013561033f366004611aba565b610c18565b61034c610d49565b6040516101429190611cca565b60606103656000610de1565b905090565b336103758183610dee565b61039a5760405162461bcd60e51b815260040161039190611cdd565b60405180910390fd5b6001600160a01b03831660009081526002602052604081206103bc9083610e32565b9050801561040557836001600160a01b0316826001600160a01b03167f12146497b3b826918ec47f0cac7272a09ed06b30c16c030e99ec48ff5dd60b4760405160405180910390a35b50505050565b6001600160a01b038116600090815260026020526040902060609061042f90610de1565b92915050565b600061043f610e47565b600092835260010160205250604090205490565b61047761045e610e47565b6000848152600191909101602052604090205433610e6b565b61047f610e47565b6000838152602091825260408082206001600160a01b0385168352909252205460ff16156104ef5760405162461bcd60e51b815260206004820152601d60248201527f43616e206f6e6c79206772616e7420746f206e6f6e20686f6c646572730000006044820152606401610391565b6104f98282610ef0565b5050565b336001600160a01b038216146105525760405162461bcd60e51b815260206004820152601a60248201527921b0b71037b7363c903932b737bab731b2903337b91039b2b63360311b6044820152606401610391565b6104f98282610f04565b60006103656000610f18565b336105738183610dee565b61058f5760405162461bcd60e51b815260040161039190611cdd565b61059a600082610e32565b6104f95760405162461bcd60e51b815260040161039190611d14565b6000806105f98585858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610f2292505050565b90506106257f000000000000000000000000ffd4505b3452dc22f8473616d50503ba9e1710ac82610f55565b9150505b9392505050565b60008061063b610fb5565b600085815260209190915260408120549150805b82811015610735576000610661610fb5565b60008881526020918252604080822085835260010190925220546001600160a01b0316146106d9578482036106c757610698610fb5565b600087815260209182526040808220938252600190930190915220546001600160a01b0316925061042f915050565b6106d2600183611d74565b9150610723565b6106e486600061073e565b801561071057506106f3610fb5565b600087815260209182526040808220828052600201909252205481145b1561072357610720600183611d74565b91505b61072e600182611d74565b905061064f565b50505092915050565b6000610748610e47565b6000938452602090815260408085206001600160a01b039490941685529290525090205460ff1690565b3361077d8183610dee565b6107995760405162461bcd60e51b815260040161039190611cdd565b6001600160a01b03831660009081526002602052604081206107bb9083610fbf565b9050801561040557836001600160a01b0316826001600160a01b03167f98d1ebbe00ae92a5de96a0f49742a8afa89f42363592bc2e7cfaaed68b45e7a660405160405180910390a350505050565b610811610fd4565b61084e5760405162461bcd60e51b815260206004820152600e60248201526d139bdd08185d5d1a1bdc9a5e995960921b6044820152606401610391565b61085781610fe0565b50565b6000610864610e47565b600084815260209182526040808220828052909252205460ff166108b45761088a610e47565b6000848152602091825260408082206001600160a01b0386168352909252205460ff16905061042f565b50600192915050565b6060816001600160401b038111156108d7576108d7611adc565b60405190808252806020026020018201604052801561090a57816020015b60608152602001906001900390816108f55790505b509050336000805b848110156107355781156109915761096f3087878481811061093657610936611d87565b90506020028101906109489190611d9d565b8660405160200161095b93929190611dea565b6040516020818303038152906040526110c7565b84828151811061098157610981611d87565b6020026020010181905250610a11565b6109f3308787848181106109a7576109a7611d87565b90506020028101906109b99190611d9d565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506110c792505050565b848281518110610a0557610a05611d87565b60200260200101819052505b600101610912565b600061042f81836110ec565b600080610a30610fb5565b6000848152602091909152604081205491505b81811015610a9d576000610a55610fb5565b60008681526020918252604080822085835260010190925220546001600160a01b031614610a8b57610a88600184611d74565b92505b610a96600182611d74565b9050610a43565b50610aa983600061073e565b15610abc57610ab9600183611d74565b91505b50919050565b61055261045e610e47565b6000807f000000000000000000000000ffd4505b3452dc22f8473616d50503ba9e1710ac90506000610b358686868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610f2292505050565b90506000610b438383610f55565b90506001600160a01b0381163b15610b5f579250610629915050565b610b69838361110e565b9050336001600160a01b037f0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27891614610bc257610ba6600082610e32565b610bc25760405162461bcd60e51b815260040161039190611d14565b610bce818888886111a5565b866001600160a01b0316816001600160a01b03167fac631f3001b55ea1509cf3d7e74898f85392a61a76e8149181ae1259622dabc860405160405180910390a39695505050505050565b60608183108015610c325750610c2e6000610f18565b8211155b610c8a5760405162461bcd60e51b815260206004820152602360248201527f426173654163636f756e74466163746f72793a20696e76616c696420696e646960448201526263657360e81b6064820152608401610391565b6000610c968484611e0b565b9050610ca28484611e0b565b6001600160401b03811115610cb957610cb9611adc565b604051908082528060200260200182016040528015610ce2578160200160208202803683370190505b50915060005b81811015610d4157610d05610cfd8683611d74565b60009061120d565b838281518110610d1757610d17611d87565b6001600160a01b0390921660209283029190910190910152610d3a600182611d74565b9050610ce8565b505092915050565b6060610d53611219565b8054610d5e90611e1e565b80601f0160208091040260200160405190810160405280929190818152602001828054610d8a90611e1e565b8015610dd75780601f10610dac57610100808354040283529160200191610dd7565b820191906000526020600020905b815481529060010190602001808311610dba57829003601f168201915b5050505050905090565b606060006106298361123d565b600080610e1b7f000000000000000000000000ffd4505b3452dc22f8473616d50503ba9e1710ac84610f55565b6001600160a01b0385811691161491505092915050565b6000610629836001600160a01b038416611299565b7f0a7b0f5c59907924802379ebe98cdc23e2ee7820f63d30126e10b3752010e50090565b610e73610e47565b6000838152602091825260408082206001600160a01b0385168352909252205460ff166104f957610eae816001600160a01b031660146112e8565b610eb98360206112e8565b604051602001610eca929190611e52565b60408051601f198184030181529082905262461bcd60e51b825261039191600401611cca565b610efa8282611483565b6104f982826114ec565b610f0e82826115ab565b6104f98282611614565b600061042f825490565b60008282604051602001610f37929190611ebf565b60405160208183030381529060405280519060200120905092915050565b6040513060388201526f5af43d82803e903d91602b57fd5bf3ff602482015260148101839052733d602d80600a3d3981f3363d3d373d3d3d363d738152605881018290526037600c82012060788201526055604390910120600090610629565b60006103656116a3565b6000610629836001600160a01b038416611705565b6000610365813361073e565b6000610fea611219565b8054610ff590611e1e565b80601f016020809104026020016040519081016040528092919081815260200182805461102190611e1e565b801561106e5780601f106110435761010080835404028352916020019161106e565b820191906000526020600020905b81548152906001019060200180831161105157829003601f168201915b505050505090508161107e611219565b906110899082611f34565b507fc9c7c3fe08b88b4df9d4d47ef47d2c43d55c025a0ba88ca442580ed9e7348a1681836040516110bb929190611ff3565b60405180910390a15050565b606061062983836040518060600160405280602781526020016120b9602791396117f8565b6001600160a01b03811660009081526001830160205260408120541515610629565b6000763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c176000526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760096000f590506001600160a01b03811661042f5760405162461bcd60e51b8152602060048201526017602482015276115490cc4c4d8dce8818dc99585d194c8819985a5b1959604a1b6044820152606401610391565b60405163347d5e2560e21b81526001600160a01b0385169063d1f57894906111d590869086908690600401612018565b600060405180830381600087803b1580156111ef57600080fd5b505af1158015611203573d6000803e3d6000fd5b5050505050505050565b60006106298383611870565b7f4bc804ba64359c0e35e5ed5d90ee596ecaa49a3a930ddcb1470ea0dd625da90090565b60608160000180548060200260200160405190810160405280929190818152602001828054801561128d57602002820191906000526020600020905b815481526020019060010190808311611279575b50505050509050919050565b60008181526001830160205260408120546112e05750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915561042f565b50600061042f565b606060006112f7836002612058565b611302906002611d74565b6001600160401b0381111561131957611319611adc565b6040519080825280601f01601f191660200182016040528015611343576020820181803683370190505b509050600360fc1b8160008151811061135e5761135e611d87565b60200101906001600160f81b031916908160001a905350600f60fb1b8160018151811061138d5761138d611d87565b60200101906001600160f81b031916908160001a90535060006113b1846002612058565b6113bc906001611d74565b90505b6001811115611434576f181899199a1a9b1b9c1cb0b131b232b360811b85600f16601081106113f0576113f0611d87565b1a60f81b82828151811061140657611406611d87565b60200101906001600160f81b031916908160001a90535060049490941c9361142d8161206f565b90506113bf565b5083156106295760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610391565b600161148d610e47565b6000848152602091825260408082206001600160a01b0386168084529352808220805460ff1916941515949094179093559151339285917f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d9190a45050565b60006114f6610fb5565b6000848152602091909152604090205490506001611512610fb5565b6000858152602091909152604081208054909190611531908490611d74565b90915550829050611540610fb5565b6000858152602091825260408082208583526001019092522080546001600160a01b0319166001600160a01b039290921691909117905580611580610fb5565b6000948552602090815260408086206001600160a01b03909516865260029094019052919092205550565b6115b58282610e6b565b6115bd610e47565b6000838152602091825260408082206001600160a01b0385168084529352808220805460ff191690555133929185917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b600061161e610fb5565b6000848152602091825260408082206001600160a01b03861683526002019092522054905061164b610fb5565b6000848152602091825260408082208483526001019092522080546001600160a01b031916905561167a610fb5565b6000938452602090815260408085206001600160a01b0390941685526002909301905250812055565b60008060ff196116d460017f0c4ba382c0009cf238e4c1ca1a52f51c61e6248a70bdfb34e5ed49d5578a5c0c611e0b565b6040516020016116e691815260200190565b60408051601f1981840301815291905280516020909101201692915050565b600081815260018301602052604081205480156117ee576000611729600183611e0b565b855490915060009061173d90600190611e0b565b90508181146117a257600086600001828154811061175d5761175d611d87565b906000526020600020015490508087600001848154811061178057611780611d87565b6000918252602080832090910192909255918252600188019052604090208390555b85548690806117b3576117b3612086565b60019003818190600052602060002001600090559055856001016000868152602001908152602001600020600090556001935050505061042f565b600091505061042f565b6060600080856001600160a01b031685604051611815919061209c565b600060405180830381855af49150503d8060008114611850576040519150601f19603f3d011682016040523d82523d6000602084013e611855565b606091505b50915091506118668683838761189a565b9695505050505050565b600082600001828154811061188757611887611d87565b9060005260206000200154905092915050565b60608315611909578251600003611902576001600160a01b0385163b6119025760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610391565b5081611913565b611913838361191b565b949350505050565b81511561192b5781518083602001fd5b8060405162461bcd60e51b81526004016103919190611cca565b6020808252825182820181905260009190848201906040850190845b818110156119865783516001600160a01b031683529284019291840191600101611961565b50909695505050505050565b80356001600160a01b03811681146119a957600080fd5b919050565b600080604083850312156119c157600080fd5b6119ca83611992565b946020939093013593505050565b6000602082840312156119ea57600080fd5b61062982611992565b600060208284031215611a0557600080fd5b5035919050565b60008060408385031215611a1f57600080fd5b82359150611a2f60208401611992565b90509250929050565b600080600060408486031215611a4d57600080fd5b611a5684611992565b925060208401356001600160401b0380821115611a7257600080fd5b818601915086601f830112611a8657600080fd5b813581811115611a9557600080fd5b876020828501011115611aa757600080fd5b6020830194508093505050509250925092565b60008060408385031215611acd57600080fd5b50508035926020909101359150565b634e487b7160e01b600052604160045260246000fd5b600060208284031215611b0457600080fd5b81356001600160401b0380821115611b1b57600080fd5b818401915084601f830112611b2f57600080fd5b813581811115611b4157611b41611adc565b604051601f8201601f19908116603f01168101908382118183101715611b6957611b69611adc565b81604052828152876020848701011115611b8257600080fd5b826020860160208301376000928101602001929092525095945050505050565b60008060208385031215611bb557600080fd5b82356001600160401b0380821115611bcc57600080fd5b818501915085601f830112611be057600080fd5b813581811115611bef57600080fd5b8660208260051b8501011115611c0457600080fd5b60209290920196919550909350505050565b60005b83811015611c31578181015183820152602001611c19565b50506000910152565b60008151808452611c52816020860160208601611c16565b601f01601f19169290920160200192915050565b600060208083016020845280855180835260408601915060408160051b87010192506020870160005b82811015611cbd57603f19888603018452611cab858351611c3a565b94509285019290850190600101611c8f565b5092979650505050505050565b6020815260006106296020830184611c3a565b6020808252601f908201527f4163636f756e74466163746f72793a206e6f7420616e206163636f756e742e00604082015260600190565b6020808252602a908201527f4163636f756e74466163746f72793a206163636f756e7420616c7265616479206040820152691c9959da5cdd195c995960b21b606082015260800190565b634e487b7160e01b600052601160045260246000fd5b8082018082111561042f5761042f611d5e565b634e487b7160e01b600052603260045260246000fd5b6000808335601e19843603018112611db457600080fd5b8301803591506001600160401b03821115611dce57600080fd5b602001915036819003821315611de357600080fd5b9250929050565b8284823760609190911b6001600160601b0319169101908152601401919050565b8181038181111561042f5761042f611d5e565b600181811c90821680611e3257607f821691505b602082108103610abc57634e487b7160e01b600052602260045260246000fd5b7402832b936b4b9b9b4b7b7399d1030b1b1b7bab73a1605d1b815260008351611e82816015850160208801611c16565b7001034b99036b4b9b9b4b733903937b6329607d1b6015918401918201528351611eb3816026840160208801611c16565b01602601949350505050565b6001600160a01b038316815260406020820181905260009061191390830184611c3a565b601f821115611f2f576000816000526020600020601f850160051c81016020861015611f0c5750805b601f850160051c820191505b81811015611f2b57828155600101611f18565b5050505b505050565b81516001600160401b03811115611f4d57611f4d611adc565b611f6181611f5b8454611e1e565b84611ee3565b602080601f831160018114611f965760008415611f7e5750858301515b600019600386901b1c1916600185901b178555611f2b565b600085815260208120601f198616915b82811015611fc557888601518255948401946001909101908401611fa6565b5085821015611fe35787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6040815260006120066040830185611c3a565b82810360208401526106258185611c3a565b6001600160a01b03841681526040602082018190528101829052818360608301376000818301606090810191909152601f909201601f1916010192915050565b808202811582820484141761042f5761042f611d5e565b60008161207e5761207e611d5e565b506000190190565b634e487b7160e01b600052603160045260246000fd5b600082516120ae818460208701611c16565b919091019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212208fee46949383576f28224ce9e6b6a4b07519741c4de38b0c75218e600dce91e564736f6c63430008170033"; +bytes constant THIRDWEB_ACCOUNT_IMPL_BYTECODE = hex"60806040526004361061014b5760003560e01c806301ffc9a714610157578063150b7a021461018c5780631626ba7e146101c55780631dd756c5146101e557806324d7806c14610205578063399b77da146102255780633a871cdd1461025357806347e1da2a146102735780634a58db19146102955780634d44560d1461029d5780635892e236146102bd5780637dff5a79146102dd5780638b52d723146102fd578063938e3d7b1461031f578063a9082d841461033f578063ac9650d81461037e578063b0d691fe146103ab578063b61d27f6146103cd578063b76464d5146103ed578063bc197c811461040d578063c45a015514610439578063d087d2881461046d578063d1f5789414610482578063d42f2f35146104a2578063e8a3d485146104b7578063e9523c97146104d9578063f15d424e146104fb578063f23a6e611461052857600080fd5b3661015257005b600080fd5b34801561016357600080fd5b50610177610172366004612d97565b610554565b60405190151581526020015b60405180910390f35b34801561019857600080fd5b506101ac6101a7366004612ea3565b61059a565b6040516001600160e01b03199091168152602001610183565b3480156101d157600080fd5b506101ac6101e0366004612f0e565b6105ab565b3480156101f157600080fd5b50610177610200366004612f6d565b6106ca565b34801561021157600080fd5b50610177610220366004612fb2565b61098e565b34801561023157600080fd5b50610245610240366004612fcf565b6109bd565b604051908152602001610183565b34801561025f57600080fd5b5061024561026e366004612fe8565b610a88565b34801561027f57600080fd5b5061029361028e366004613079565b610aae565b005b610293610c15565b3480156102a957600080fd5b506102936102b8366004613112565b610c7d565b3480156102c957600080fd5b506102936102d836600461317f565b610cf0565b3480156102e957600080fd5b506101776102f8366004612fb2565b6110ad565b34801561030957600080fd5b50610312611166565b6040516101839190613292565b34801561032b57600080fd5b5061029361033a3660046132f6565b6113ad565b34801561034b57600080fd5b5061035f61035a36600461317f565b6113fe565b6040805192151583526001600160a01b03909116602083015201610183565b34801561038a57600080fd5b5061039e61039936600461333e565b611455565b60405161018391906133cf565b3480156103b757600080fd5b506103c06115ba565b6040516101839190613426565b3480156103d957600080fd5b506102936103e836600461343a565b611603565b3480156103f957600080fd5b50610293610408366004612fb2565b611693565b34801561041957600080fd5b506101ac610428366004613527565b63bc197c8160e01b95945050505050565b34801561044557600080fd5b506103c07f0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b81565b34801561047957600080fd5b506102456116c5565b34801561048e57600080fd5b5061029361049d3660046135d4565b611745565b3480156104ae57600080fd5b506103126118fd565b3480156104c357600080fd5b506104cc611a6e565b604051610183919061361b565b3480156104e557600080fd5b506104ee611b06565b604051610183919061362e565b34801561050757600080fd5b5061051b610516366004612fb2565b611b18565b604051610183919061367b565b34801561053457600080fd5b506101ac61054336600461368e565b63f23a6e6160e01b95945050505050565b60006001600160e01b03198216630271189760e51b148061058557506001600160e01b03198216630a85bd0160e11b145b80610594575061059482611bf0565b92915050565b630a85bd0160e11b5b949350505050565b6000806105b7846109bd565b905060006105c58285611c25565b90506105d08161098e565b156105e75750630b135d3f60e11b91506105949050565b3360006105f2611c49565b6001600160a01b038416600090815260069190910160205260409020905061061a8183611c6d565b8061064a575061062981611c8f565b600114801561064a5750600061063f8282611c99565b6001600160a01b0316145b6106a75760405162461bcd60e51b8152602060048201526024808201527f4163636f756e743a2063616c6c6572206e6f7420617070726f7665642074617260448201526333b2ba1760e11b60648201526084015b60405180910390fd5b6106b0836110ad565b156106c057630b135d3f60e11b94505b5050505092915050565b60006106d4611c49565b6001600160a01b0384166000908152600491909101602052604090205460ff161561070157506001610594565b600061070b611c49565b6001600160a01b0385166000908152600591909101602090815260408083208151606081018352815481526001909101546001600160801b0380821694830194909452600160801b9004909216908201529150610766611c49565b6006016000866001600160a01b03166001600160a01b0316815260200190815260200160002090504282602001516001600160801b031611806107b6575081604001516001600160801b03164210155b806107c757506107c581611c8f565b155b156107d757600092505050610594565b60006107ee6107e960608701876136f6565b611ca5565b905060006107fb83611c8f565b600114801561081c575060006108118482611c99565b6001600160a01b0316145b90506324f16c0560e11b6001600160e01b03198316016108935760008061084e61084960608a018a6136f6565b611cdf565b9150915082610874576108618583611c6d565b6108745760009650505050505050610594565b855181111561088c5760009650505050505050610594565b5050610981565b635c0f12eb60e11b6001600160e01b0319831601610974576000806108c36108be60608a018a6136f6565b611d44565b5091509150826109235760005b8251811015610921576109058382815181106108ee576108ee61373c565b602002602001015187611c6d90919063ffffffff16565b610919576000975050505050505050610594565b6001016108d0565b505b60005b825181101561096c578181815181106109415761094161373c565b602002602001015187600001511015610964576000975050505050505050610594565b600101610926565b505050610981565b6000945050505050610594565b5060019695505050505050565b6000610998611c49565b6001600160a01b03909216600090815260049290920160205250604090205460ff1690565b600080826040516020016109d391815260200190565b60405160208183030381529060405280519060200120905060007f82cac545155fcbf147f2a9013809613677ac7d65498556e6d19ce43bcbf6c28482604051602001610a29929190918252602082015260400190565b604051602081830303815290604052805190602001209050610a49611d91565b60405161190160f01b60208201526022810191909152604281018290526062016040516020818303038152906040528051906020012092505050919050565b6000610a92611eb8565b610a9c8484611f21565b9050610aa782612066565b9392505050565b610ab66115ba565b6001600160a01b0316336001600160a01b03161480610ad95750610ad93361098e565b610af55760405162461bcd60e51b815260040161069e90613752565b610afd6120b3565b8481148015610b0b57508483145b610b575760405162461bcd60e51b815260206004820152601d60248201527f4163636f756e743a2077726f6e67206172726179206c656e677468732e000000604482015260640161069e565b60005b85811015610c0c57610c03878783818110610b7757610b7761373c565b9050602002016020810190610b8c9190612fb2565b868684818110610b9e57610b9e61373c565b90506020020135858585818110610bb757610bb761373c565b9050602002810190610bc991906136f6565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061219992505050565b50600101610b5a565b50505050505050565b610c1d6115ba565b6001600160a01b031663b760faf934306040518363ffffffff1660e01b8152600401610c499190613426565b6000604051808303818588803b158015610c6257600080fd5b505af1158015610c76573d6000803e3d6000fd5b5050505050565b610c8561220a565b610c8d6115ba565b6001600160a01b031663205c287883836040518363ffffffff1660e01b8152600401610cba929190613793565b600060405180830381600087803b158015610cd457600080fd5b505af1158015610ce8573d6000803e3d6000fd5b505050505050565b6000610cff6020850185612fb2565b905042610d1260e0860160c087016137c3565b6001600160801b031611158015610d415750610d35610100850160e086016137c3565b6001600160801b031642105b610d775760405162461bcd60e51b8152602060048201526007602482015266085c195c9a5bd960ca1b604482015260640161069e565b600080610d858686866113fe565b9150915081610dbf5760405162461bcd60e51b815260040161069e906020808252600490820152632173696760e01b604082015260600190565b6001610dc9611c49565b610100880135600090815260079190910160209081526040808320805460ff1916941515949094179093559091610e05919089019089016137ef565b60ff161115610e32576000610e2060408801602089016137ef565b60ff166001149050610c0c8482612248565b610e3b8361098e565b15610e705760405162461bcd60e51b815260206004820152600560248201526430b236b4b760d91b604482015260640161069e565b610e8583610e7c611c49565b6002019061231d565b50604051806060016040528087606001358152602001876080016020810190610eae91906137c3565b6001600160801b03168152602001610ecc60c0890160a08a016137c3565b6001600160801b03169052610edf611c49565b6001600160a01b03851660009081526005919091016020908152604080832084518155918401519301516001600160801b03908116600160801b02931692909217600190920191909155610f55610f34611c49565b6001600160a01b038616600090815260069190910160205260409020612332565b805190915060005b81811015610fbf57610fac838281518110610f7a57610f7a61373c565b6020026020010151610f8a611c49565b6001600160a01b0389166000908152600691909101602052604090209061233f565b50610fb8600182613820565b9050610f5d565b50610fcd6040890189613833565b9050905060005b8181101561104e5761103b610fec60408b018b613833565b83818110610ffc57610ffc61373c565b90506020020160208101906110119190612fb2565b611019611c49565b6001600160a01b0389166000908152600691909101602052604090209061231d565b50611047600182613820565b9050610fd4565b5061105888612354565b846001600160a01b0316836001600160a01b03167ff21d10c26e35863a8df291aca54181ee8c4a3bc8e00246c3f7a5a14b69d826a78a60405161109b919061390d565b60405180910390a35050505050505050565b6000806110b8611c49565b6001600160a01b038416600090815260059190910160209081526040918290208251606081018452815481526001909101546001600160801b03808216938301849052600160801b90910416928101929092529091504210801590611129575080604001516001600160801b031642105b8015610aa75750600061115e61113d611c49565b6001600160a01b038616600090815260069190910160205260409020611c8f565b119392505050565b6060600061117d611175611c49565b600201612332565b80519091506000805b8281101561120e576111b08482815181106111a3576111a361373c565b60200260200101516110ad565b156111c757816111bf816139f8565b9250506111fc565b60008482815181106111db576111db61373c565b60200260200101906001600160a01b031690816001600160a01b0316815250505b611207600182613820565b9050611186565b50806001600160401b0381111561122757611227612de6565b60405190808252806020026020018201604052801561126057816020015b61124d612d4d565b8152602001906001900390816112455790505b5093506000805b838110156113a55760006001600160a01b031685828151811061128c5761128c61373c565b60200260200101516001600160a01b0316146113935760008582815181106112b6576112b661373c565b6020026020010151905060006112ca611c49565b6001600160a01b038316600081815260059290920160209081526040928390208351606081018552815481526001909101546001600160801b0380821683850152600160801b9091041681850152835160a081019094529183529092508101611334610f34611c49565b81526020018260000151815260200182602001516001600160801b0316815260200182604001516001600160801b0316815250888580611373906139f8565b9650815181106113855761138561373c565b602002602001018190525050505b61139e600182613820565b9050611267565b505050505090565b6113b56123e9565b6113f25760405162461bcd60e51b815260206004820152600e60248201526d139bdd08185d5d1a1bdc9a5e995960921b604482015260640161069e565b6113fb81612401565b50565b60008061141461140d866124e8565b858561262c565b905061141e611c49565b6101008601356000908152600791909101602052604090205460ff1615801561144b575061144b8161098e565b9150935093915050565b6060816001600160401b0381111561146f5761146f612de6565b6040519080825280602002602001820160405280156114a257816020015b606081526020019060019003908161148d5790505b509050336000805b848110156115b157811561152957611507308787848181106114ce576114ce61373c565b90506020028101906114e091906136f6565b866040516020016114f393929190613a11565b60405160208183030381529060405261267e565b8482815181106115195761151961373c565b60200260200101819052506115a9565b61158b3087878481811061153f5761153f61373c565b905060200281019061155191906136f6565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061267e92505050565b84828151811061159d5761159d61373c565b60200260200101819052505b6001016114aa565b50505092915050565b6000806115c56126a3565b546001600160a01b0316905080156115dc57919050565b7f0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d278991505090565b61160b6115ba565b6001600160a01b0316336001600160a01b0316148061162e575061162e3361098e565b61164a5760405162461bcd60e51b815260040161069e90613752565b6116526120b3565b610c76848484848080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061219992505050565b61169b61220a565b806116a46126a3565b80546001600160a01b0319166001600160a01b039290921691909117905550565b60006116cf6115ba565b604051631aab3f0d60e11b8152306004820152600060248201526001600160a01b0391909116906335567e1a90604401602060405180830381865afa15801561171c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117409190613a32565b905090565b600061174f6126c7565b5460ff169050600061175f6126c7565b54610100900460ff169050801580801561177c575060018360ff16105b8061179b575061178b306126eb565b15801561179b57508260ff166001145b6117fe5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161069e565b60016118086126c7565b805460ff191660ff92909216919091179055801561184157600161182a6126c7565b80549115156101000261ff00199092169190911790555b6118818686868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506126fa92505050565b6118896126a3565b6001018190555061189b866001612248565b8015610ce85760006118ab6126c7565b80549115156101000261ff0019909216919091179055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a1505050505050565b6060600061190c611175611c49565b8051909150806001600160401b0381111561192957611929612de6565b60405190808252806020026020018201604052801561196257816020015b61194f612d4d565b8152602001906001900390816119475790505b50925060005b81811015611a685760008382815181106119845761198461373c565b602002602001015190506000611998611c49565b6001600160a01b038316600081815260059290920160209081526040928390208351606081018552815481526001909101546001600160801b0380821683850152600160801b9091041681850152835160a081019094529183529092508101611a02610f34611c49565b81526020018260000151815260200182602001516001600160801b0316815260200182604001516001600160801b0316815250868481518110611a4757611a4761373c565b60200260200101819052505050600181611a619190613820565b9050611968565b50505090565b6060611a7861272d565b8054611a8390613a4b565b80601f0160208091040260200160405190810160405280929190818152602001828054611aaf90613a4b565b8015611afc5780601f10611ad157610100808354040283529160200191611afc565b820191906000526020600020905b815481529060010190602001808311611adf57829003601f168201915b5050505050905090565b6060611740611b13611c49565b612332565b611b20612d4d565b6000611b2a611c49565b6001600160a01b038416600081815260059290920160209081526040928390208351606081018552815481526001909101546001600160801b0380821683850152600160801b9091041681850152835160a081019094529183529092508101611bb5611b94611c49565b6001600160a01b038716600090815260069190910160205260409020612332565b81526020018260000151815260200182602001516001600160801b0316815260200182604001516001600160801b0316815250915050919050565b60006001600160e01b03198216630271189760e51b148061059457506301ffc9a760e01b6001600160e01b0319831614610594565b6000806000611c348585612751565b91509150611c4181612796565b509392505050565b7f3181e78fc1b109bc611fd2406150bf06e33faa75f71cba12c3e1fd670f2def0090565b6001600160a01b03811660009081526001830160205260408120541515610aa7565b6000610594825490565b6000610aa783836128db565b60006004821015611cc85760405162461bcd60e51b815260040161069e90613a7f565b611cd6600460008486613a9e565b610aa791613ac8565b6000806044831015611d035760405162461bcd60e51b815260040161069e90613a7f565b611d11602460048587613a9e565b810190611d1e9190612fb2565b9150611d2e604460248587613a9e565b810190611d3b9190612fcf565b90509250929050565b606080806064841015611d695760405162461bcd60e51b815260040161069e90613a7f565b611d768460048188613a9e565b810190611d839190613b77565b919790965090945092505050565b6000306001600160a01b037f000000000000000000000000ffd4505b3452dc22f8473616d50503ba9e1710ac16148015611dea57507f0000000000000000000000000000000000000000000000000000000000007a6946145b15611e1457507fbcdadf6444930a967ffda04923d78c49b3dd65df3ed39abb04a1e3eb1190553790565b50604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6020808301919091527ff0729608244859f656d32ae4cbc6b0367695d68d8e941a28f5e2d33c6d5182dd828401527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608301524660808301523060a0808401919091528351808403909101815260c0909201909252805191012090565b611ec06115ba565b6001600160a01b0316336001600160a01b031614611f1f5760405162461bcd60e51b815260206004820152601c60248201527b1858d8dbdd5b9d0e881b9bdd08199c9bdb48115b9d1c9e541bda5b9d60221b604482015260640161069e565b565b7b0ca2ba3432b932bab69029b4b3b732b21026b2b9b9b0b3b29d05199960211b6000908152601c829052603c81206000611f9f611f626101408701876136f6565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508693925050611c259050565b9050611fab81866106ca565b611fba57600192505050610594565b6000611fc4611c49565b6001600160a01b03929092166000908152600590920160209081526040808420815160608082018452825482526001909201546001600160801b0380821683870152600160801b8204908116928501929092528351928301845295825265ffffffffffff8087169483019490945292831691015260d09290921b6001600160d01b03191660a09290921b65ffffffffffff60a01b169190911795945050505050565b80156113fb57604051600090339060001990849084818181858888f193505050503d8060008114610c76576040519150601f19603f3d011682016040523d82523d6000602084013e610c76565b60405163c3c5a54760e01b81527f0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b906001600160a01b0382169063c3c5a54790612101903090600401613426565b602060405180830381865afa15801561211e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121429190613c5c565b6113fb57806001600160a01b03166383a03f8c61215d6126a3565b600101546040518263ffffffff1660e01b815260040161217f91815260200190565b600060405180830381600087803b158015610c6257600080fd5b60606000846001600160a01b031684846040516121b69190613c7e565b60006040518083038185875af1925050503d80600081146121f3576040519150601f19603f3d011682016040523d82523d6000602084013e6121f8565b606091505b509250905080611c4157815160208301fd5b6122133361098e565b611f1f5760405162461bcd60e51b815260206004820152600660248201526510b0b236b4b760d11b604482015260640161069e565b6122528282612905565b6001600160a01b037f0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b163b156123195780156122e1577f0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b6001600160a01b0316630b61e12b836122c06126a3565b600101546040518363ffffffff1660e01b8152600401610cba929190613793565b7f0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b6001600160a01b0316639387a380836122c06126a3565b5050565b6000610aa7836001600160a01b0384166129b4565b60606000610aa783612a03565b6000610aa7836001600160a01b038416612a5f565b6001600160a01b037f0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b163b156113fb576001600160a01b037f0000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b16630b61e12b6123c06020840184612fb2565b6123c86126a3565b600101546040518363ffffffff1660e01b815260040161217f929190613793565b60006123f43361098e565b8061174057505030331490565b600061240b61272d565b805461241690613a4b565b80601f016020809104026020016040519081016040528092919081815260200182805461244290613a4b565b801561248f5780601f106124645761010080835404028352916020019161248f565b820191906000526020600020905b81548152906001019060200180831161247257829003601f168201915b505050505090508161249f61272d565b906124aa9082613ce7565b507fc9c7c3fe08b88b4df9d4d47ef47d2c43d55c025a0ba88ca442580ed9e7348a1681836040516124dc929190613da6565b60405180910390a15050565b60607f3fd4a1a1a267c84185e3b7eecd57c68783c0581d538b9d6e5f23e4670497c1e96125186020840184612fb2565b61252860408501602086016137ef565b6125356040860186613833565b604051602001612546929190613dd4565b60408051601f198184030181529190528051602090910120606086013561257360a08801608089016137c3565b61258360c0890160a08a016137c3565b61259360e08a0160c08b016137c3565b6125a46101008b0160e08c016137c3565b60408051602081019a909a526001600160a01b039098169789019790975260ff9095166060880152608087019390935260a08601919091526001600160801b0390811660c086015290811660e0850152908116610100848101919091529116610120830152830135610140820152610160016040516020818303038152906040529050919050565b60006105a383838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250508751602089012061267892509050612b52565b90611c25565b6060610aa78383604051806060016040528060278152602001613e7a60279139612b7f565b7f036f52c1827dab135f7fd44ca0bddde297e2f659c710e0ec53e975f22b54830090565b7f322cf19c484104d3b1a9c2982ebae869ede3fa5f6c4703ca41b9a48c76ee030090565b6001600160a01b03163b151590565b6000828260405160200161270f929190613e16565b60405160208183030381529060405280519060200120905092915050565b7f4bc804ba64359c0e35e5ed5d90ee596ecaa49a3a930ddcb1470ea0dd625da90090565b60008082516041036127875760208301516040840151606085015160001a61277b87828585612bf7565b9450945050505061278f565b506000905060025b9250929050565b60008160048111156127aa576127aa613e3a565b036127b25750565b60018160048111156127c6576127c6613e3a565b0361280e5760405162461bcd60e51b815260206004820152601860248201527745434453413a20696e76616c6964207369676e617475726560401b604482015260640161069e565b600281600481111561282257612822613e3a565b0361286f5760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e67746800604482015260640161069e565b600381600481111561288357612883613e3a565b036113fb5760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b606482015260840161069e565b60008260000182815481106128f2576128f261373c565b9060005260206000200154905092915050565b8061290e611c49565b6001600160a01b038416600090815260049190910160205260409020805460ff19169115159190911790558015612957576129518261294b611c49565b9061231d565b5061296b565b61296982612963611c49565b9061233f565b505b816001600160a01b03167f235bc17e7930760029e9f4d860a2a8089976de5b381cf8380fc11c1d88a11133826040516129a8911515815260200190565b60405180910390a25050565b60008181526001830160205260408120546129fb57508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610594565b506000610594565b606081600001805480602002602001604051908101604052809291908181526020018280548015612a5357602002820191906000526020600020905b815481526020019060010190808311612a3f575b50505050509050919050565b60008181526001830160205260408120548015612b48576000612a83600183613e50565b8554909150600090612a9790600190613e50565b9050818114612afc576000866000018281548110612ab757612ab761373c565b9060005260206000200154905080876000018481548110612ada57612ada61373c565b6000918252602080832090910192909255918252600188019052604090208390555b8554869080612b0d57612b0d613e63565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610594565b6000915050610594565b6000610594612b5f611d91565b8360405161190160f01b8152600281019290925260228201526042902090565b6060600080856001600160a01b031685604051612b9c9190613c7e565b600060405180830381855af49150503d8060008114612bd7576040519150601f19603f3d011682016040523d82523d6000602084013e612bdc565b606091505b5091509150612bed86838387612cb1565b9695505050505050565b6000806fa2a8918ca85bafe22016d0b997e4df60600160ff1b03831115612c245750600090506003612ca8565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa158015612c78573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116612ca157600060019250925050612ca8565b9150600090505b94509492505050565b60608315612d1e578251600003612d1757612ccb856126eb565b612d175760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161069e565b50816105a3565b6105a38383815115612d335781518083602001fd5b8060405162461bcd60e51b815260040161069e919061361b565b6040518060a0016040528060006001600160a01b03168152602001606081526020016000815260200160006001600160801b0316815260200160006001600160801b031681525090565b600060208284031215612da957600080fd5b81356001600160e01b031981168114610aa757600080fd5b6001600160a01b03811681146113fb57600080fd5b8035612de181612dc1565b919050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b0381118282101715612e2457612e24612de6565b604052919050565b60006001600160401b03831115612e4557612e45612de6565b612e58601f8401601f1916602001612dfc565b9050828152838383011115612e6c57600080fd5b828260208301376000602084830101529392505050565b600082601f830112612e9457600080fd5b610aa783833560208501612e2c565b60008060008060808587031215612eb957600080fd5b8435612ec481612dc1565b93506020850135612ed481612dc1565b92506040850135915060608501356001600160401b03811115612ef657600080fd5b612f0287828801612e83565b91505092959194509250565b60008060408385031215612f2157600080fd5b8235915060208301356001600160401b03811115612f3e57600080fd5b612f4a85828601612e83565b9150509250929050565b60006101608284031215612f6757600080fd5b50919050565b60008060408385031215612f8057600080fd5b8235612f8b81612dc1565b915060208301356001600160401b03811115612fa657600080fd5b612f4a85828601612f54565b600060208284031215612fc457600080fd5b8135610aa781612dc1565b600060208284031215612fe157600080fd5b5035919050565b600080600060608486031215612ffd57600080fd5b83356001600160401b0381111561301357600080fd5b61301f86828701612f54565b9660208601359650604090950135949350505050565b60008083601f84011261304757600080fd5b5081356001600160401b0381111561305e57600080fd5b6020830191508360208260051b850101111561278f57600080fd5b6000806000806000806060878903121561309257600080fd5b86356001600160401b03808211156130a957600080fd5b6130b58a838b01613035565b909850965060208901359150808211156130ce57600080fd5b6130da8a838b01613035565b909650945060408901359150808211156130f357600080fd5b5061310089828a01613035565b979a9699509497509295939492505050565b6000806040838503121561312557600080fd5b823561313081612dc1565b946020939093013593505050565b60008083601f84011261315057600080fd5b5081356001600160401b0381111561316757600080fd5b60208301915083602082850101111561278f57600080fd5b60008060006040848603121561319457600080fd5b83356001600160401b03808211156131ab57600080fd5b9085019061012082880312156131c057600080fd5b909350602085013590808211156131d657600080fd5b506131e38682870161313e565b9497909650939450505050565b6001600160801b03169052565b80516001600160a01b03908116835260208083015160a082860181905281519086018190526000939183019290849060c08801905b8083101561325457855185168252948301946001929092019190830190613232565b50604087015160408901526060870151945061327360608901866131f0565b6080870151945061328760808901866131f0565b979650505050505050565b600060208083016020845280855180835260408601915060408160051b87010192506020870160005b828110156132e957603f198886030184526132d78583516131fd565b945092850192908501906001016132bb565b5092979650505050505050565b60006020828403121561330857600080fd5b81356001600160401b0381111561331e57600080fd5b8201601f8101841361332f57600080fd5b6105a384823560208401612e2c565b6000806020838503121561335157600080fd5b82356001600160401b0381111561336757600080fd5b61337385828601613035565b90969095509350505050565b60005b8381101561339a578181015183820152602001613382565b50506000910152565b600081518084526133bb81602086016020860161337f565b601f01601f19169290920160200192915050565b600060208083016020845280855180835260408601915060408160051b87010192506020870160005b828110156132e957603f198886030184526134148583516133a3565b945092850192908501906001016133f8565b6001600160a01b0391909116815260200190565b6000806000806060858703121561345057600080fd5b843561345b81612dc1565b93506020850135925060408501356001600160401b0381111561347d57600080fd5b6134898782880161313e565b95989497509550505050565b60006001600160401b038211156134ae576134ae612de6565b5060051b60200190565b600082601f8301126134c957600080fd5b813560206134de6134d983613495565b612dfc565b8083825260208201915060208460051b87010193508684111561350057600080fd5b602086015b8481101561351c5780358352918301918301613505565b509695505050505050565b600080600080600060a0868803121561353f57600080fd5b853561354a81612dc1565b9450602086013561355a81612dc1565b935060408601356001600160401b038082111561357657600080fd5b61358289838a016134b8565b9450606088013591508082111561359857600080fd5b6135a489838a016134b8565b935060808801359150808211156135ba57600080fd5b506135c788828901612e83565b9150509295509295909350565b6000806000604084860312156135e957600080fd5b83356135f481612dc1565b925060208401356001600160401b0381111561360f57600080fd5b6131e38682870161313e565b602081526000610aa760208301846133a3565b6020808252825182820181905260009190848201906040850190845b8181101561366f5783516001600160a01b03168352928401929184019160010161364a565b50909695505050505050565b602081526000610aa760208301846131fd565b600080600080600060a086880312156136a657600080fd5b85356136b181612dc1565b945060208601356136c181612dc1565b9350604086013592506060860135915060808601356001600160401b038111156136ea57600080fd5b6135c788828901612e83565b6000808335601e1984360301811261370d57600080fd5b8301803591506001600160401b0382111561372757600080fd5b60200191503681900382131561278f57600080fd5b634e487b7160e01b600052603260045260246000fd5b60208082526021908201527f4163636f756e743a206e6f742061646d696e206f7220456e747279506f696e746040820152601760f91b606082015260800190565b6001600160a01b03929092168252602082015260400190565b80356001600160801b0381168114612de157600080fd5b6000602082840312156137d557600080fd5b610aa7826137ac565b803560ff81168114612de157600080fd5b60006020828403121561380157600080fd5b610aa7826137de565b634e487b7160e01b600052601160045260246000fd5b808201808211156105945761059461380a565b6000808335601e1984360301811261384a57600080fd5b8301803591506001600160401b0382111561386457600080fd5b6020019150600581901b360382131561278f57600080fd5b6000808335601e1984360301811261389357600080fd5b83016020810192503590506001600160401b038111156138b257600080fd5b8060051b360382131561278f57600080fd5b8183526000602080850194508260005b858110156139025781356138e781612dc1565b6001600160a01b0316875295820195908201906001016138d4565b509495945050505050565b6020815261392e6020820161392184612dd6565b6001600160a01b03169052565b600061393c602084016137de565b60ff8116604084015250613953604084018461387c565b61012080606086015261396b610140860183856138c4565b925060608601356080860152613983608087016137ac565b915061399260a08601836131f0565b61399e60a087016137ac565b91506139ad60c08601836131f0565b6139b960c087016137ac565b91506139c860e08601836131f0565b6139d460e087016137ac565b91506101006139e5818701846131f0565b9590950135939094019290925250919050565b600060018201613a0a57613a0a61380a565b5060010190565b8284823760609190911b6001600160601b0319169101908152601401919050565b600060208284031215613a4457600080fd5b5051919050565b600181811c90821680613a5f57607f821691505b602082108103612f6757634e487b7160e01b600052602260045260246000fd5b602080825260059082015264214461746160d81b604082015260600190565b60008085851115613aae57600080fd5b83861115613abb57600080fd5b5050820193919092039150565b6001600160e01b03198135818116916004851015613af05780818660040360031b1b83161692505b505092915050565b600082601f830112613b0957600080fd5b81356020613b196134d983613495565b82815260059290921b84018101918181019086841115613b3857600080fd5b8286015b8481101561351c5780356001600160401b03811115613b5b5760008081fd5b613b698986838b0101612e83565b845250918301918301613b3c565b600080600060608486031215613b8c57600080fd5b83356001600160401b0380821115613ba357600080fd5b818601915086601f830112613bb757600080fd5b81356020613bc76134d983613495565b82815260059290921b8401810191818101908a841115613be657600080fd5b948201945b83861015613c0d578535613bfe81612dc1565b82529482019490820190613beb565b97505087013592505080821115613c2357600080fd5b613c2f878388016134b8565b93506040860135915080821115613c4557600080fd5b50613c5286828701613af8565b9150509250925092565b600060208284031215613c6e57600080fd5b81518015158114610aa757600080fd5b60008251613c9081846020870161337f565b9190910192915050565b601f821115613ce2576000816000526020600020601f850160051c81016020861015613cc35750805b601f850160051c820191505b81811015610ce857828155600101613ccf565b505050565b81516001600160401b03811115613d0057613d00612de6565b613d1481613d0e8454613a4b565b84613c9a565b602080601f831160018114613d495760008415613d315750858301515b600019600386901b1c1916600185901b178555610ce8565b600085815260208120601f198616915b82811015613d7857888601518255948401946001909101908401613d59565b5085821015613d965787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b604081526000613db960408301856133a3565b8281036020840152613dcb81856133a3565b95945050505050565b60008184825b85811015613e0b578135613ded81612dc1565b6001600160a01b031683526020928301929190910190600101613dda565b509095945050505050565b6001600160a01b03831681526040602082018190526000906105a3908301846133a3565b634e487b7160e01b600052602160045260246000fd5b818103818111156105945761059461380a565b634e487b7160e01b600052603160045260246000fdfe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220247c9feadcfb4aa67bba286fdc86b80cc167fce1383f2afbc218bf965fb6bc3264736f6c63430008170033";