diff --git a/tools/erc-repository-indexer/erc-contract-indexer/package-lock.json b/tools/erc-repository-indexer/erc-contract-indexer/package-lock.json index 1b04f1243..176119809 100644 --- a/tools/erc-repository-indexer/erc-contract-indexer/package-lock.json +++ b/tools/erc-repository-indexer/erc-contract-indexer/package-lock.json @@ -9,7 +9,8 @@ "version": "0.1.0", "license": "Apache-2.0", "dependencies": { - "dotenv": "^16.4.5" + "dotenv": "^16.4.5", + "sevm": "^0.7.3" }, "devDependencies": { "@types/jest": "^29.5.14", @@ -1179,6 +1180,15 @@ "node": ">=0.4.0" } }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -1199,7 +1209,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -1209,7 +1218,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -1564,7 +1572,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -1597,7 +1604,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -1610,7 +1616,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/concat-map": { @@ -1796,9 +1801,20 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -1813,7 +1829,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2016,7 +2031,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -2200,7 +2214,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2945,6 +2958,12 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/js-sha3": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.9.3.tgz", + "integrity": "sha512-BcJPCQeLg6WjEx3FE591wVAevlli8lxsxm9/FzV4HXkV49TmBH38Yvrpce6fjbADGMKFrBMGTqrVz3qPIZ88Gg==", + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3447,7 +3466,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3514,6 +3532,24 @@ "semver": "bin/semver.js" } }, + "node_modules/sevm": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/sevm/-/sevm-0.7.3.tgz", + "integrity": "sha512-19z9OwlHw2ZZy0v2PDLALzfBhl47sTNtWJRPFN6Uqut+PJesXmfR1OwVbfMW8Il4X6Kr/lDTdr2Vq4g3QNxnpA==", + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "env-paths": "^3.0.0", + "js-sha3": "^0.9.3", + "yargs": "^17.7.2" + }, + "bin": { + "sevm": "bin/sevm.mjs" + }, + "engines": { + "node": ">=16.6.0" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3620,7 +3656,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -3635,7 +3670,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -3982,7 +4016,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -4021,7 +4054,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -4038,7 +4070,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -4057,7 +4088,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" diff --git a/tools/erc-repository-indexer/erc-contract-indexer/package.json b/tools/erc-repository-indexer/erc-contract-indexer/package.json index 227659c60..342f8e119 100644 --- a/tools/erc-repository-indexer/erc-contract-indexer/package.json +++ b/tools/erc-repository-indexer/erc-contract-indexer/package.json @@ -19,6 +19,7 @@ "ts-jest": "^29.2.5" }, "dependencies": { - "dotenv": "^16.4.5" + "dotenv": "^16.4.5", + "sevm": "^0.7.3" } } diff --git a/tools/erc-repository-indexer/erc-contract-indexer/src/schemas/ERCRegistrySchemas.ts b/tools/erc-repository-indexer/erc-contract-indexer/src/schemas/ERCRegistrySchemas.ts new file mode 100644 index 000000000..6712986c5 --- /dev/null +++ b/tools/erc-repository-indexer/erc-contract-indexer/src/schemas/ERCRegistrySchemas.ts @@ -0,0 +1,24 @@ +/*- + * + * Hedera Smart Contracts + * + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +export interface ERCOutputInterface { + address: string; + contractId: string | null; +} diff --git a/tools/erc-repository-indexer/erc-contract-indexer/src/services/byteCodeAnalyzer.ts b/tools/erc-repository-indexer/erc-contract-indexer/src/services/byteCodeAnalyzer.ts new file mode 100644 index 000000000..51591bc5e --- /dev/null +++ b/tools/erc-repository-indexer/erc-contract-indexer/src/services/byteCodeAnalyzer.ts @@ -0,0 +1,111 @@ +/*- + * + * Hedera Smart Contracts + * + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { Contract } from 'sevm'; +import { ContractScannerService } from './contractScanner'; +import { MirrorNodeContract } from '../schemas/MirrorNodeSchemas'; +import { ERCOutputInterface } from '../schemas/ERCRegistrySchemas'; + +export enum ERCID { + ERC20 = 'ERC20', + ERC721 = 'ERC721', +} + +export class ByteCodeAnalyzer { + /** + * Analyzes bytecode, detects and categorizes contracts into ERC20 and ERC721 types based on their bytecode. + * @param {ContractScannerService} contractScannerService - The service used to fetch contract bytecode. + * @param {MirrorNodeContract[]} contractObject - An array of contract objects to categorize. + * @returns {Promise<{erc20Contracts: ERCOutputInterface[], erc721Contracts: ERCOutputInterface[]}>} An object containing arrays of categorized ERC20 and ERC721 contracts. + * @throws {Error} If there's an error while analyzing contract bytecode. + */ + async categorizeERCContracts( + contractScannerService: ContractScannerService, + contractObject: MirrorNodeContract[] + ): Promise<{ + erc20Contracts: ERCOutputInterface[]; + erc721Contracts: ERCOutputInterface[]; + }> { + const erc20Contracts: ERCOutputInterface[] = []; + const erc721Contracts: ERCOutputInterface[] = []; + + try { + const contractResponses = await Promise.all( + contractObject.map(({ contract_id }) => + contract_id + ? contractScannerService.fetchContractByteCode(contract_id) + : null + ) + ); + + contractResponses.forEach((contract) => { + if ( + !contract || + !contract.bytecode || + !contract.contract_id || + !contract.evm_address || + !contract.runtime_bytecode + ) { + console.warn('Skipping contract due to missing data:', { + contractId: contract?.contract_id, + hasBytecode: !!contract?.bytecode, + hasContractId: !!contract?.contract_id, + hasEvmAddress: !!contract?.evm_address, + hasRuntimeBytecode: !!contract?.runtime_bytecode, + }); + return; + } + const contractBytecode = + contract.runtime_bytecode === '0x' + ? contract.bytecode + : contract.runtime_bytecode; + + console.log(`Analyzing contract: contractId=${contract.contract_id}`); + + const sevmContract = new Contract(contractBytecode); + const ercOutput: ERCOutputInterface = { + address: contract.evm_address, + contractId: contract.contract_id, + }; + + if (sevmContract.isERC(ERCID.ERC20)) { + console.log( + `New ERC contract detected: contractId=${contract.contract_id}, ercID: ${ERCID.ERC20}` + ); + erc20Contracts.push(ercOutput); + + // TODO: Make calls to MN to retrieve name, symbol, decimals, totalSuply, etc. + } + if (sevmContract.isERC(ERCID.ERC721)) { + console.log( + `New ERC contract detected: contractId=${contract.contract_id}, ercID: ${ERCID.ERC721}` + ); + erc721Contracts.push(ercOutput); + + // TODO: Make calls to MN to retrieve name, symbol, etc. + } + }); + } catch (error) { + console.error('Error while analyzing contract bytecode:', error); + } + + return { erc20Contracts, erc721Contracts }; + } +} diff --git a/tools/erc-repository-indexer/erc-contract-indexer/tests/services/byteCodeAnalyzer.test.ts b/tools/erc-repository-indexer/erc-contract-indexer/tests/services/byteCodeAnalyzer.test.ts new file mode 100644 index 000000000..79d7f2bbf --- /dev/null +++ b/tools/erc-repository-indexer/erc-contract-indexer/tests/services/byteCodeAnalyzer.test.ts @@ -0,0 +1,103 @@ +/*- + * + * Hedera Smart Contracts + * + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { ByteCodeAnalyzer } from '../../src/services/byteCodeAnalyzer'; +import constants from '../utils/constants'; +import { ContractScannerService } from '../../src/services/contractScanner'; +import { MirrorNodeContract } from '../../src/schemas/MirrorNodeSchemas'; +import { jest } from '@jest/globals'; + +describe('ByteCodeAnalyzer', () => { + let byteCodeAnalyzer: ByteCodeAnalyzer; + let contractScannerService: ContractScannerService; + const mockContracts: MirrorNodeContract[] = constants.MOCK_MN_CONTRACTS; + + beforeEach(() => { + byteCodeAnalyzer = new ByteCodeAnalyzer(); + contractScannerService = new ContractScannerService(); + }); + + describe('categorizeERCContracts', () => { + it('should categorize contracts into ERC20 and ERC721', async () => { + // Mock the fetchContractByteCode method to return specific bytecode + jest + .spyOn(contractScannerService, 'fetchContractByteCode') + .mockImplementation(async (contractId) => { + if (contractId === '0.0.1013') { + return { + ...mockContracts[0], + bytecode: constants.ERC_20_BYTECODE_EXAMPLE, + runtime_bytecode: constants.ERC_20_BYTECODE_EXAMPLE, + }; + } else if (contractId === '0.0.1014') { + return { + ...mockContracts[1], + bytecode: constants.ERC_721_BYTECODE_EXAMPLE, + runtime_bytecode: constants.ERC_721_BYTECODE_EXAMPLE, + }; + } + return null; + }); + + const result = await byteCodeAnalyzer.categorizeERCContracts( + contractScannerService, + mockContracts + ); + + expect(result.erc20Contracts).toHaveLength(1); + expect(result.erc721Contracts).toHaveLength(1); + expect(result.erc20Contracts[0]).toEqual({ + address: mockContracts[0].evm_address, + contractId: mockContracts[0].contract_id, + }); + expect(result.erc721Contracts[0]).toEqual({ + address: mockContracts[1].evm_address, + contractId: mockContracts[1].contract_id, + }); + }); + + it('should skip contracts with missing data', async () => { + // Mock the fetchContractByteCode method to return null + jest + .spyOn(contractScannerService, 'fetchContractByteCode') + .mockResolvedValue(null); + const result = await byteCodeAnalyzer.categorizeERCContracts( + contractScannerService, + mockContracts + ); + expect(result.erc20Contracts).toHaveLength(0); + expect(result.erc721Contracts).toHaveLength(0); + }); + + it('should handle errors gracefully', async () => { + jest + .spyOn(contractScannerService, 'fetchContractByteCode') + .mockImplementation(async () => { + throw new Error('Fetch error'); + }); + const result = await byteCodeAnalyzer.categorizeERCContracts( + contractScannerService, + mockContracts + ); + expect(result.erc20Contracts).toHaveLength(0); + expect(result.erc721Contracts).toHaveLength(0); + }); + }); +}); diff --git a/tools/erc-repository-indexer/erc-contract-indexer/tests/utils/constants.ts b/tools/erc-repository-indexer/erc-contract-indexer/tests/utils/constants.ts index 05718ec87..6a29cc394 100644 --- a/tools/erc-repository-indexer/erc-contract-indexer/tests/utils/constants.ts +++ b/tools/erc-repository-indexer/erc-contract-indexer/tests/utils/constants.ts @@ -57,4 +57,8 @@ export default { timestamp: {}, }, ], + ERC_20_BYTECODE_EXAMPLE: + '0x608060405234801561001057600080fd5b50600436106100f55760003560e01c806340c10f19116100975780639dc29fac116100665780639dc29fac146101ee578063a457c2d714610201578063a9059cbb14610214578063dd62ed3e1461022757600080fd5b806340c10f191461019757806356189cb4146101aa57806370a08231146101bd57806395d89b41146101e657600080fd5b8063222f5be0116100d3578063222f5be01461014d57806323b872dd14610162578063313ce56714610175578063395093511461018457600080fd5b806306fdde03146100fa578063095ea7b31461011857806318160ddd1461013b575b600080fd5b61010261023a565b60405161010f91906109ba565b60405180910390f35b61012b610126366004610a2b565b6102cc565b604051901515815260200161010f565b6002545b60405190815260200161010f565b61016061015b366004610a55565b6102e4565b005b61012b610170366004610a55565b6102f4565b6040516012815260200161010f565b61012b610192366004610a2b565b610318565b6101606101a5366004610a2b565b61033a565b6101606101b8366004610a55565b610348565b61013f6101cb366004610a91565b6001600160a01b031660009081526020819052604090205490565b610102610353565b6101606101fc366004610a2b565b610362565b61012b61020f366004610a2b565b61036c565b61012b610222366004610a2b565b6103ec565b61013f610235366004610ab3565b6103fa565b60606003805461024990610ae6565b80601f016020809104026020016040519081016040528092919081815260200182805461027590610ae6565b80156102c25780601f10610297576101008083540402835291602001916102c2565b820191906000526020600020905b8154815290600101906020018083116102a557829003601f168201915b5050505050905090565b6000336102da818585610425565b5060019392505050565b6102ef838383610549565b505050565b600033610302858285610719565b61030d858585610549565b506001949350505050565b6000336102da81858561032b83836103fa565b6103359190610b36565b610425565b610344828261078d565b5050565b6102ef838383610425565b60606004805461024990610ae6565b610344828261086c565b6000338161037a82866103fa565b9050838110156103df5760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084015b60405180910390fd5b61030d8286868403610425565b6000336102da818585610549565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6001600160a01b0383166104875760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016103d6565b6001600160a01b0382166104e85760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016103d6565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b6001600160a01b0383166105ad5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016103d6565b6001600160a01b03821661060f5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016103d6565b6001600160a01b038316600090815260208190526040902054818110156106875760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b60648201526084016103d6565b6001600160a01b038085166000908152602081905260408082208585039055918516815290812080548492906106be908490610b36565b92505081905550826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161070a91815260200190565b60405180910390a35b50505050565b600061072584846103fa565b9050600019811461071357818110156107805760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016103d6565b6107138484848403610425565b6001600160a01b0382166107e35760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016103d6565b80600260008282546107f59190610b36565b90915550506001600160a01b03821660009081526020819052604081208054839290610822908490610b36565b90915550506040518181526001600160a01b038316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a35050565b6001600160a01b0382166108cc5760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b60648201526084016103d6565b6001600160a01b038216600090815260208190526040902054818110156109405760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b60648201526084016103d6565b6001600160a01b038316600090815260208190526040812083830390556002805484929061096f908490610b4e565b90915550506040518281526000906001600160a01b038516907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a3505050565b600060208083528351808285015260005b818110156109e7578581018301518582016040015282016109cb565b818111156109f9576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b0381168114610a2657600080fd5b919050565b60008060408385031215610a3e57600080fd5b610a4783610a0f565b946020939093013593505050565b600080600060608486031215610a6a57600080fd5b610a7384610a0f565b9250610a8160208501610a0f565b9150604084013590509250925092565b600060208284031215610aa357600080fd5b610aac82610a0f565b9392505050565b60008060408385031215610ac657600080fd5b610acf83610a0f565b9150610add60208401610a0f565b90509250929050565b600181811c90821680610afa57607f821691505b602082108103610b1a57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b60008219821115610b4957610b49610b20565b500190565b600082821015610b6057610b60610b20565b50039056fea264697066735822122062e84cb8f44c4c035bb08b344b04b097859a13109c006676ce804cd4dee3465b64736f6c634300080d0033', + ERC_721_BYTECODE_EXAMPLE: + '0x608060405234801561000f575f80fd5b50600436106100e5575f3560e01c80636352211e11610088578063a22cb46511610063578063a22cb465146101db578063b88d4fde146101ee578063c87b56dd14610201578063e985e9c514610214575f80fd5b80636352211e1461019f57806370a08231146101b257806395d89b41146101d3575f80fd5b8063095ea7b3116100c3578063095ea7b31461015157806323b872dd1461016657806340c10f191461017957806342842e0e1461018c575f80fd5b806301ffc9a7146100e957806306fdde0314610111578063081812fc14610126575b5f80fd5b6100fc6100f7366004610c22565b61024f565b60405190151581526020015b60405180910390f35b6101196102a0565b6040516101089190610c8a565b610139610134366004610c9c565b61032f565b6040516001600160a01b039091168152602001610108565b61016461015f366004610cce565b610356565b005b610164610174366004610cf6565b610365565b610164610187366004610cce565b6103f3565b61016461019a366004610cf6565b6103fd565b6101396101ad366004610c9c565b61041c565b6101c56101c0366004610d2f565b610426565b604051908152602001610108565b61011961046b565b6101646101e9366004610d48565b61047a565b6101646101fc366004610d95565b610485565b61011961020f366004610c9c565b61049c565b6100fc610222366004610e6a565b6001600160a01b039182165f90815260056020908152604080832093909416825291909152205460ff1690565b5f6001600160e01b031982166380ac58cd60e01b148061027f57506001600160e01b03198216635b5e139f60e01b145b8061029a57506301ffc9a760e01b6001600160e01b03198316145b92915050565b60605f80546102ae90610e9b565b80601f01602080910402602001604051908101604052809291908181526020018280546102da90610e9b565b80156103255780601f106102fc57610100808354040283529160200191610325565b820191905f5260205f20905b81548152906001019060200180831161030857829003601f168201915b5050505050905090565b5f6103398261050d565b505f828152600460205260409020546001600160a01b031661029a565b610361828233610545565b5050565b6001600160a01b03821661039357604051633250574960e11b81525f60048201526024015b60405180910390fd5b5f61039f838333610552565b9050836001600160a01b0316816001600160a01b0316146103ed576040516364283d7b60e01b81526001600160a01b038086166004830152602482018490528216604482015260640161038a565b50505050565b6103618282610651565b61041783838360405180602001604052805f815250610485565b505050565b5f61029a8261050d565b5f6001600160a01b038216610450576040516322718ad960e21b81525f600482015260240161038a565b506001600160a01b03165f9081526003602052604090205490565b6060600180546102ae90610e9b565b6103613383836106b2565b610490848484610365565b6103ed84848484610750565b60606104a78261050d565b505f6104bd60408051602081019091525f815290565b90505f8151116104db5760405180602001604052805f815250610506565b806104e584610876565b6040516020016104f6929190610ed3565b6040516020818303038152906040525b9392505050565b5f818152600260205260408120546001600160a01b03168061029a57604051637e27328960e01b81526004810184905260240161038a565b6104178383836001610913565b5f828152600260205260408120546001600160a01b039081169083161561057e5761057e818486610a42565b6001600160a01b038116156105b8576105995f855f80610913565b6001600160a01b0381165f90815260036020526040902080545f190190555b6001600160a01b038516156105e6576001600160a01b0385165f908152600360205260409020805460010190555b5f84815260026020526040808220805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0389811691821790925591518793918516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4949350505050565b6001600160a01b03821661067a57604051633250574960e11b81525f600482015260240161038a565b5f61068683835f610552565b90506001600160a01b03811615610417576040516339e3563760e11b81525f600482015260240161038a565b6001600160a01b0382166106e457604051630b61174360e31b81526001600160a01b038316600482015260240161038a565b6001600160a01b038381165f81815260056020908152604080832094871680845294825291829020805460ff191686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b6001600160a01b0383163b156103ed57604051630a85bd0160e11b81526001600160a01b0384169063150b7a0290610792903390889087908790600401610f01565b6020604051808303815f875af19250505080156107cc575060408051601f3d908101601f191682019092526107c991810190610f3c565b60015b610833573d8080156107f9576040519150601f19603f3d011682016040523d82523d5f602084013e6107fe565b606091505b5080515f0361082b57604051633250574960e11b81526001600160a01b038516600482015260240161038a565b805181602001fd5b6001600160e01b03198116630a85bd0160e11b1461086f57604051633250574960e11b81526001600160a01b038516600482015260240161038a565b5050505050565b60605f61088283610aa6565b60010190505f8167ffffffffffffffff8111156108a1576108a1610d81565b6040519080825280601f01601f1916602001820160405280156108cb576020820181803683370190505b5090508181016020015b5f19017f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a85049450846108d557509392505050565b808061092757506001600160a01b03821615155b15610a06575f6109368461050d565b90506001600160a01b038316158015906109625750826001600160a01b0316816001600160a01b031614155b801561099357506001600160a01b038082165f9081526005602090815260408083209387168352929052205460ff16155b156109bc5760405163a9fbf51f60e01b81526001600160a01b038416600482015260240161038a565b8115610a045783856001600160a01b0316826001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45b505b50505f908152600460205260409020805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0392909216919091179055565b610a4d838383610b87565b610417576001600160a01b038316610a7b57604051637e27328960e01b81526004810182905260240161038a565b60405163177e802f60e01b81526001600160a01b03831660048201526024810182905260440161038a565b5f807a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008310610aee577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000830492506040015b6d04ee2d6d415b85acef81000000008310610b1a576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc100008310610b3857662386f26fc10000830492506010015b6305f5e1008310610b50576305f5e100830492506008015b6127108310610b6457612710830492506004015b60648310610b76576064830492506002015b600a831061029a5760010192915050565b5f6001600160a01b03831615801590610c025750826001600160a01b0316846001600160a01b03161480610bdf57506001600160a01b038085165f9081526005602090815260408083209387168352929052205460ff165b80610c0257505f828152600460205260409020546001600160a01b038481169116145b949350505050565b6001600160e01b031981168114610c1f575f80fd5b50565b5f60208284031215610c32575f80fd5b813561050681610c0a565b5f5b83811015610c57578181015183820152602001610c3f565b50505f910152565b5f8151808452610c76816020860160208601610c3d565b601f01601f19169290920160200192915050565b602081525f6105066020830184610c5f565b5f60208284031215610cac575f80fd5b5035919050565b80356001600160a01b0381168114610cc9575f80fd5b919050565b5f8060408385031215610cdf575f80fd5b610ce883610cb3565b946020939093013593505050565b5f805f60608486031215610d08575f80fd5b610d1184610cb3565b9250610d1f60208501610cb3565b9150604084013590509250925092565b5f60208284031215610d3f575f80fd5b61050682610cb3565b5f8060408385031215610d59575f80fd5b610d6283610cb3565b915060208301358015158114610d76575f80fd5b809150509250929050565b634e487b7160e01b5f52604160045260245ffd5b5f805f8060808587031215610da8575f80fd5b610db185610cb3565b9350610dbf60208601610cb3565b925060408501359150606085013567ffffffffffffffff80821115610de2575f80fd5b818701915087601f830112610df5575f80fd5b813581811115610e0757610e07610d81565b604051601f8201601f19908116603f01168101908382118183101715610e2f57610e2f610d81565b816040528281528a6020848701011115610e47575f80fd5b826020860160208301375f60208483010152809550505050505092959194509250565b5f8060408385031215610e7b575f80fd5b610e8483610cb3565b9150610e9260208401610cb3565b90509250929050565b600181811c90821680610eaf57607f821691505b602082108103610ecd57634e487b7160e01b5f52602260045260245ffd5b50919050565b5f8351610ee4818460208801610c3d565b835190830190610ef8818360208801610c3d565b01949350505050565b5f6001600160a01b03808716835280861660208401525083604083015260806060830152610f326080830184610c5f565b9695505050505050565b5f60208284031215610f4c575f80fd5b815161050681610c0a56fea26469706673582212201c3f43711bdbae92c86c5f31bc94cb0310652d089b0efba65e34230f0d67962d64736f6c63430008180033', };