├── .github └── workflows │ └── release.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── DEVELOPMENT.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── NOTICE ├── Pipfile ├── README.md ├── ethtx ├── .gitignore ├── __init__.py ├── decoders │ ├── __init__.py │ ├── abi │ │ ├── __init__.py │ │ ├── abc.py │ │ ├── balances.py │ │ ├── calls.py │ │ ├── decoder.py │ │ ├── events.py │ │ ├── helpers │ │ │ ├── __init__.py │ │ │ └── utils.py │ │ └── transfers.py │ ├── decoder_service.py │ ├── decoders │ │ ├── __init__.py │ │ ├── errors.py │ │ ├── parameters.py │ │ └── semantics.py │ └── semantic │ │ ├── __init__.py │ │ ├── abc.py │ │ ├── balances.py │ │ ├── calls.py │ │ ├── decoder.py │ │ ├── events.py │ │ ├── helpers │ │ ├── __init__.py │ │ └── utils.py │ │ ├── metadata.py │ │ └── transfers.py ├── ethtx.py ├── exceptions.py ├── models │ ├── __init__.py │ ├── _types.py │ ├── base_model.py │ ├── decoded_model.py │ ├── objects_model.py │ ├── semantics_model.py │ └── w3_model.py ├── providers │ ├── __init__.py │ ├── ens_provider.py │ ├── etherscan │ │ ├── __init__.py │ │ ├── client.py │ │ ├── contracts.py │ │ └── etherscan_provider.py │ ├── node │ │ ├── __init__.py │ │ ├── connection_base.py │ │ └── pool.py │ ├── semantic_providers │ │ ├── __init__.py │ │ ├── base.py │ │ ├── const.py │ │ ├── database.py │ │ └── repository.py │ ├── signature_provider.py │ ├── static │ │ └── tracer.js │ └── web3_provider.py ├── semantics │ ├── __init__.py │ ├── base.py │ ├── protocols │ │ ├── __init__.py │ │ ├── anonymous.py │ │ ├── maker │ │ │ ├── __init__.py │ │ │ ├── cdp_manager.py │ │ │ ├── dai.py │ │ │ ├── dai_join.py │ │ │ ├── ds_proxy.py │ │ │ ├── gem_join.py │ │ │ ├── jug.py │ │ │ └── vat.py │ │ └── wrappers │ │ │ ├── __init__.py │ │ │ └── weth.py │ ├── protocols_router.py │ ├── rollups │ │ ├── __init__.py │ │ └── aztec.py │ ├── router.py │ ├── solidity │ │ ├── __init__.py │ │ └── precompiles.py │ ├── standards │ │ ├── __init__.py │ │ ├── eip1969.py │ │ ├── erc20.py │ │ └── erc721.py │ └── utilities │ │ ├── __init__.py │ │ └── functions.py └── utils │ ├── __init__.py │ ├── attr_dict.py │ ├── cache_tools.py │ ├── measurable.py │ └── validators.py ├── scripts └── get_last_changelog_entry.py ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── conftest.py ├── decode_test.py ├── mocks ├── __init__.py └── web3provider.py ├── model_test.py ├── models ├── __init__.py ├── decoded_model_test.py ├── mock.py ├── objects_model_test.py ├── semantics_model_test.py └── w3_model_test.py ├── providers ├── __init__.py ├── node │ ├── __init__.py │ ├── connection_base_test.py │ └── pool_test.py └── semantic_providers │ ├── __init__.py │ └── semantics_database_test.py ├── test_decimal_format.py └── validator_test.py /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*.*.*' 7 | 8 | jobs: 9 | Release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | 16 | - name: Setup Python 17 | uses: actions/setup-python@v1 18 | with: 19 | python-version: 3.9 20 | 21 | - name: Install dependencies with pipenv 22 | run: | 23 | pip install pipenv 24 | pipenv install --deploy --dev 25 | 26 | - name: Get tag 27 | id: tag 28 | run: echo ::set-output name=tag::${GITHUB_REF#refs/tags/} 29 | 30 | - name: Get Changelog 31 | id: get_changelog 32 | run: | 33 | LOG=$(make get_last_changelog_entry | tail -n +2) 34 | echo "LOG<> $GITHUB_ENV 35 | echo "$LOG" >> $GITHUB_ENV 36 | echo "EOF" >> $GITHUB_ENV 37 | 38 | - name: Build project 39 | run: make build 40 | 41 | - name: Create Release 42 | uses: ncipollo/release-action@v1 43 | with: 44 | artifacts: "dist/*" 45 | body: ${{env.LOG}} 46 | token: ${{ secrets.GITHUB_TOKEN }} 47 | draft: false 48 | prerelease: steps.check-version.outputs.prerelease == 'true' 49 | 50 | - name: Publish distribution 📦 to PyPI 51 | if: startsWith(github.ref, 'refs/tags') 52 | uses: pypa/gh-action-pypi-publish@master 53 | with: 54 | password: ${{ secrets.PYPI_PASSWORD }} 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Ignore pipenvs lock file 2 | Pipfile.lock 3 | 4 | ### Python template 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | .idea 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | cover/ 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | .pybuilder/ 71 | target/ 72 | 73 | # IPython 74 | profile_default/ 75 | ipython_config.py 76 | 77 | # pyenv 78 | # For a library or package, you might want to ignore these files since the code is 79 | # intended to run in multiple environments; otherwise, check them in: 80 | # .python-version 81 | 82 | # pipenv 83 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 84 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 85 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 86 | # install all needed dependencies. 87 | #Pipfile.lock 88 | 89 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 90 | __pypackages__/ 91 | 92 | # Environments 93 | .env 94 | .venv 95 | env/ 96 | venv/ 97 | ENV/ 98 | env.bak/ 99 | venv.bak/ 100 | 101 | # mypy 102 | .mypy_cache/ 103 | .dmypy.json 104 | dmypy.json 105 | 106 | # Pyre type checker 107 | .pyre/ 108 | 109 | # pytype static type analyzer 110 | .pytype/ 111 | 112 | # Cython debug symbols 113 | cython_debug/ 114 | 115 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: 23.3.0 4 | hooks: 5 | - id: black 6 | language_version: python3.9 7 | name: ethtx:black 8 | alias: ethtx-black 9 | 10 | - repo: https://github.com/pre-commit/pre-commit-hooks 11 | rev: v4.4.0 12 | hooks: 13 | - id: trailing-whitespace 14 | - id: check-ast 15 | - id: check-docstring-first 16 | - id: check-merge-conflict 17 | 18 | - repo: local 19 | hooks: 20 | - id: pytest 21 | name: pytest 22 | language: system 23 | entry: make test 24 | pass_filenames: false 25 | always_run: true 26 | 27 | # - repo: https://gitlab.com/pycqa/flake8 28 | # rev: 3.9.2 29 | # hooks: 30 | # - id: flake8 31 | # language_version: python3.8 32 | # name: Backend:flake8 33 | # alias: backend-flake8 34 | # args: [--config=.flake8] 35 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/DEVELOPMENT.md -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include CHANGELOG.md 3 | include LICENSE 4 | include Pipfile 5 | include ethtx/providers/static/tracer.js 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | help: 4 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 5 | 6 | test: 7 | PYTHONPATH=ethtx pipenv run python -m pytest tests 8 | 9 | test-all: 10 | PYTHONPATH=ethtx pipenv run python -m pytest . 11 | 12 | setup: 13 | pipenv install --dev 14 | pipenv run pre-commit install 15 | 16 | build: clean 17 | pipenv run python3 setup.py sdist bdist_wheel 18 | 19 | clean: 20 | rm -rf ./dist 21 | 22 | get_last_changelog_entry: 23 | @pipenv run python3 scripts/get_last_changelog_entry.py 24 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | 5 | The product contains trademarks and other branding elements of Token Flow Insights SA which are 6 | not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 7 | the trademark and/or other branding elements. 8 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | toml = "*" 8 | pymongo = ">=3.10.0,<4.0" 9 | dnspython = ">=2.0.0" 10 | mongoengine = "<0.27.0" 11 | mongomock = "*" 12 | web3 = ">=6.0.0,<7.0.0" 13 | pydantic = ">=1.8.0" 14 | python-dotenv = "*" 15 | requests = "*" 16 | 17 | [dev-packages] 18 | black = "*" 19 | pytest = ">=6.0.0" 20 | pytest-cov ="*" 21 | pytest-mock ="*" 22 | pre-commit = "*" 23 | 24 | [requires] 25 | python_version = "3.9" 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

EthTx - Ethereum transactions decoder

3 |

4 | 5 |

6 | 7 | Python 8 | 9 | 10 | Black 11 | 12 | 13 | OpenSource 14 | 15 | 16 | Apache 17 | 18 | 19 | EthTxPyPi 20 | 21 |

22 | 23 | ## Introduction 24 | 25 | Source Code: [https://github.com/ethtx/ethtx](https://github.com/ethtx/ethtx) 26 | 27 | ## Installation 28 | 29 | ```shell 30 | pip install ethtx 31 | ``` 32 | 33 | ## Requirements 34 | 35 | The package needs a few external resources, defined in `EthTxConfig` object: 36 | 37 | 1. **Erigon/Geth node** - required to have access to the raw Ethereum data; it must be a full archive node with 38 | the `debug` option ON 39 | 2. **Etherscan API key** - required to get the source code and ABI for smart contracts used in transaction 40 | 3. (Optional) **MongoDB database** - required to store smart contracts' ABI and semantics used in the decoding process. 41 | If you don't want to setup permanent database, you can enter `mongomock://localhost/ethtx`, then in-memory mongo will be 42 | set up that discards all data with every run. 43 | 44 | ## Getting started 45 | 46 | ```python 47 | from ethtx import EthTx, EthTxConfig 48 | from ethtx.models.decoded_model import DecodedTransaction 49 | 50 | ethtx_config = EthTxConfig( 51 | mongo_connection_string="mongomock://localhost/ethtx", ##MongoDB connection string, 52 | etherscan_api_key="", ##Etherscan API key, 53 | web3nodes={ 54 | "mainnet": { 55 | "hook": "_Geth_archive_node_URL_", # multiple nodes supported, separate them with comma 56 | "poa": _POA_chain_indicator_ # represented by bool value 57 | } 58 | }, 59 | default_chain="mainnet", 60 | etherscan_urls={"mainnet": "https://api.etherscan.io/api", }, 61 | ) 62 | 63 | ethtx = EthTx.initialize(ethtx_config) 64 | decoded_transaction: DecodedTransaction = ethtx.decoders.decode_transaction( 65 | '0x50051e0a6f216ab9484c2080001c7e12d5138250acee1f4b7c725b8fb6bb922d') 66 | ``` 67 | 68 | ## Features 69 | 70 | EthTx most important functions: 71 | 72 | 1. Raw node data access: 73 | 74 | ```python 75 | web3provider = ethtx.providers.web3provider 76 | 77 | from ethtx.models.w3_model import W3Transaction, W3Block, W3Receipt, W3CallTree 78 | 79 | # read raw transaction data directly from the node 80 | w3transaction: W3Transaction = web3provider.get_transaction( 81 | '0x50051e0a6f216ab9484c2080001c7e12d5138250acee1f4b7c725b8fb6bb922d') 82 | w3block: W3Block = web3provider.get_block(w3transaction.blockNumber) 83 | w3receipt: W3Receipt = web3provider.get_receipt(w3transaction.hash.hex()) 84 | w3calls: W3CallTree = web3provider.get_calls(w3transaction.hash.hex()) 85 | ``` 86 | 87 | 2. ABI decoding: 88 | 89 | ```python 90 | from ethtx.models.decoded_model import ( 91 | DecodedTransfer, 92 | DecodedBalance, 93 | DecodedEvent, DecodedCall, 94 | ) 95 | from ethtx.models.objects_model import Transaction, Event, Block, Call 96 | 97 | # read the raw transaction from the node 98 | transaction = Transaction.from_raw( 99 | w3transaction=w3transaction, w3receipt=w3receipt, w3calltree=w3calls 100 | ) 101 | 102 | # get proxies used in the transaction 103 | proxies = ethtx.decoders.get_proxies(transaction.root_call, "mainnet") 104 | 105 | block: Block = Block.from_raw( 106 | w3block=web3provider.get_block(transaction.metadata.block_number), 107 | chain_id="mainnet", 108 | ) 109 | 110 | # decode transaction components 111 | abi_decoded_events: List[Event] = ethtx.decoders.abi_decoder.decode_events( 112 | transaction.events, block.metadata, transaction.metadata 113 | ) 114 | abi_decoded_calls: DecodedCall = ethtx.decoders.abi_decoder.decode_calls( 115 | transaction.root_call, block.metadata, transaction.metadata, proxies 116 | ) 117 | abi_decoded_transfers: List[ 118 | DecodedTransfer 119 | ] = ethtx.decoders.abi_decoder.decode_transfers(abi_decoded_calls, abi_decoded_events) 120 | abi_decoded_balances: List[DecodedBalance] = ethtx.decoders.abi_decoder.decode_balances( 121 | abi_decoded_transfers 122 | ) 123 | 124 | # decode a single event 125 | raw_event: Event = transaction.events[3] 126 | abi_decoded_event: DecodedEvent = ethtx.decoders.abi_decoder.decode_event( 127 | raw_event, block.metadata, transaction.metadata 128 | ) 129 | 130 | # decode a single call 131 | raw_call: Call = transaction.root_call.subcalls[0] 132 | abi_decoded_call: DecodedCall = ethtx.decoders.abi_decoder.decode_call( 133 | raw_call, block.metadata, transaction.metadata, proxies 134 | ) 135 | ``` 136 | 137 | 3. Semantic decoding: 138 | 139 | ```python 140 | from ethtx.models.decoded_model import DecodedTransactionMetadata 141 | 142 | # semantically decode transaction components 143 | decoded_metadata: DecodedTransactionMetadata = ( 144 | ethtx.decoders.semantic_decoder.decode_metadata( 145 | block.metadata, transaction.metadata, "mainnet" 146 | ) 147 | ) 148 | decoded_events: List[DecodedEvent] = ethtx.decoders.semantic_decoder.decode_events( 149 | abi_decoded_events, decoded_metadata, proxies 150 | ) 151 | 152 | decoded_calls: Call = ethtx.decoders.semantic_decoder.decode_calls( 153 | abi_decoded_calls, decoded_metadata, proxies 154 | ) 155 | decoded_transfers: List[ 156 | DecodedTransfer 157 | ] = ethtx.decoders.semantic_decoder.decode_transfers( 158 | abi_decoded_transfers, decoded_metadata 159 | ) 160 | decoded_balances: List[ 161 | DecodedBalance 162 | ] = ethtx.decoders.semantic_decoder.decode_balances( 163 | abi_decoded_balances, decoded_metadata 164 | ) 165 | 166 | # semantically decode a single event 167 | decoded_event: DecodedEvent = ethtx.decoders.semantic_decoder.decode_event( 168 | abi_decoded_events[0], decoded_metadata, proxies 169 | ) 170 | # semantically decode a single call 171 | decoded_call: Call = ethtx.decoders.semantic_decoder.decode_call( 172 | abi_decoded_calls.subcalls[0], decoded_metadata, proxies 173 | ) 174 | ``` 175 | -------------------------------------------------------------------------------- /ethtx/.gitignore: -------------------------------------------------------------------------------- 1 | ### Python template 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | .idea 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | cover/ 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | .pybuilder/ 68 | target/ 69 | 70 | # IPython 71 | profile_default/ 72 | ipython_config.py 73 | 74 | # pyenv 75 | # For a library or package, you might want to ignore these files since the code is 76 | # intended to run in multiple environments; otherwise, check them in: 77 | # .python-version 78 | 79 | # pipenv 80 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 81 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 82 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 83 | # install all needed dependencies. 84 | #Pipfile.lock 85 | 86 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 87 | __pypackages__/ 88 | 89 | # Environments 90 | .env 91 | .venv 92 | env/ 93 | venv/ 94 | ENV/ 95 | env.bak/ 96 | venv.bak/ 97 | 98 | # mypy 99 | .mypy_cache/ 100 | .dmypy.json 101 | dmypy.json 102 | 103 | # Pyre type checker 104 | .pyre/ 105 | 106 | # pytype static type analyzer 107 | .pytype/ 108 | 109 | # Cython debug symbols 110 | cython_debug/ 111 | 112 | -------------------------------------------------------------------------------- /ethtx/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from .ethtx import EthTx, EthTxConfig 18 | -------------------------------------------------------------------------------- /ethtx/decoders/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/ethtx/decoders/__init__.py -------------------------------------------------------------------------------- /ethtx/decoders/abi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/ethtx/decoders/abi/__init__.py -------------------------------------------------------------------------------- /ethtx/decoders/abi/abc.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from abc import ABC, abstractmethod 18 | from typing import Optional, Any, List, Dict 19 | 20 | from ethtx.models.decoded_model import DecodedCall, DecodedTransfer, Proxy 21 | from ethtx.models.objects_model import ( 22 | Block, 23 | Transaction, 24 | Call, 25 | Event, 26 | TransactionMetadata, 27 | BlockMetadata, 28 | ) 29 | from ethtx.providers.semantic_providers import SemanticsRepository 30 | 31 | 32 | class ABIBasic: 33 | def __init__(self, repository: SemanticsRepository, chain_id: str): 34 | self._default_chain = chain_id 35 | self._repository: SemanticsRepository = repository 36 | 37 | 38 | class ABISubmoduleAbc(ABC, ABIBasic): 39 | @abstractmethod 40 | def decode(self, *args, **kwargs) -> Any: 41 | ... 42 | 43 | 44 | class IABIDecoder(ABC, ABIBasic): 45 | def __init__( 46 | self, 47 | repository: SemanticsRepository, 48 | chain_id: str, 49 | strict: Optional[bool] = False, 50 | ): 51 | super().__init__(repository, chain_id) 52 | self.strict = strict 53 | 54 | @abstractmethod 55 | def decode_transaction( 56 | self, block: Block, transaction: Transaction, proxies: Dict[str, Proxy] 57 | ): 58 | ... 59 | 60 | @abstractmethod 61 | def decode_calls( 62 | self, 63 | call: Call, 64 | block: BlockMetadata, 65 | transaction: TransactionMetadata, 66 | proxies: Dict[str, Proxy], 67 | ) -> ABISubmoduleAbc.decode: 68 | ... 69 | 70 | @abstractmethod 71 | def decode_events( 72 | self, 73 | events: [Event], 74 | block: BlockMetadata, 75 | transaction: TransactionMetadata, 76 | proxies: Dict[str, Proxy], 77 | ) -> ABISubmoduleAbc.decode: 78 | ... 79 | 80 | @abstractmethod 81 | def decode_transfers( 82 | self, call: DecodedCall, events: [Event], proxies: Dict[str, Proxy] 83 | ) -> ABISubmoduleAbc.decode: 84 | ... 85 | 86 | @abstractmethod 87 | def decode_balances( 88 | self, transfers: List[DecodedTransfer] 89 | ) -> ABISubmoduleAbc.decode: 90 | ... 91 | -------------------------------------------------------------------------------- /ethtx/decoders/abi/balances.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from collections import defaultdict 18 | from typing import List 19 | 20 | from ethtx.models.decoded_model import DecodedTransfer, DecodedBalance, AddressInfo 21 | from .abc import ABISubmoduleAbc 22 | 23 | ZERO_ADDRESS = "0x" + 40 * "0" 24 | 25 | 26 | class ABIBalancesDecoder(ABISubmoduleAbc): 27 | """Abi Balances Decoder.""" 28 | 29 | def decode(self, transfers: List[DecodedTransfer]) -> List: 30 | """Decode balances.""" 31 | 32 | balance_holders = {} 33 | balance_tokens = {} 34 | 35 | for transfer in transfers: 36 | if transfer.from_address.address != ZERO_ADDRESS: 37 | balance_holders[ 38 | transfer.from_address.address 39 | ] = transfer.from_address.name 40 | if transfer.to_address.address != ZERO_ADDRESS: 41 | balance_holders[transfer.to_address.address] = transfer.to_address.name 42 | balance_tokens[transfer.token_address] = ( 43 | transfer.token_standard, 44 | transfer.token_symbol, 45 | ) 46 | 47 | balance_sheet: dict = {address: defaultdict(int) for address in balance_holders} 48 | 49 | for transfer in transfers: 50 | if transfer.from_address.address != ZERO_ADDRESS: 51 | balance_sheet[transfer.from_address.address][ 52 | transfer.token_address 53 | ] -= transfer.value 54 | if transfer.to_address.address != ZERO_ADDRESS: 55 | balance_sheet[transfer.to_address.address][ 56 | transfer.token_address 57 | ] += transfer.value 58 | 59 | balances = [] 60 | for holder_address in balance_holders: 61 | tokens = [] 62 | for token_address in balance_sheet[holder_address]: 63 | if balance_sheet[holder_address][token_address]: 64 | token_standard, token_symbol = balance_tokens[token_address] 65 | tokens.append( 66 | dict( 67 | token_address=token_address, 68 | token_symbol=token_symbol, 69 | token_standard=token_standard, 70 | balance=balance_sheet[holder_address][token_address], 71 | ) 72 | ) 73 | if tokens: 74 | holder_name = balance_holders[holder_address] 75 | balances.append( 76 | DecodedBalance( 77 | holder=AddressInfo(address=holder_address, name=holder_name), 78 | tokens=tokens, 79 | ) 80 | ) 81 | 82 | return balances 83 | -------------------------------------------------------------------------------- /ethtx/decoders/abi/events.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from typing import List, Dict, Union, Optional 18 | 19 | from ethtx.models.decoded_model import DecodedEvent, Proxy, AddressInfo 20 | from ethtx.models.objects_model import BlockMetadata, TransactionMetadata, Event 21 | from ethtx.semantics.standards.erc20 import ERC20_EVENTS 22 | from ethtx.semantics.standards.erc721 import ERC721_EVENTS 23 | from .abc import ABISubmoduleAbc 24 | from .helpers.utils import decode_event_abi_name_with_external_source 25 | from ..decoders.parameters import decode_event_parameters 26 | 27 | 28 | class ABIEventsDecoder(ABISubmoduleAbc): 29 | """ABI Events Decoder.""" 30 | 31 | def decode( 32 | self, 33 | events: Union[Event, List[Event]], 34 | block: BlockMetadata, 35 | transaction: TransactionMetadata, 36 | proxies: Optional[Dict[str, Proxy]] = None, 37 | chain_id: Optional[str] = None, 38 | ) -> Union[DecodedEvent, List[DecodedEvent]]: 39 | """Return list of decoded events.""" 40 | if isinstance(events, list): 41 | return ( 42 | [ 43 | self.decode_event(event, block, transaction, proxies, chain_id) 44 | for event in events 45 | ] 46 | if events 47 | else [] 48 | ) 49 | 50 | return self.decode_event(events, block, transaction, proxies, chain_id) 51 | 52 | def decode_event( 53 | self, 54 | event: Event, 55 | block: BlockMetadata, 56 | transaction: TransactionMetadata, 57 | proxies: Dict[str, Proxy] = None, 58 | chain_id: str = None, 59 | ) -> DecodedEvent: 60 | if event.topics: 61 | event_signature = event.topics[0] 62 | else: 63 | event_signature = None 64 | 65 | anonymous, guessed = False, False 66 | chain_id = chain_id or self._default_chain 67 | 68 | event_abi = self._repository.get_event_abi( 69 | chain_id, event.contract, event_signature 70 | ) 71 | 72 | if not event_abi: 73 | if not event_abi: 74 | # if signature is not known but there is exactly one anonymous event in tha ABI 75 | # we can assume that this is this the anonymous one (e.g. Maker's LogNote) 76 | event_abi = self._repository.get_anonymous_event_abi( 77 | chain_id, event.contract 78 | ) 79 | if event_abi: 80 | anonymous = True 81 | 82 | if not event_abi and event.contract in proxies: 83 | # try to find signature in delegate-called contracts 84 | for semantic in proxies[event.contract].semantics: 85 | event_abi = ( 86 | semantic.contract.events[event_signature] 87 | if event_signature in semantic.contract.events 88 | else None 89 | ) 90 | if event_abi: 91 | break 92 | 93 | if not event_abi and event_signature in ERC20_EVENTS: 94 | # try standard ERC20 events 95 | if ( 96 | len( 97 | [ 98 | parameter 99 | for parameter in ERC20_EVENTS[event_signature].parameters 100 | if parameter.indexed 101 | ] 102 | ) 103 | == len([topic for topic in event.topics if topic]) - 1 104 | ): 105 | event_abi = ERC20_EVENTS[event_signature] 106 | elif event_signature in ERC721_EVENTS: 107 | # try standard ERC721 events 108 | if ( 109 | len( 110 | [ 111 | parameter 112 | for parameter in ERC721_EVENTS[ 113 | event_signature 114 | ].parameters 115 | if parameter.indexed 116 | ] 117 | ) 118 | == len([topic for topic in event.topics if topic]) - 1 119 | ): 120 | event_abi = ERC721_EVENTS[event_signature] 121 | 122 | contract_name = self._repository.get_address_label( 123 | chain_id, event.contract, proxies 124 | ) 125 | 126 | if event_abi: 127 | event_name = event_abi.name 128 | elif event_signature: 129 | event_name = event_signature 130 | else: 131 | event_name = "Anonymous" 132 | 133 | parameters = decode_event_parameters( 134 | event.log_data, event.topics, event_abi, anonymous 135 | ) 136 | 137 | if event_name.startswith("0x") and len(event_name) > 2: 138 | guessed, event_name = decode_event_abi_name_with_external_source( 139 | signature=event_signature 140 | ) 141 | 142 | return DecodedEvent( 143 | chain_id=chain_id, 144 | tx_hash=transaction.tx_hash, 145 | timestamp=block.timestamp, 146 | contract=AddressInfo(address=event.contract, name=contract_name), 147 | index=event.log_index, 148 | call_id=event.call_id, 149 | event_signature=event_signature, 150 | event_name=event_name, 151 | parameters=parameters, 152 | event_guessed=guessed, 153 | ) 154 | -------------------------------------------------------------------------------- /ethtx/decoders/abi/helpers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/ethtx/decoders/abi/helpers/__init__.py -------------------------------------------------------------------------------- /ethtx/decoders/abi/helpers/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | import logging 18 | from typing import Optional, List, Tuple 19 | 20 | from ethtx.models.semantics_model import ( 21 | FunctionSemantics, 22 | ParameterSemantics, 23 | Signature, 24 | SignatureArg, 25 | ) 26 | from ethtx.providers.semantic_providers import SemanticsRepository 27 | from ethtx.providers.signature_provider import SignatureProvider, FourByteProvider 28 | 29 | log = logging.getLogger(__name__) 30 | 31 | 32 | def decode_function_abi_with_repository( 33 | signature: str, repository: SemanticsRepository 34 | ) -> Tuple[bool, Optional[FunctionSemantics]]: 35 | signature_obj = repository.get_most_used_signature(signature_hash=signature) 36 | if signature_obj: 37 | log.info( 38 | "Function (signature: %s, name: %s) guessed from SemanticsRepository.", 39 | signature_obj.signature_hash, 40 | signature_obj.name, 41 | ) 42 | function_semantics = FunctionSemantics( 43 | signature=signature, 44 | name=signature_obj.name, 45 | inputs=_prepare_parameter_semantics( 46 | signature_obj.args, signature_obj.tuple, unknown=False 47 | ), 48 | ) 49 | 50 | return signature_obj.guessed, function_semantics 51 | 52 | return False, None 53 | 54 | 55 | def decode_function_abi_with_external_source( 56 | signature: str, 57 | _provider: Optional[SignatureProvider] = FourByteProvider, 58 | ) -> List[Optional[Tuple[bool, FunctionSemantics]]]: 59 | functions = _provider.get_function(signature=signature) 60 | 61 | return [ 62 | ( 63 | True, 64 | FunctionSemantics( 65 | signature=signature, 66 | name=func.get("name"), 67 | inputs=_prepare_parameter_semantics( 68 | func.get("args"), 69 | isinstance(func.get("args"), tuple), 70 | unknown=True, 71 | ), 72 | ), 73 | ) 74 | for func in functions 75 | if func 76 | ] 77 | 78 | 79 | def upsert_guessed_function_semantics( 80 | signature: str, 81 | function_semantics: FunctionSemantics, 82 | repository: SemanticsRepository, 83 | ) -> None: 84 | log.info( 85 | "Function (signature: %s, name: %s) guessed from external source.", 86 | signature, 87 | function_semantics.name, 88 | ) 89 | 90 | repository.update_or_insert_signature( 91 | signature=Signature( 92 | signature_hash=signature, 93 | name=function_semantics.name, 94 | args=[ 95 | SignatureArg(name=f"arg_{i}", type=arg.parameter_type) 96 | for i, arg in enumerate(function_semantics.inputs) 97 | ], 98 | tuple=isinstance(function_semantics.inputs, tuple), 99 | guessed=True, 100 | ) 101 | ) 102 | 103 | 104 | def decode_event_abi_name_with_external_source( 105 | signature: str, _provider: Optional[SignatureProvider] = FourByteProvider 106 | ) -> Tuple[bool, str]: 107 | events = _provider.get_event(signature=signature) 108 | 109 | for event in events: 110 | if not event: 111 | return False, signature 112 | 113 | event_name = event.get("name") 114 | if event_name: 115 | log.info( 116 | "Event (signature: %s, name: %s) guessed from external source.", 117 | signature, 118 | event_name, 119 | ) 120 | return True, event.get("name", signature) 121 | 122 | return False, signature 123 | 124 | 125 | def _prepare_parameter_semantics( 126 | args, is_tuple: bool, unknown: bool 127 | ) -> List[ParameterSemantics]: 128 | if not args: 129 | return [] 130 | 131 | elif not is_tuple: 132 | return [ 133 | ParameterSemantics( 134 | parameter_name=arg.name if not unknown else f"arg_{i}", 135 | parameter_type=arg.type if not unknown else arg, 136 | ) 137 | for i, arg in enumerate(args) 138 | ] 139 | 140 | return [ 141 | ParameterSemantics( 142 | parameter_name="params", 143 | parameter_type="tuple", 144 | components=[ 145 | ParameterSemantics( 146 | parameter_name=arg.name if not unknown else f"arg_{i}", 147 | parameter_type=arg.type if not unknown else arg, 148 | ) 149 | for i, arg in enumerate(args) 150 | ], 151 | ) 152 | ] 153 | -------------------------------------------------------------------------------- /ethtx/decoders/decoders/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/ethtx/decoders/decoders/__init__.py -------------------------------------------------------------------------------- /ethtx/decoders/decoders/errors.py: -------------------------------------------------------------------------------- 1 | from ethtx.models.semantics_model import ParameterSemantics 2 | 3 | 4 | ERRORS = { 5 | "0x08c379a0": { 6 | "name": "Error", 7 | "abi": [ 8 | ParameterSemantics( 9 | parameter_name="Error", 10 | parameter_type="string", 11 | components=[], 12 | indexed=False, 13 | dynamic=True, 14 | ) 15 | ], 16 | }, 17 | "0xfdb6ca8d": { 18 | "name": "OrderStatusError", 19 | "abi": [ 20 | ParameterSemantics( 21 | parameter_name="orderHash", 22 | parameter_type="bytes32", 23 | ), 24 | ParameterSemantics(parameter_name="orderStatus", parameter_type="uint8"), 25 | ], 26 | }, 27 | "0x990174d2": { 28 | "name": "IncompleteTransformERC20Error", 29 | "abi": [ 30 | ParameterSemantics(parameter_name="outputToken", parameter_type="address"), 31 | ParameterSemantics( 32 | parameter_name="outputTokenAmount", parameter_type="uint256" 33 | ), 34 | ParameterSemantics( 35 | parameter_name="minOutputTokenAmount", parameter_type="uint256" 36 | ), 37 | ], 38 | }, 39 | "0x4678472b": { 40 | "name": "AssetProxyTransferError", 41 | "abi": [ 42 | ParameterSemantics(parameter_name="orderHash", parameter_type="bytes32"), 43 | ParameterSemantics(parameter_name="assetData", parameter_type="bytes"), 44 | ParameterSemantics(parameter_name="errorData", parameter_type="bytes"), 45 | ], 46 | }, 47 | "0x87cb1e75": { 48 | "name": "PayProtocolFeeError", 49 | "abi": [ 50 | ParameterSemantics(parameter_name="orderHash", parameter_type="bytes32"), 51 | ParameterSemantics(parameter_name="protocolFee", parameter_type="uint256"), 52 | ParameterSemantics(parameter_name="makerAddress", parameter_type="address"), 53 | ParameterSemantics(parameter_name="takerAddress", parameter_type="address"), 54 | ParameterSemantics(parameter_name="errorData", parameter_type="bytes"), 55 | ], 56 | }, 57 | "0x339f3de2": { 58 | "name": "RoundingError", 59 | "abi": [ 60 | ParameterSemantics(parameter_name="numerator", parameter_type="uint256"), 61 | ParameterSemantics(parameter_name="denominator", parameter_type="uint256"), 62 | ParameterSemantics(parameter_name="target", parameter_type="uint256"), 63 | ], 64 | }, 65 | "0x18e4b141": { 66 | "name": "IncompleteFillError", 67 | "abi": [ 68 | ParameterSemantics(parameter_name="errorCode", parameter_type="uint8"), 69 | ParameterSemantics( 70 | parameter_name="expectedAssetFillAmount", parameter_type="uint256" 71 | ), 72 | ParameterSemantics( 73 | parameter_name="actualAssetFillAmount", parameter_type="uint256" 74 | ), 75 | ], 76 | }, 77 | } 78 | -------------------------------------------------------------------------------- /ethtx/decoders/decoders/semantics.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from typing import List, Dict, Tuple 18 | 19 | from ethtx.models.semantics_model import ( 20 | ParameterSemantics, 21 | EventSemantics, 22 | FunctionSemantics, 23 | TransformationSemantics, 24 | ) 25 | 26 | 27 | def _decode_parameters_list(raw_parameters_list: list) -> List[ParameterSemantics]: 28 | parameters_list = [] 29 | 30 | if not raw_parameters_list: 31 | return parameters_list 32 | 33 | for raw_parameter_semantics in raw_parameters_list: 34 | if "indexed" in raw_parameter_semantics: 35 | indexed = raw_parameter_semantics["indexed"] 36 | else: 37 | indexed = False 38 | 39 | if "dynamic" in raw_parameter_semantics: 40 | dynamic = raw_parameter_semantics["dynamic"] 41 | else: 42 | dynamic = False 43 | 44 | if raw_parameter_semantics["type"] == "tuple": 45 | components = _decode_parameters_list(raw_parameter_semantics["components"]) 46 | else: 47 | components = [] 48 | 49 | parameters_list.append( 50 | ParameterSemantics( 51 | parameter_name=raw_parameter_semantics["name"], 52 | parameter_type=raw_parameter_semantics["type"], 53 | components=components, 54 | indexed=indexed, 55 | dynamic=dynamic, 56 | ) 57 | ) 58 | return parameters_list 59 | 60 | 61 | def decode_events_and_functions( 62 | abi: dict, 63 | ) -> Tuple[Dict[str, EventSemantics], Dict[str, FunctionSemantics]]: 64 | events = {} 65 | for signature, raw_event_semantics in abi.get("events", {}).items(): 66 | parameters = _decode_parameters_list(raw_event_semantics.get("parameters")) 67 | events[signature] = EventSemantics( 68 | signature=signature, 69 | anonymous=raw_event_semantics["anonymous"], 70 | name=raw_event_semantics["name"], 71 | parameters=parameters, 72 | ) 73 | 74 | functions = {} 75 | for signature, raw_function_semantics in abi.get("functions", {}).items(): 76 | if raw_function_semantics: 77 | inputs = _decode_parameters_list(raw_function_semantics.get("inputs")) 78 | outputs = _decode_parameters_list(raw_function_semantics.get("outputs")) 79 | name = raw_function_semantics["name"] 80 | else: 81 | inputs = outputs = [] 82 | name = signature 83 | 84 | functions[signature] = FunctionSemantics( 85 | signature=signature, name=name, inputs=inputs, outputs=outputs 86 | ) 87 | 88 | return events, functions 89 | 90 | 91 | def decode_transformations( 92 | raw_transformations: dict, 93 | ) -> Dict[str, Dict[str, TransformationSemantics]]: 94 | transformations = {} 95 | if raw_transformations: 96 | for signature, transformation in raw_transformations.items(): 97 | transformations[signature] = {} 98 | for parameter_name, parameter_transformation in transformation.get( 99 | "arguments", {} 100 | ).items(): 101 | transformations[signature][parameter_name] = TransformationSemantics( 102 | transformed_name=parameter_transformation.get("name"), 103 | transformed_type=parameter_transformation.get("type"), 104 | transformation=parameter_transformation.get("value"), 105 | ) 106 | return transformations 107 | -------------------------------------------------------------------------------- /ethtx/decoders/semantic/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/ethtx/decoders/semantic/__init__.py -------------------------------------------------------------------------------- /ethtx/decoders/semantic/abc.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from abc import ABC, abstractmethod 18 | from typing import Any, List, Dict 19 | 20 | from ethtx.models.decoded_model import ( 21 | DecodedTransaction, 22 | DecodedCall, 23 | DecodedEvent, 24 | DecodedTransfer, 25 | DecodedBalance, 26 | Proxy, 27 | ) 28 | from ethtx.models.objects_model import BlockMetadata, TransactionMetadata 29 | from ethtx.providers.semantic_providers import SemanticsRepository 30 | 31 | 32 | class SemanticSubmoduleAbc(ABC): 33 | def __init__(self, repository: SemanticsRepository): 34 | self.repository = repository 35 | 36 | @abstractmethod 37 | def decode(self, *args, **kwargs) -> Any: 38 | ... 39 | 40 | 41 | class ISemanticDecoder(ABC): 42 | def __init__(self, repository: SemanticsRepository, chain_id: str): 43 | self.repository = repository 44 | self._default_chain = chain_id 45 | 46 | @abstractmethod 47 | def decode_transaction( 48 | self, 49 | block: BlockMetadata, 50 | transaction: DecodedTransaction, 51 | proxies: Dict[str, Proxy], 52 | chain_id: str, 53 | ) -> DecodedTransaction: 54 | ... 55 | 56 | @abstractmethod 57 | def decode_metadata( 58 | self, 59 | block_metadata: BlockMetadata, 60 | tx_metadata: TransactionMetadata, 61 | chain_id: str, 62 | ): 63 | ... 64 | 65 | @abstractmethod 66 | def decode_calls( 67 | self, 68 | call: DecodedCall, 69 | tx_metadata: TransactionMetadata, 70 | proxies: Dict[str, Proxy], 71 | ) -> SemanticSubmoduleAbc.decode: 72 | ... 73 | 74 | @abstractmethod 75 | def decode_events( 76 | self, 77 | events: List[DecodedEvent], 78 | tx_metadata: TransactionMetadata, 79 | proxies: Dict[str, Proxy], 80 | ) -> SemanticSubmoduleAbc.decode: 81 | ... 82 | 83 | @abstractmethod 84 | def decode_transfers( 85 | self, transfers: List[DecodedTransfer], tx_metadata: TransactionMetadata 86 | ) -> SemanticSubmoduleAbc.decode: 87 | ... 88 | 89 | @abstractmethod 90 | def decode_balances( 91 | self, balances: List[DecodedBalance], tx_metadata: TransactionMetadata 92 | ) -> SemanticSubmoduleAbc.decode: 93 | ... 94 | -------------------------------------------------------------------------------- /ethtx/decoders/semantic/balances.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from typing import List 18 | 19 | from ethtx.models.decoded_model import DecodedBalance, DecodedTransactionMetadata 20 | from .abc import SemanticSubmoduleAbc 21 | from .helpers.utils import get_badge 22 | 23 | 24 | class SemanticBalancesDecoder(SemanticSubmoduleAbc): 25 | """Semantic Balances Decoder.""" 26 | 27 | def decode( 28 | self, balances: List[DecodedBalance], tx_metadata: DecodedTransactionMetadata 29 | ) -> List[DecodedBalance]: 30 | """Semantically decode balances.""" 31 | 32 | for balance in balances: 33 | # decode the proper holder badge 34 | balance.holder.badge = get_badge( 35 | balance.holder.address, 36 | tx_metadata.sender.address, 37 | tx_metadata.receiver.address, 38 | ) 39 | 40 | for token in balance.tokens: 41 | token["balance"] = f"{token['balance']:,.4f}" 42 | 43 | return balances 44 | -------------------------------------------------------------------------------- /ethtx/decoders/semantic/decoder.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from typing import Dict, List, Optional 18 | 19 | from ethtx.decoders.semantic.abc import ISemanticDecoder 20 | from ethtx.decoders.semantic.balances import SemanticBalancesDecoder 21 | from ethtx.decoders.semantic.calls import SemanticCallsDecoder 22 | from ethtx.decoders.semantic.events import SemanticEventsDecoder 23 | from ethtx.decoders.semantic.metadata import SemanticMetadataDecoder 24 | from ethtx.decoders.semantic.transfers import SemanticTransfersDecoder 25 | from ethtx.models.decoded_model import ( 26 | DecodedTransactionMetadata, 27 | DecodedTransaction, 28 | DecodedEvent, 29 | DecodedTransfer, 30 | DecodedBalance, 31 | DecodedCall, 32 | Proxy, 33 | ) 34 | from ethtx.models.objects_model import BlockMetadata 35 | 36 | 37 | class SemanticDecoder(ISemanticDecoder): 38 | def decode_transaction( 39 | self, 40 | block: BlockMetadata, 41 | transaction: DecodedTransaction, 42 | proxies: Dict[str, Proxy], 43 | chain_id: str, 44 | ) -> DecodedTransaction: 45 | transaction.metadata = self.decode_metadata( 46 | block, transaction.metadata, chain_id 47 | ) 48 | transaction.events = self.decode_events( 49 | transaction.events, transaction.metadata, proxies 50 | ) 51 | transaction.calls = self.decode_calls( 52 | transaction.calls, transaction.metadata, proxies 53 | ) 54 | transaction.transfers = self.decode_transfers( 55 | transaction.transfers, transaction.metadata 56 | ) 57 | transaction.balances = self.decode_balances( 58 | transaction.balances, transaction.metadata 59 | ) 60 | 61 | return transaction 62 | 63 | def decode_metadata( 64 | self, 65 | block_metadata: BlockMetadata, 66 | tx_metadata: DecodedTransactionMetadata, 67 | chain_id: str, 68 | ) -> DecodedTransactionMetadata: 69 | return SemanticMetadataDecoder(repository=self.repository).decode( 70 | block_metadata=block_metadata, tx_metadata=tx_metadata, chain_id=chain_id 71 | ) 72 | 73 | def decode_event( 74 | self, 75 | event: DecodedEvent, 76 | tx_metadata: DecodedTransactionMetadata, 77 | proxies: Optional[Dict[str, Proxy]] = None, 78 | ) -> DecodedEvent: 79 | return SemanticEventsDecoder(repository=self.repository).decode( 80 | events=event, tx_metadata=tx_metadata, proxies=proxies or {} 81 | ) 82 | 83 | def decode_events( 84 | self, 85 | events: List[DecodedEvent], 86 | tx_metadata: DecodedTransactionMetadata, 87 | proxies: Optional[Dict[str, Proxy]] = None, 88 | ) -> List[DecodedEvent]: 89 | return SemanticEventsDecoder(repository=self.repository).decode( 90 | events=events, tx_metadata=tx_metadata, proxies=proxies or {} 91 | ) 92 | 93 | def decode_calls( 94 | self, 95 | call: DecodedCall, 96 | tx_metadata: DecodedTransactionMetadata, 97 | proxies: Optional[Dict[str, Proxy]] = None, 98 | ) -> DecodedCall: 99 | return SemanticCallsDecoder(repository=self.repository).decode( 100 | call=call, tx_metadata=tx_metadata, proxies=proxies or {} 101 | ) 102 | 103 | def decode_call( 104 | self, 105 | call: DecodedCall, 106 | tx_metadata: DecodedTransactionMetadata, 107 | proxies: Optional[Dict[str, Proxy]] = None, 108 | ) -> DecodedCall: 109 | return SemanticCallsDecoder(repository=self.repository).decode( 110 | call=call, tx_metadata=tx_metadata, proxies=proxies or {} 111 | ) 112 | 113 | def decode_transfers( 114 | self, transfers: List[DecodedTransfer], tx_metadata: DecodedTransactionMetadata 115 | ) -> List[DecodedTransfer]: 116 | return SemanticTransfersDecoder(repository=self.repository).decode( 117 | transfers=transfers, tx_metadata=tx_metadata 118 | ) 119 | 120 | def decode_balances( 121 | self, balances: List[DecodedBalance], tx_metadata: DecodedTransactionMetadata 122 | ) -> List[DecodedBalance]: 123 | return SemanticBalancesDecoder(repository=self.repository).decode( 124 | balances=balances, tx_metadata=tx_metadata 125 | ) 126 | -------------------------------------------------------------------------------- /ethtx/decoders/semantic/helpers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/ethtx/decoders/semantic/helpers/__init__.py -------------------------------------------------------------------------------- /ethtx/decoders/semantic/metadata.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from ethtx.models.decoded_model import DecodedTransactionMetadata, AddressInfo 18 | from ethtx.models.objects_model import BlockMetadata 19 | from .abc import SemanticSubmoduleAbc 20 | 21 | 22 | class SemanticMetadataDecoder(SemanticSubmoduleAbc): 23 | """Semantic Metadata Decoder.""" 24 | 25 | def decode( 26 | self, 27 | block_metadata: BlockMetadata, 28 | tx_metadata: DecodedTransactionMetadata, 29 | chain_id: str, 30 | ) -> DecodedTransactionMetadata: 31 | """Semantically decode metadata.""" 32 | 33 | decoded_metadata = DecodedTransactionMetadata( 34 | chain_id=chain_id, 35 | tx_hash=tx_metadata.tx_hash, 36 | block_number=block_metadata.block_number, 37 | block_hash=block_metadata.block_hash, 38 | timestamp=block_metadata.timestamp, 39 | gas_price=tx_metadata.gas_price / 10**9, 40 | sender=AddressInfo( 41 | address=tx_metadata.from_address, 42 | name=self.repository.get_address_label( 43 | chain_id, tx_metadata.from_address 44 | ), 45 | badge="sender", 46 | ), 47 | receiver=AddressInfo( 48 | address=tx_metadata.to_address, 49 | name=self.repository.get_address_label( 50 | chain_id, tx_metadata.to_address 51 | ), 52 | badge="receiver", 53 | ), 54 | tx_index=tx_metadata.tx_index, 55 | tx_value=tx_metadata.tx_value, 56 | gas_limit=tx_metadata.gas_limit, 57 | gas_used=tx_metadata.gas_used, 58 | success=tx_metadata.success, 59 | ) 60 | 61 | return decoded_metadata 62 | -------------------------------------------------------------------------------- /ethtx/decoders/semantic/transfers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from typing import List 18 | 19 | from ethtx.models.decoded_model import DecodedTransfer, DecodedTransactionMetadata 20 | from .abc import SemanticSubmoduleAbc 21 | from .helpers.utils import get_badge 22 | 23 | 24 | class SemanticTransfersDecoder(SemanticSubmoduleAbc): 25 | def decode( 26 | self, transfers: List[DecodedTransfer], tx_metadata: DecodedTransactionMetadata 27 | ) -> List[DecodedTransfer]: 28 | for transfer in transfers: 29 | # decode proper from and to addresses badges 30 | transfer.from_address.badge = get_badge( 31 | transfer.from_address.address, 32 | tx_metadata.sender.address, 33 | tx_metadata.receiver.address, 34 | ) 35 | transfer.to_address.badge = get_badge( 36 | transfer.to_address.address, 37 | tx_metadata.sender.address, 38 | tx_metadata.receiver.address, 39 | ) 40 | 41 | return transfers 42 | -------------------------------------------------------------------------------- /ethtx/ethtx.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from typing import Dict 18 | 19 | from mongoengine import connect 20 | from pymongo import MongoClient 21 | 22 | from .decoders.abi.decoder import ABIDecoder 23 | from .decoders.decoder_service import DecoderService 24 | from .decoders.semantic.decoder import SemanticDecoder 25 | from .models.decoded_model import Proxy, DecodedTransaction 26 | from .models.objects_model import Call 27 | from .providers import EtherscanProvider, Web3Provider, ENSProvider 28 | from .providers.semantic_providers import ( 29 | ISemanticsDatabase, 30 | SemanticsRepository, 31 | MongoSemanticsDatabase, 32 | ) 33 | from .utils.validators import assert_tx_hash 34 | 35 | 36 | class EthTxConfig: 37 | mongo_connection_string: str 38 | etherscan_api_key: str 39 | web3nodes: Dict[str, dict] 40 | etherscan_urls: Dict[str, str] 41 | default_chain: str 42 | 43 | def __init__( 44 | self, 45 | mongo_connection_string: str, 46 | web3nodes: Dict[str, dict], 47 | etherscan_api_key: str, 48 | etherscan_urls: Dict[str, str], 49 | default_chain: str = "mainnet", 50 | ): 51 | self.mongo_connection_string = mongo_connection_string 52 | self.etherscan_api_key = etherscan_api_key 53 | self.web3nodes = web3nodes 54 | self.default_chain = default_chain 55 | self.etherscan_urls = etherscan_urls 56 | 57 | 58 | class EthTxDecoders: 59 | semantic_decoder: SemanticDecoder 60 | abi_decoder: ABIDecoder 61 | 62 | def __init__(self, decoder_service: DecoderService): 63 | self._decoder_service = decoder_service 64 | self.abi_decoder: ABIDecoder = decoder_service.abi_decoder 65 | self.semantic_decoder: SemanticDecoder = decoder_service.semantic_decoder 66 | 67 | def decode_transaction( 68 | self, tx_hash: str, chain_id: str = None, recreate_semantics: bool = False 69 | ) -> DecodedTransaction: 70 | assert_tx_hash(tx_hash) 71 | return self._decoder_service.decode_transaction( 72 | chain_id, tx_hash, recreate_semantics 73 | ) 74 | 75 | def get_proxies(self, call_tree: Call, chain_id: str) -> Dict[str, Proxy]: 76 | delegations = self._decoder_service.get_delegations(call_tree) 77 | return self._decoder_service.get_proxies(delegations, chain_id) 78 | 79 | 80 | class EthTxProviders: 81 | web3provider: Web3Provider 82 | etherscan_provider: EtherscanProvider 83 | ens_provider: ENSProvider 84 | 85 | def __init__( 86 | self, 87 | web3provider: Web3Provider, 88 | etherscan_provider: EtherscanProvider, 89 | ens_provider: ENSProvider, 90 | ): 91 | self.web3provider = web3provider 92 | self.etherscan_provider = etherscan_provider 93 | self.ens_provider = ens_provider 94 | 95 | 96 | class EthTx: 97 | def __init__( 98 | self, 99 | default_chain: str, 100 | database: ISemanticsDatabase, 101 | web3provider: Web3Provider, 102 | etherscan_provider: EtherscanProvider, 103 | ens_provider: ENSProvider, 104 | ): 105 | self._default_chain = default_chain 106 | self._semantics_repository = SemanticsRepository( 107 | database_connection=database, 108 | etherscan_provider=etherscan_provider, 109 | web3provider=web3provider, 110 | ens_provider=ens_provider, 111 | ) 112 | 113 | abi_decoder = ABIDecoder(self.semantics, self._default_chain) 114 | semantic_decoder = SemanticDecoder(self.semantics, self._default_chain) 115 | decoder_service = DecoderService( 116 | abi_decoder, semantic_decoder, web3provider, self._default_chain 117 | ) 118 | self._decoders = EthTxDecoders(decoder_service=decoder_service) 119 | self._providers = EthTxProviders( 120 | web3provider=web3provider, 121 | etherscan_provider=etherscan_provider, 122 | ens_provider=ens_provider, 123 | ) 124 | 125 | @staticmethod 126 | def initialize(config: EthTxConfig): 127 | mongo_client: MongoClient = connect(host=config.mongo_connection_string) 128 | repository = MongoSemanticsDatabase(db=mongo_client.get_database()) 129 | 130 | web3provider = Web3Provider( 131 | nodes=config.web3nodes, default_chain=config.default_chain 132 | ) 133 | etherscan_provider = EtherscanProvider( 134 | api_key=config.etherscan_api_key, 135 | nodes=config.etherscan_urls, 136 | default_chain_id=config.default_chain, 137 | ) 138 | 139 | ens_provider = ENSProvider 140 | 141 | return EthTx( 142 | config.default_chain, 143 | repository, 144 | web3provider, 145 | etherscan_provider, 146 | ens_provider, 147 | ) 148 | 149 | @property 150 | def decoders(self) -> EthTxDecoders: 151 | """EthTx Decoders.""" 152 | return self._decoders 153 | 154 | @property 155 | def semantics(self) -> SemanticsRepository: 156 | """EthTx Semantics Repository.""" 157 | return self._semantics_repository 158 | 159 | @property 160 | def providers(self) -> EthTxProviders: 161 | """EthTx Providers.""" 162 | return self._providers 163 | 164 | @property 165 | def default_chain(self) -> str: 166 | """Default chain.""" 167 | return self._default_chain 168 | 169 | @default_chain.setter 170 | def default_chain(self, chain: str) -> None: 171 | """Default chain setter.""" 172 | self._default_chain = chain 173 | -------------------------------------------------------------------------------- /ethtx/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | 18 | __all__ = [ 19 | "NodeConnectionException", 20 | "ProcessingException", 21 | "InvalidTransactionHash", 22 | "InvalidEtherscanReturnCodeException", 23 | ] 24 | 25 | import json 26 | from typing import Dict 27 | 28 | 29 | class NodeConnectionException(Exception): 30 | """Node Connection Exception.""" 31 | 32 | def __init__(self): 33 | super().__init__("Couldn't connect to node(s)") 34 | 35 | 36 | class ProcessingException(Exception): 37 | """Processing Exception.""" 38 | 39 | def __init__(self, msg): 40 | super().__init__("Exception processing: " + msg) 41 | 42 | 43 | class InvalidTransactionHash(Exception): 44 | """Invalid Transaction Hash.""" 45 | 46 | def __init__(self, tx_hash): 47 | super().__init__("Invalid transaction hash provided: " + tx_hash) 48 | 49 | 50 | class InvalidEtherscanReturnCodeException(Exception): 51 | def __init__(self, returned_code: int, params: Dict = None): 52 | params_msg = " with params: " + json.dumps(params) if params else "" 53 | msg = f"Invalid status code for etherscan request: {returned_code} {params_msg}" 54 | super().__init__(msg) 55 | -------------------------------------------------------------------------------- /ethtx/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/ethtx/models/__init__.py -------------------------------------------------------------------------------- /ethtx/models/_types.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from typing import TypeVar 18 | 19 | from hexbytes import HexBytes 20 | 21 | THexBytes = TypeVar("THexBytes", bound=HexBytes.__class__.__base__) 22 | -------------------------------------------------------------------------------- /ethtx/models/base_model.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from pydantic import BaseModel as PydanticBaseModel 18 | 19 | 20 | class BaseModel(PydanticBaseModel): 21 | class Config: 22 | arbitrary_types_allowed = True 23 | 24 | def __hash__(self): 25 | return hash((type(self),) + tuple(self.__dict__.values())) 26 | -------------------------------------------------------------------------------- /ethtx/models/decoded_model.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from __future__ import annotations 18 | 19 | from datetime import datetime 20 | from typing import List, Any, Optional 21 | from decimal import Decimal, getcontext 22 | 23 | from pydantic import validator 24 | 25 | from ethtx.models.base_model import BaseModel 26 | from ethtx.models.objects_model import BlockMetadata 27 | from ethtx.models.semantics_model import AddressSemantics, ERC20Semantics 28 | 29 | 30 | class AddressInfo(BaseModel): 31 | address: Optional[str] 32 | name: str 33 | badge: Optional[str] 34 | 35 | 36 | class DecodedTransactionMetadata(BaseModel): 37 | chain_id: Optional[str] 38 | tx_hash: str 39 | block_number: Optional[int] 40 | block_hash: Optional[str] 41 | timestamp: Optional[datetime] 42 | gas_price: Optional[int] 43 | from_address: Optional[str] 44 | to_address: Optional[str] 45 | sender: Optional[AddressInfo] 46 | receiver: Optional[AddressInfo] 47 | tx_index: int 48 | tx_value: int 49 | gas_limit: int 50 | gas_used: int 51 | success: bool 52 | 53 | 54 | class Argument(BaseModel): 55 | name: str 56 | type: str 57 | value: Any 58 | 59 | @validator("value") 60 | def decimal_conv(cls, v: Any) -> Any: 61 | """Method dealing with the case of large int and float by handling them as Decimal. Avoids loss of precision 62 | in digits. 63 | """ 64 | if isinstance(v, int) or isinstance(v, float): 65 | getcontext().prec = 256 66 | return Decimal(v) 67 | return v 68 | 69 | 70 | class DecodedEvent(BaseModel): 71 | chain_id: str 72 | tx_hash: str 73 | timestamp: datetime 74 | contract: AddressInfo 75 | index: Optional[int] 76 | call_id: Optional[str] 77 | event_signature: Optional[str] 78 | event_name: str 79 | parameters: List[Argument] 80 | event_guessed: bool = False 81 | 82 | 83 | class DecodedCall(BaseModel): 84 | chain_id: str 85 | timestamp: datetime 86 | tx_hash: str 87 | call_id: Optional[str] 88 | call_type: str 89 | from_address: AddressInfo 90 | to_address: Optional[AddressInfo] 91 | value: Decimal 92 | function_signature: str 93 | function_name: str 94 | arguments: List[Argument] 95 | outputs: List[Argument] 96 | gas_used: Optional[int] 97 | error: Optional[str] 98 | status: bool 99 | indent: int 100 | subcalls: List[DecodedCall] = [] 101 | function_guessed: bool = False 102 | 103 | 104 | class DecodedTransfer(BaseModel): 105 | from_address: AddressInfo 106 | to_address: AddressInfo 107 | token_address: Optional[str] 108 | token_symbol: str 109 | token_standard: Optional[str] 110 | value: Decimal 111 | 112 | 113 | class DecodedBalance(BaseModel): 114 | holder: AddressInfo 115 | tokens: List[dict] 116 | 117 | 118 | class DecodedTransaction(BaseModel): 119 | block_metadata: BlockMetadata 120 | metadata: DecodedTransactionMetadata 121 | events: List[DecodedEvent] 122 | calls: Optional[DecodedCall] 123 | transfers: List[DecodedTransfer] 124 | balances: List[DecodedBalance] 125 | status: bool = False 126 | 127 | 128 | class Proxy(BaseModel): 129 | address: str 130 | name: str 131 | type: str 132 | semantics: Optional[List[AddressSemantics]] 133 | token: Optional[ERC20Semantics] 134 | -------------------------------------------------------------------------------- /ethtx/models/objects_model.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from __future__ import annotations 18 | 19 | from datetime import datetime 20 | from typing import List, Optional 21 | 22 | from pydantic import Field 23 | 24 | from ethtx.models.base_model import BaseModel 25 | 26 | 27 | class BlockMetadata(BaseModel): 28 | block_number: int 29 | block_hash: str 30 | timestamp: datetime 31 | parent_hash: str 32 | miner: str 33 | gas_limit: int 34 | gas_used: int 35 | tx_count: int 36 | 37 | # for future use 38 | canonical: bool = True 39 | 40 | @staticmethod 41 | def from_raw(w3block) -> BlockMetadata: 42 | return w3block.to_object() 43 | 44 | 45 | class TransactionMetadata(BaseModel): 46 | tx_hash: str 47 | block_number: int 48 | gas_price: int 49 | from_address: str 50 | to_address: str 51 | tx_index: int 52 | tx_value: int 53 | gas_limit: int 54 | gas_used: int 55 | success: bool 56 | 57 | # for future use 58 | gas_refund: Optional[int] 59 | return_value: Optional[str] 60 | exception_error: Optional[str] 61 | exception_error_type: Optional[str] 62 | revert_reason: Optional[str] 63 | 64 | @staticmethod 65 | def from_raw(w3transaction, w3receipt) -> TransactionMetadata: 66 | return w3transaction.to_object(w3receipt) 67 | 68 | 69 | class Event(BaseModel): 70 | contract: Optional[str] 71 | topics: List[str] 72 | log_data: Optional[str] 73 | log_index: Optional[int] 74 | 75 | call_id: Optional[str] 76 | 77 | @staticmethod 78 | def from_raw(w3log) -> Event: 79 | return w3log.to_object() 80 | 81 | 82 | class Call(BaseModel): 83 | call_type: str 84 | call_gas: Optional[int] 85 | from_address: str 86 | to_address: Optional[str] 87 | call_value: int 88 | call_data: str 89 | return_value: str 90 | gas_used: Optional[int] 91 | status: bool 92 | error: Optional[str] 93 | subcalls: List[Call] = Field(default_factory=list) 94 | 95 | # for future use 96 | call_id: Optional[str] 97 | created_address: Optional[str] 98 | gas_refund: Optional[int] 99 | exception_error: Optional[str] 100 | exception_error_type: Optional[str] 101 | revert_reason: Optional[str] 102 | success: Optional[bool] 103 | 104 | @staticmethod 105 | def from_raw(w3calltree) -> Call: 106 | return w3calltree.to_object() 107 | 108 | 109 | Call.update_forward_refs() 110 | 111 | 112 | class Transaction(BaseModel): 113 | metadata: TransactionMetadata 114 | root_call: Call 115 | events: List[Event] 116 | 117 | @staticmethod 118 | def from_raw(w3transaction, w3receipt, w3calltree) -> Transaction: 119 | data = w3transaction.to_object(w3receipt) 120 | events = [w3log.to_object() for w3log in w3receipt.logs] 121 | root_call = w3calltree.to_object() 122 | return Transaction(metadata=data, root_call=root_call, events=events) 123 | 124 | 125 | class Block(BaseModel): 126 | chain_id: str 127 | metadata: BlockMetadata 128 | transactions: List[Transaction] 129 | 130 | @staticmethod 131 | def from_raw(chain_id, w3block, w3transactions=None) -> Block: 132 | data = w3block.to_object() 133 | if w3transactions: 134 | transactions = [ 135 | Transaction.from_raw(w3transaction, w3receipt, w3calltree) 136 | for (w3transaction, w3receipt, w3calltree) in w3transactions 137 | ] 138 | else: 139 | transactions = [] 140 | 141 | return Block(chain_id=chain_id, metadata=data, transactions=transactions) 142 | -------------------------------------------------------------------------------- /ethtx/models/w3_model.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from __future__ import annotations 18 | 19 | from datetime import datetime 20 | from typing import Optional, List 21 | 22 | from ethtx.models._types import THexBytes 23 | from ethtx.models.base_model import BaseModel 24 | from ethtx.models.objects_model import BlockMetadata, TransactionMetadata, Event, Call 25 | 26 | 27 | class W3Block(BaseModel): 28 | chain_id: str 29 | difficulty: int 30 | extraData: THexBytes 31 | gasLimit: int 32 | gasUsed: int 33 | hash: THexBytes 34 | logsBloom: THexBytes 35 | miner: str 36 | nonce: THexBytes 37 | number: int 38 | parentHash: THexBytes 39 | receiptsRoot: THexBytes 40 | sha3Uncles: THexBytes 41 | size: int 42 | stateRoot: THexBytes 43 | timestamp: int 44 | totalDifficulty: int 45 | transactions: list 46 | transactionsRoot: THexBytes 47 | uncles: list 48 | 49 | def to_object(self) -> BlockMetadata: 50 | block_hash = self.hash.hex() 51 | timestamp = datetime.utcfromtimestamp(self.timestamp) 52 | parent_hash = self.parentHash.hex() 53 | miner = self.miner.lower() 54 | gas_limit = self.gasLimit 55 | gas_used = self.gasUsed 56 | tx_count = len(self.transactions) 57 | 58 | return BlockMetadata( 59 | block_number=self.number, 60 | block_hash=block_hash, 61 | timestamp=timestamp, 62 | parent_hash=parent_hash, 63 | miner=miner, 64 | gas_limit=gas_limit, 65 | gas_used=gas_used, 66 | tx_count=tx_count, 67 | ) 68 | 69 | 70 | class W3Transaction(BaseModel): 71 | chain_id: str 72 | blockHash: THexBytes 73 | blockNumber: int 74 | from_address: str 75 | gas: int 76 | gasPrice: int 77 | hash: THexBytes 78 | input: str 79 | nonce: int 80 | r: THexBytes 81 | s: THexBytes 82 | to: Optional[str] 83 | transactionIndex: int 84 | v: int 85 | value: int 86 | 87 | def to_object(self, w3receipt: W3Receipt) -> TransactionMetadata: 88 | tx_hash = self.hash.hex() 89 | block_number = self.blockNumber 90 | tx_index = self.transactionIndex 91 | from_address = self.from_address.lower() 92 | to_address = ( 93 | self.to.lower() 94 | if self.to 95 | else w3receipt.contractAddress.lower() 96 | if w3receipt.contractAddress 97 | else None 98 | ) 99 | tx_value = self.value 100 | gas_limit = self.gas 101 | gas_price = self.gasPrice 102 | gas_used = w3receipt.gasUsed 103 | success = w3receipt.status == 1 104 | 105 | return TransactionMetadata( 106 | tx_hash=tx_hash, 107 | block_number=block_number, 108 | tx_index=tx_index, 109 | from_address=from_address, 110 | to_address=to_address, 111 | tx_value=tx_value, 112 | gas_limit=gas_limit, 113 | gas_price=gas_price, 114 | gas_used=gas_used, 115 | success=success, 116 | ) 117 | 118 | 119 | class W3Receipt(BaseModel): 120 | tx_hash: str 121 | chain_id: str 122 | blockHash: THexBytes 123 | blockNumber: int 124 | contractAddress: Optional[str] 125 | cumulativeGasUsed: int 126 | from_address: str 127 | gasUsed: int 128 | logsBloom: THexBytes 129 | root: Optional[str] 130 | status: int 131 | to_address: Optional[str] 132 | transactionHash: THexBytes 133 | transactionIndex: int 134 | logs: list = [] 135 | 136 | 137 | class W3Log(BaseModel): 138 | tx_hash: str 139 | chain_id: str 140 | address: str 141 | blockHash: THexBytes 142 | blockNumber: int 143 | data: str 144 | logIndex: int 145 | removed: bool 146 | topics: List[THexBytes] 147 | transactionHash: THexBytes 148 | transactionIndex: int 149 | 150 | def to_object(self) -> Event: 151 | contract = self.address.lower() 152 | log_index = self.logIndex 153 | log_data = self.data 154 | topics = [] 155 | 156 | for i, _ in enumerate(self.topics): 157 | topics.append(self.topics[i].hex()) 158 | 159 | return Event( 160 | contract=contract, topics=topics, log_data=log_data, log_index=log_index 161 | ) 162 | 163 | 164 | class W3CallTree(BaseModel): 165 | tx_hash: str 166 | chain_id: str 167 | type: str 168 | from_address: str 169 | to_address: Optional[str] 170 | input: str 171 | output: str 172 | value: Optional[str] 173 | time: Optional[str] 174 | gas: Optional[str] 175 | gasUsed: Optional[str] 176 | error: Optional[str] 177 | calls: list = [] 178 | 179 | def to_object(self) -> Call: 180 | from_address = self.from_address 181 | to_address = self.to_address 182 | call_value = int(self.value, 16) if self.value else 0 183 | call_type = self.type.lower() 184 | call_data = self.input 185 | return_value = self.output 186 | gas_used = int(self.gasUsed, 16) if self.gasUsed else None 187 | call_gas = int(self.gas, 16) if self.gas else None 188 | status = self.error is None 189 | error = self.error 190 | 191 | call = Call( 192 | call_type=call_type, 193 | from_address=from_address, 194 | to_address=to_address, 195 | call_value=call_value, 196 | call_data=call_data, 197 | return_value=return_value, 198 | gas_used=gas_used, 199 | call_gas=call_gas, 200 | status=status, 201 | error=error, 202 | ) 203 | 204 | for child_call in self.calls: 205 | call.subcalls.append(child_call.to_object()) 206 | 207 | return call 208 | -------------------------------------------------------------------------------- /ethtx/providers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from .ens_provider import ENSProvider 18 | from .etherscan import EtherscanProvider 19 | from .signature_provider import FourByteProvider 20 | from .web3_provider import Web3Provider 21 | -------------------------------------------------------------------------------- /ethtx/providers/ens_provider.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | import logging 18 | from abc import ABC, abstractmethod 19 | from typing import Any, TypeVar, Type 20 | 21 | from ens import ENS 22 | from web3 import Web3, exceptions 23 | 24 | log = logging.getLogger(__name__) 25 | 26 | T = TypeVar("T") 27 | 28 | 29 | class ENSProviderBase(ABC): 30 | @abstractmethod 31 | def name(self, provider: Type[T], address: Any): 32 | ... 33 | 34 | @abstractmethod 35 | def address(self, provider: Type[T], name: Any): 36 | ... 37 | 38 | 39 | class Web3ENSProvider(ENSProviderBase): 40 | ns: ENS 41 | 42 | def name(self, provider: Web3, address: str) -> str: 43 | ns = self._set_provider(provider) 44 | check_sum_address = Web3.to_checksum_address(address) 45 | 46 | try: 47 | name = ns.name(address=check_sum_address) 48 | except exceptions.BadFunctionCallOutput: 49 | log.warning( 50 | "ENS name not found for address: %s. There is no code associated with this address.", 51 | address, 52 | ) 53 | name = None 54 | 55 | if name: 56 | log.info("ENS resolved an address: %s to name: %s", address, name) 57 | 58 | return name if name else address 59 | 60 | def address(self, provider: Web3, name: str) -> str: 61 | ns = self._set_provider(provider) 62 | address = ns.address(name=name) 63 | if address: 64 | log.info("ENS resolved name: %s to address: %s", name, address) 65 | 66 | return address if address else name 67 | 68 | def _set_provider(self, provider: Web3) -> ENS: 69 | return ENS.from_web3(provider) 70 | 71 | 72 | ENSProvider = Web3ENSProvider() 73 | -------------------------------------------------------------------------------- /ethtx/providers/etherscan/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from .etherscan_provider import EtherscanProvider 18 | -------------------------------------------------------------------------------- /ethtx/providers/etherscan/client.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | import logging 18 | from collections import OrderedDict 19 | from typing import Dict, Optional 20 | 21 | import requests 22 | 23 | from ethtx.exceptions import ProcessingException 24 | 25 | log = logging.getLogger(__name__) 26 | 27 | 28 | class EtherscanClient: 29 | MODULE = "module=" 30 | ACTION = "&action=" 31 | CONTRACT_ADDRESS = "&contractaddress=" 32 | ADDRESS = "&address=" 33 | OFFSET = "&offset=" 34 | PAGE = "&page=" 35 | SORT = "&sort=" 36 | BLOCK_TYPE = "&blocktype=" 37 | TO = "&to=" 38 | VALUE = "&value=" 39 | DATA = "&data=" 40 | POSITION = "&position=" 41 | HEX = "&hex=" 42 | GAS_PRICE = "&gasPrice=" 43 | GAS = "&gas=" 44 | START_BLOCK = "&startblock=" 45 | END_BLOCK = "&endblock=" 46 | BLOCKNO = "&blockno=" 47 | TXHASH = "&txhash=" 48 | TAG = "&tag=" 49 | BOOLEAN = "&boolean=" 50 | INDEX = "&index=" 51 | API_KEY = "&apikey=" 52 | 53 | url_dict: OrderedDict = {} 54 | 55 | def __init__( 56 | self, 57 | api_key: str, 58 | nodes: Dict[str, str], 59 | default_chain_id: Optional[str] = None, 60 | ): 61 | self.api_key = api_key 62 | self.endpoints = nodes 63 | self.default_chain = default_chain_id 64 | 65 | self.http = requests.session() 66 | self.http.headers.update({"User-Agent": "API"}) 67 | 68 | self.url_dict = OrderedDict( 69 | [ 70 | (self.MODULE, ""), 71 | (self.ADDRESS, ""), 72 | (self.ACTION, ""), 73 | (self.OFFSET, ""), 74 | (self.PAGE, ""), 75 | (self.SORT, ""), 76 | (self.BLOCK_TYPE, ""), 77 | (self.TO, ""), 78 | (self.VALUE, ""), 79 | (self.DATA, ""), 80 | (self.POSITION, ""), 81 | (self.HEX, ""), 82 | (self.GAS_PRICE, ""), 83 | (self.GAS, ""), 84 | (self.START_BLOCK, ""), 85 | (self.END_BLOCK, ""), 86 | (self.BLOCKNO, ""), 87 | (self.TXHASH, ""), 88 | (self.TAG, ""), 89 | (self.BOOLEAN, ""), 90 | (self.INDEX, ""), 91 | (self.API_KEY, api_key), 92 | ] 93 | ) 94 | 95 | def build_url(self, chain_id: str, url_dict: OrderedDict) -> str: 96 | return ( 97 | self.endpoints[chain_id] 98 | + "?" 99 | + "".join([param + val if val else "" for param, val in url_dict.items()]) 100 | ) 101 | 102 | def _get_chain_id(self, chain_id) -> str: 103 | _id = chain_id or self.default_chain 104 | 105 | if _id is None: 106 | raise ProcessingException( 107 | "chain_id must be provided as argument or constructor default" 108 | ) 109 | return _id 110 | -------------------------------------------------------------------------------- /ethtx/providers/etherscan/etherscan_provider.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from typing import Dict, Optional 18 | 19 | from .contracts import EtherscanContract 20 | 21 | 22 | class EtherscanProvider: 23 | api_key: str 24 | endpoints: Dict[str, str] 25 | default_chain: Optional[str] 26 | 27 | def __init__( 28 | self, 29 | api_key: str, 30 | nodes: Dict[str, str], 31 | default_chain_id: Optional[str] = None, 32 | ): 33 | self.api_key = api_key 34 | self.endpoints = nodes 35 | self.default_chain = default_chain_id 36 | 37 | self._contract = EtherscanContract( 38 | api_key=self.api_key, 39 | nodes=self.endpoints, 40 | default_chain_id=self.default_chain, 41 | ) 42 | 43 | @property 44 | def contract(self) -> EtherscanContract: 45 | return self._contract 46 | -------------------------------------------------------------------------------- /ethtx/providers/node/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from .pool import NodeConnectionPool 18 | -------------------------------------------------------------------------------- /ethtx/providers/node/connection_base.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from dataclasses import dataclass 18 | 19 | 20 | @dataclass 21 | class NodeConnection: 22 | chain: str 23 | url: str 24 | poa: bool 25 | 26 | def __repr__(self) -> str: 27 | return f"" 28 | 29 | def __iter__(self): 30 | return iter(self.__dict__.items()) 31 | -------------------------------------------------------------------------------- /ethtx/providers/node/pool.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from typing import Dict, List 18 | 19 | from .connection_base import NodeConnection 20 | 21 | 22 | class NodeConnectionPool: 23 | def __init__(self, nodes: Dict[str, dict]): 24 | self._connections: List[NodeConnection] = [] 25 | 26 | self._set_connections(nodes) 27 | 28 | def __len__(self) -> int: 29 | return len(self._connections) 30 | 31 | @property 32 | def connections(self) -> List[NodeConnection]: 33 | return self._connections 34 | 35 | def add_connection(self, connection: NodeConnection) -> None: 36 | if not isinstance(connection, NodeConnection): 37 | raise ValueError("Value is not instance of NodeBase") 38 | 39 | self._connections.append(connection) 40 | 41 | def get_connection(self, chain: str) -> List[NodeConnection]: 42 | return [ 43 | connection for connection in self._connections if connection.chain == chain 44 | ] 45 | 46 | def _set_connections(self, nodes) -> None: 47 | for chain, node_params in nodes.items(): 48 | nodes: List[str] = list(node_params.values())[0].split(",") 49 | poa: bool = list(node_params.values())[1] 50 | for url in nodes: 51 | self.add_connection( 52 | NodeConnection(chain=chain, url=url.strip(), poa=poa) 53 | ) 54 | -------------------------------------------------------------------------------- /ethtx/providers/semantic_providers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from .base import ISemanticsDatabase 18 | from .database import MongoSemanticsDatabase 19 | from .repository import SemanticsRepository 20 | -------------------------------------------------------------------------------- /ethtx/providers/semantic_providers/base.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from abc import ABC 18 | from typing import Dict, Optional, Any, List 19 | 20 | 21 | class ISemanticsDatabase(ABC): 22 | """Semantics Database. Represents raw interface required to be 23 | implemented by a database that provides persistent 24 | data about semantics""" 25 | 26 | def get_address_semantics(self, chain_id: str, address: str) -> Optional[Dict]: 27 | ... 28 | 29 | def get_contract_semantics(self, code_hash: str) -> Optional[Dict]: 30 | ... 31 | 32 | def get_signature_semantics(self, signature_hash: str) -> Optional[List[Dict]]: 33 | ... 34 | 35 | def insert_contract(self, contract: dict, update_if_exist: bool = False) -> Any: 36 | ... 37 | 38 | def insert_address(self, address: dict, update_if_exist: bool = False) -> Any: 39 | ... 40 | 41 | def insert_signature(self, signature, update_if_exist: bool = False) -> Any: 42 | ... 43 | 44 | def delete_semantics_by_address(self, chain_id: str, address: str) -> None: 45 | ... 46 | -------------------------------------------------------------------------------- /ethtx/providers/semantic_providers/const.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from enum import Enum 18 | 19 | 20 | class MongoCollections(str, Enum): 21 | ADDRESSES = "addresses" 22 | CONTRACTS = "contracts" 23 | SIGNATURES = "signatures" 24 | -------------------------------------------------------------------------------- /ethtx/providers/semantic_providers/database.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | import logging 18 | from typing import Dict, Optional 19 | 20 | from bson import ObjectId 21 | from pymongo.collection import Collection 22 | from pymongo.cursor import Cursor 23 | from pymongo.database import Database as MongoDatabase 24 | 25 | from .base import ISemanticsDatabase 26 | from .const import MongoCollections 27 | from ...utils.cache_tools import cache 28 | 29 | log = logging.getLogger(__name__) 30 | 31 | 32 | class MongoSemanticsDatabase(ISemanticsDatabase): 33 | _db: MongoDatabase 34 | 35 | _addresses: Collection 36 | _contracts: Collection 37 | _signatures: Collection 38 | 39 | def __init__(self, db: MongoDatabase): 40 | self._db = db 41 | 42 | self._addresses = None 43 | self._contracts = None 44 | self._signatures = None 45 | 46 | self._init_collections() 47 | 48 | def get_collection_count(self) -> int: 49 | return len(self._db.list_collection_names()) 50 | 51 | @cache 52 | def get_address_semantics(self, chain_id: str, address: str) -> Dict: 53 | return self._addresses.find_one({"chain_id": chain_id, "address": address}) 54 | 55 | def get_signature_semantics(self, signature_hash: str) -> Cursor: 56 | return self._signatures.find({"signature_hash": signature_hash}) 57 | 58 | def insert_signature( 59 | self, signature: dict, update_if_exist: Optional[bool] = False 60 | ) -> Optional[ObjectId]: 61 | if update_if_exist: 62 | updated_signature = self._signatures.replace_one( 63 | {"_id": signature["_id"]}, signature, upsert=True 64 | ) 65 | return ( 66 | None 67 | if updated_signature.modified_count 68 | else updated_signature.upserted_id 69 | ) 70 | 71 | inserted_signature = self._signatures.insert_one(signature) 72 | return inserted_signature.inserted_id 73 | 74 | @cache 75 | def get_contract_semantics(self, code_hash: str) -> Dict: 76 | """Contract hashes are always the same, no mather what chain we use, so there is no need 77 | to use chain_id""" 78 | return self._contracts.find_one({"code_hash": code_hash}) 79 | 80 | def insert_contract( 81 | self, contract: Dict, update_if_exist: Optional[bool] = False 82 | ) -> Optional[ObjectId]: 83 | if update_if_exist: 84 | updated_contract = self._contracts.replace_one( 85 | {"code_hash": contract["code_hash"]}, contract, upsert=True 86 | ) 87 | 88 | return ( 89 | None 90 | if updated_contract.modified_count 91 | else updated_contract.upserted_id 92 | ) 93 | 94 | inserted_contract = self._contracts.insert_one(contract) 95 | return inserted_contract.inserted_id 96 | 97 | def insert_address( 98 | self, address: Dict, update_if_exist: Optional[bool] = False 99 | ) -> Optional[ObjectId]: 100 | if update_if_exist: 101 | updated_address = self._addresses.replace_one( 102 | {"chain_id": address["chain_id"], "address": address["address"]}, 103 | address, 104 | upsert=True, 105 | ) 106 | return ( 107 | None if updated_address.modified_count else updated_address.upserted_id 108 | ) 109 | 110 | inserted_address = self._addresses.insert_one(address) 111 | return inserted_address.inserted_id 112 | 113 | def _init_collections(self) -> None: 114 | for mongo_collection in MongoCollections: 115 | self.__setattr__(f"_{mongo_collection}", self._db[mongo_collection]) 116 | 117 | def delete_semantics_by_address(self, chain_id: str, address: str) -> None: 118 | address_semantics = self.get_address_semantics(chain_id, address) 119 | 120 | if not address_semantics: 121 | return 122 | 123 | codehash = address_semantics["contract"] 124 | # contract_semantics = self.get_contract_semantics(codehash) 125 | 126 | self._addresses.delete_one({"chain_id": chain_id, "address": address}) 127 | self._contracts.delete_one({"code_hash": codehash}) 128 | 129 | self.get_contract_semantics.cache_clear() 130 | self.get_address_semantics.cache_clear() 131 | -------------------------------------------------------------------------------- /ethtx/providers/signature_provider.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | import logging 18 | from abc import ABC, abstractmethod 19 | from typing import Dict, List, Any, Iterator, TypedDict, Union, Tuple, Optional 20 | 21 | import requests 22 | 23 | log = logging.getLogger(__name__) 24 | 25 | 26 | class SignatureReturnType(TypedDict): 27 | name: str 28 | args: Union[List[str], Tuple[str]] 29 | 30 | 31 | class SignatureProvider(ABC): 32 | @abstractmethod 33 | def list_function_signatures(self, filters: Dict): 34 | ... 35 | 36 | @abstractmethod 37 | def list_event_signatures(self, filters: Dict): 38 | ... 39 | 40 | @abstractmethod 41 | def get_function(self, signature: str): 42 | ... 43 | 44 | @abstractmethod 45 | def get_event(self, signature: str): 46 | ... 47 | 48 | 49 | class FourByteProvider(SignatureProvider): 50 | API_URL: str = "https://www.4byte.directory/api/v1" 51 | FUNCTION_ENDPOINT: str = "signatures" 52 | EVENT_ENDPOINT: str = "event-signatures" 53 | 54 | def list_function_signatures(self, filters: Dict = None) -> List[Dict]: 55 | return self._get_all(endpoint=self.FUNCTION_ENDPOINT, filters=filters) 56 | 57 | def list_event_signatures(self, filters: Dict = None) -> List[Dict]: 58 | return self._get_all(endpoint=self.EVENT_ENDPOINT, filters=filters) 59 | 60 | def get_function(self, signature: str) -> Iterator[Optional[SignatureReturnType]]: 61 | if signature == "0x": 62 | raise ValueError(f"Signature can not be: {signature}") 63 | 64 | data = self._get_all( 65 | endpoint=self.FUNCTION_ENDPOINT, filters={"hex_signature": signature} 66 | ) 67 | 68 | for function in reversed(data): 69 | if parsed := self._parse_text_signature_response(function): 70 | yield parsed 71 | 72 | def get_event(self, signature: str) -> Iterator[Optional[SignatureReturnType]]: 73 | if signature == "0x": 74 | raise ValueError(f"Signature can not be: {signature}") 75 | 76 | data = self._get_all( 77 | endpoint=self.EVENT_ENDPOINT, filters={"hex_signature": signature} 78 | ) 79 | 80 | for event in reversed(data): 81 | if parsed := self._parse_text_signature_response(event): 82 | yield parsed 83 | 84 | def url(self, endpoint: str) -> str: 85 | return f"{self.API_URL}/{endpoint}/" 86 | 87 | def _get_all(self, endpoint: str, filters: Dict = None) -> List[Dict]: 88 | page = 1 89 | results = [] 90 | 91 | while True: 92 | res = self._get(endpoint, page, filters) 93 | next_url = res.get("next") 94 | results.extend(res.get("results", [])) 95 | 96 | if not next_url: 97 | break 98 | page += 1 99 | 100 | return results 101 | 102 | def _get( 103 | self, endpoint: str, page: int = 0, filters: Dict = None 104 | ) -> Dict[str, Any]: 105 | if filters is None: 106 | filters = {} 107 | 108 | if page: 109 | filters["page"] = page 110 | 111 | try: 112 | response = requests.get(self.url(endpoint), params=filters, timeout=3) 113 | return response.json() 114 | 115 | except requests.exceptions.RequestException as e: 116 | log.warning("Could not get data from 4byte.directory: %s", e) 117 | return {} 118 | 119 | except Exception as e: 120 | log.warning("Unexpected error from 4byte.directory: %s", e) 121 | return {} 122 | 123 | def _parse_text_signature_response( 124 | self, data: Dict 125 | ) -> Optional[SignatureReturnType]: 126 | text_sig = data.get("text_signature", "") 127 | 128 | name = text_sig.split("(")[0] if text_sig else "" 129 | 130 | types = ( 131 | text_sig[text_sig.find("(") + 1 : text_sig.rfind(")")] if text_sig else "" 132 | ) 133 | 134 | if not name and not types: 135 | return None 136 | 137 | if "(" in types: 138 | args = tuple(types[types.find("(") + 1 : types.rfind(")")].split(",")) 139 | if any("(" in arg for arg in args): 140 | log.warning( 141 | "Could not parse %s signature: %s", 142 | data.get("hex_signature"), 143 | data.get("text_signature"), 144 | ) 145 | return None 146 | else: 147 | args = list(filter(None, types.split(","))) 148 | 149 | return {"name": name, "args": args} 150 | 151 | 152 | FourByteProvider = FourByteProvider() 153 | -------------------------------------------------------------------------------- /ethtx/semantics/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/ethtx/semantics/__init__.py -------------------------------------------------------------------------------- /ethtx/semantics/base.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from abc import ABC, abstractmethod 18 | from typing import TypeVar, Dict 19 | 20 | 21 | class Base(ABC): 22 | """ 23 | Semantics Base class. 24 | All semantics should have `code_hash: str` and `contract_semantics: dict` class attribute. 25 | Inherit this class if you want to implement new semantic. 26 | """ 27 | 28 | @property 29 | @abstractmethod 30 | def contract_semantics(self) -> str: 31 | ... 32 | 33 | @property 34 | @abstractmethod 35 | def code_hash(self) -> dict: 36 | ... 37 | 38 | 39 | BaseType = TypeVar( 40 | "BaseType", bound=Dict[Base.code_hash.fget, Base.contract_semantics.fget] 41 | ) 42 | -------------------------------------------------------------------------------- /ethtx/semantics/protocols/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/ethtx/semantics/protocols/__init__.py -------------------------------------------------------------------------------- /ethtx/semantics/protocols/anonymous.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from ethtx.models.semantics_model import ( 18 | EventSemantics, 19 | ParameterSemantics, 20 | TransformationSemantics, 21 | ) 22 | 23 | lognote_event_v1 = EventSemantics( 24 | signature="0xd3ff30f94bb4ebb4f3d773ea26b6efc7328b9766f99f19dff6f01392138be46d", 25 | anonymous=False, 26 | name="LogNote", 27 | parameters=[ 28 | ParameterSemantics( 29 | parameter_name="sig", parameter_type="bytes4", components=[], indexed=True 30 | ), 31 | ParameterSemantics( 32 | parameter_name="arg1", parameter_type="bytes32", components=[], indexed=True 33 | ), 34 | ParameterSemantics( 35 | parameter_name="arg2", parameter_type="bytes32", components=[], indexed=True 36 | ), 37 | ParameterSemantics( 38 | parameter_name="arg3", parameter_type="bytes32", components=[], indexed=True 39 | ), 40 | ParameterSemantics( 41 | parameter_name="data", 42 | parameter_type="bytes", 43 | components=[], 44 | indexed=False, 45 | dynamic=True, 46 | ), 47 | ], 48 | ) 49 | 50 | lognote_transformation_v1 = { 51 | "sig": TransformationSemantics(transformed_type="ignore"), 52 | "arg1": TransformationSemantics(transformed_type="ignore"), 53 | "arg2": TransformationSemantics(transformed_type="ignore"), 54 | "arg3": TransformationSemantics(transformed_type="ignore"), 55 | "data": TransformationSemantics( 56 | transformed_type="call", transformation="decode_call(__contract__, data)" 57 | ), 58 | } 59 | 60 | lognote_event_v2 = EventSemantics( 61 | signature="0xd3d8bec38a91a5f4411247483bc030a174e77cda9c0351924c759f41453aa5e8", 62 | anonymous=False, 63 | name="LogNote", 64 | parameters=[ 65 | ParameterSemantics( 66 | parameter_name="sig", parameter_type="bytes4", components=[], indexed=True 67 | ), 68 | ParameterSemantics( 69 | parameter_name="user", parameter_type="address", components=[], indexed=True 70 | ), 71 | ParameterSemantics( 72 | parameter_name="arg1", parameter_type="bytes32", components=[], indexed=True 73 | ), 74 | ParameterSemantics( 75 | parameter_name="arg2", parameter_type="bytes32", components=[], indexed=True 76 | ), 77 | ParameterSemantics( 78 | parameter_name="data", 79 | parameter_type="bytes", 80 | components=[], 81 | indexed=False, 82 | dynamic=True, 83 | ), 84 | ], 85 | ) 86 | 87 | lognote_transformation_v2 = { 88 | "sig": TransformationSemantics(transformed_type="ignore"), 89 | "arg1": TransformationSemantics(transformed_type="ignore"), 90 | "arg2": TransformationSemantics(transformed_type="ignore"), 91 | "data": TransformationSemantics( 92 | transformed_type="call", transformation="decode_call(__contract__, data)" 93 | ), 94 | } 95 | 96 | lognote_event_v3 = EventSemantics( 97 | signature="0x644843f351d3fba4abcd60109eaff9f54bac8fb8ccf0bab941009c21df21cf31", 98 | anonymous=False, 99 | name="LogNote", 100 | parameters=[ 101 | ParameterSemantics( 102 | parameter_name="sig", parameter_type="bytes4", components=[], indexed=True 103 | ), 104 | ParameterSemantics( 105 | parameter_name="guy", parameter_type="address", components=[], indexed=True 106 | ), 107 | ParameterSemantics( 108 | parameter_name="foo", parameter_type="bytes32", components=[], indexed=True 109 | ), 110 | ParameterSemantics( 111 | parameter_name="bar", parameter_type="bytes32", components=[], indexed=True 112 | ), 113 | ParameterSemantics( 114 | parameter_name="wad", parameter_type="uint256", components=[], indexed=False 115 | ), 116 | ParameterSemantics( 117 | parameter_name="fax", 118 | parameter_type="bytes", 119 | components=[], 120 | indexed=False, 121 | dynamic=True, 122 | ), 123 | ], 124 | ) 125 | 126 | lognote_transformation_v3 = { 127 | "sig": TransformationSemantics(transformed_type="ignore"), 128 | "foo": TransformationSemantics(transformed_type="ignore"), 129 | "bar": TransformationSemantics(transformed_type="ignore"), 130 | "wad": TransformationSemantics(transformation="wad / 10**18"), 131 | "fax": TransformationSemantics( 132 | transformed_type="call", transformation="decode_call(__contract__, fax)" 133 | ), 134 | } 135 | 136 | logcall_event = EventSemantics( 137 | signature="0x25fce1fe01d9b241fda40b2152ddd6f4ba063fcfb3c2c81dddf84ee20d3f341f", 138 | anonymous=False, 139 | name="LOG_CALL", 140 | parameters=[ 141 | ParameterSemantics( 142 | parameter_name="sig", parameter_type="bytes4", components=[], indexed=True 143 | ), 144 | ParameterSemantics( 145 | parameter_name="caller", 146 | parameter_type="address", 147 | components=[], 148 | indexed=True, 149 | ), 150 | ParameterSemantics( 151 | parameter_name="data", 152 | parameter_type="bytes", 153 | components=[], 154 | indexed=False, 155 | dynamic=True, 156 | ), 157 | ], 158 | ) 159 | 160 | logcall_transformation = { 161 | "sig": TransformationSemantics(transformed_type="ignore"), 162 | "data": TransformationSemantics( 163 | transformed_type="call", transformation="decode_call(__contract__, data)" 164 | ), 165 | } 166 | 167 | anonymous_events = { 168 | lognote_event_v1.signature: lognote_transformation_v1, 169 | lognote_event_v2.signature: lognote_transformation_v2, 170 | lognote_event_v3.signature: lognote_transformation_v3, 171 | logcall_event.signature: logcall_transformation, 172 | } 173 | -------------------------------------------------------------------------------- /ethtx/semantics/protocols/maker/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/ethtx/semantics/protocols/maker/__init__.py -------------------------------------------------------------------------------- /ethtx/semantics/protocols/maker/cdp_manager.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from ethtx.models.semantics_model import TransformationSemantics 18 | from ethtx.semantics.base import Base 19 | 20 | 21 | class CDPManager(Base): 22 | code_hash = "0xdc70464baedd675aadf1b109ac8ec36f78adb5db19bc087d2a70208305c786b1" 23 | contract_semantics = dict( 24 | transformations={ 25 | "0x6090dec5": { # open 26 | "ilk": TransformationSemantics( 27 | transformed_type="string", transformation="string_from_bytes(ilk)" 28 | ) 29 | }, 30 | "0x45e6bdcd": { # frob 31 | "dart": TransformationSemantics(transformation="dart / 10**18"), 32 | "dink": TransformationSemantics(transformation="dink / 10**18"), 33 | }, 34 | "0xf9f30db6": { # move 35 | "rad": TransformationSemantics(transformation="rad / 10**45") 36 | }, 37 | "0x18af4d60": { # flux 38 | "ilk": TransformationSemantics( 39 | transformed_type="string", transformation="string_from_bytes(ilk)" 40 | ), 41 | "wad": TransformationSemantics(transformation="wad / 10**18"), 42 | }, 43 | "0x2c2cb9fd": { # ilks 44 | "__output0__": TransformationSemantics( 45 | transformed_type="string", 46 | transformation="string_from_bytes(__output0__)", 47 | ) 48 | }, 49 | } 50 | ) 51 | -------------------------------------------------------------------------------- /ethtx/semantics/protocols/maker/dai.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from ethtx.models.semantics_model import TransformationSemantics 18 | from ethtx.semantics.base import Base 19 | 20 | 21 | class Dai(Base): 22 | code_hash = "0x4e36f96ee1667a663dfaac57c4d185a0e369a3a217e0079d49620f34f85d1ac7" 23 | contract_semantics = dict( 24 | transformations={ 25 | "0x40c10f19": { # mint 26 | "wad": TransformationSemantics(transformation="wad / 10**18") 27 | }, 28 | "0x9dc29fac": { # burn 29 | "wad": TransformationSemantics(transformation="wad / 10**18") 30 | }, 31 | } 32 | ) 33 | -------------------------------------------------------------------------------- /ethtx/semantics/protocols/maker/dai_join.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from ethtx.models.semantics_model import TransformationSemantics 18 | from ethtx.semantics.base import Base 19 | 20 | 21 | class DaiJoin(Base): 22 | code_hash = "0x76dac2e14412f6ee4c5ca566296c954598168738955fa974cb3302cb73b95f0f" 23 | 24 | contract_semantics = dict( 25 | transformations={ 26 | "0x3b4da69f": { # join 27 | "wad": TransformationSemantics(transformation="wad / 10**18") 28 | }, 29 | "0xef693bed": { # exit 30 | "wad": TransformationSemantics(transformation="wad / 10**18") 31 | }, 32 | } 33 | ) 34 | -------------------------------------------------------------------------------- /ethtx/semantics/protocols/maker/ds_proxy.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from ethtx.models.semantics_model import TransformationSemantics 18 | from ethtx.semantics.base import Base 19 | 20 | 21 | class DsProxy(Base): 22 | code_hash = "0x27c02a1a822222c2ad6a9a01021c98abf05dbe6d19540035756ef97697ed41d0" 23 | contract_semantics = dict( 24 | transformations={ 25 | "0x1cff79cd": { # execute 26 | "_data": TransformationSemantics( 27 | transformed_type="call", 28 | transformation="decode_call(_target, _data)", 29 | ) 30 | } 31 | } 32 | ) 33 | -------------------------------------------------------------------------------- /ethtx/semantics/protocols/maker/gem_join.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from ethtx.models.semantics_model import TransformationSemantics 18 | from ethtx.semantics.base import Base 19 | 20 | 21 | class GemJoin(Base): 22 | code_hash = "0x8661c673677de206fda20eb1883063ea4a827c2ac1c7f232dd30e276d20874fa" 23 | contract_semantics = dict( 24 | transformations={ 25 | "0xc5ce281e": { # ilk 26 | "__output0__": TransformationSemantics( 27 | transformed_type="string", 28 | transformation="string_from_bytes(__output0__)", 29 | ) 30 | }, 31 | "0x3b4da69f": { # join 32 | "wad": TransformationSemantics(transformation="wad / 10**18") 33 | }, 34 | "0xef693bed": { # exit 35 | "wad": TransformationSemantics(transformation="wad / 10**18") 36 | }, 37 | } 38 | ) 39 | -------------------------------------------------------------------------------- /ethtx/semantics/protocols/maker/jug.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from ethtx.models.semantics_model import TransformationSemantics 18 | from ethtx.semantics.base import Base 19 | 20 | 21 | class Jug(Base): 22 | code_hash = "0x79e3139c72f7ad381e81a7ff8ae63b5998f0778c1c025c6095a6f878771dc729" 23 | contract_semantics = dict( 24 | transformations={ 25 | "0x44e2a5a8": { # drip 26 | "ilk": TransformationSemantics( 27 | transformed_type="string", transformation="string_from_bytes(ilk)" 28 | ) 29 | } 30 | } 31 | ) 32 | -------------------------------------------------------------------------------- /ethtx/semantics/protocols/maker/vat.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from ethtx.models.semantics_model import TransformationSemantics 18 | from ethtx.semantics.base import Base 19 | 20 | 21 | class Vat(Base): 22 | code_hash = "0x808b98f6475736d56c978e4fb476175ecd9d7abdab0797017fc10c7f46311a59" 23 | contract_semantics = dict( 24 | transformations={ 25 | "0x2424be5c": { # urns 26 | "art": TransformationSemantics(transformation="art / 10**18"), 27 | "ilk": TransformationSemantics( 28 | transformed_type="string", transformation="string_from_bytes(ilk)" 29 | ), 30 | }, 31 | "ink": TransformationSemantics(transformation="ink / 10**18"), 32 | "0x3b663195": { # init 33 | "ilk": TransformationSemantics( 34 | transformed_type="string", transformation="string_from_bytes(ilk)" 35 | ) 36 | }, 37 | "0x6111be2e": { # flux 38 | "ilk": TransformationSemantics( 39 | transformed_type="string", transformation="string_from_bytes(ilk)" 40 | ), 41 | "wad": TransformationSemantics(transformation="wad / 10**18"), 42 | }, 43 | "0x7cdd3fde": { # slip 44 | "ilk": TransformationSemantics( 45 | transformed_type="string", transformation="string_from_bytes(ilk)" 46 | ), 47 | "wad": TransformationSemantics(transformation="wad / 10**18"), 48 | }, 49 | "0x76088703": { # frob 50 | "dart": TransformationSemantics(transformation="dart / 10**18"), 51 | "dink": TransformationSemantics(transformation="dink / 10**18"), 52 | "i": TransformationSemantics( 53 | transformed_type="string", transformation="string_from_bytes(i)" 54 | ), 55 | }, 56 | "0x870c616d": { # fork 57 | "dart": TransformationSemantics(transformation="dart / 10**18"), 58 | "dink": TransformationSemantics(transformation="dink / 10**18"), 59 | "i": TransformationSemantics( 60 | transformed_type="string", transformation="string_from_bytes(i)" 61 | ), 62 | }, 63 | "0xb65337df": { # fold 64 | "i": TransformationSemantics( 65 | transformed_type="string", transformation="string_from_bytes(i)" 66 | ), 67 | "rate": TransformationSemantics(transformation="rate / 10**27"), 68 | }, 69 | "0xbb35783b": { # move 70 | "rad": TransformationSemantics(transformation="rad / 10**45") 71 | }, 72 | "0xd9638d36": { # ilks 73 | "__input0__": TransformationSemantics( 74 | transformed_type="string", 75 | transformation="string_from_bytes(__input0__)", 76 | ), 77 | "Art": TransformationSemantics(transformation="Art / 10**18"), 78 | "dust": TransformationSemantics(transformation="dust / 10**45"), 79 | "line": TransformationSemantics(transformation="line / 10**45"), 80 | "rate": TransformationSemantics(transformation="rate / 10**27"), 81 | "spot": TransformationSemantics(transformation="spot / 10**27"), 82 | }, 83 | } 84 | ) 85 | -------------------------------------------------------------------------------- /ethtx/semantics/protocols/wrappers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/ethtx/semantics/protocols/wrappers/__init__.py -------------------------------------------------------------------------------- /ethtx/semantics/protocols/wrappers/weth.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from ethtx.models.semantics_model import TransformationSemantics 18 | from ethtx.semantics.base import Base 19 | 20 | 21 | class Weth(Base): 22 | code_hash = "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" 23 | contract_semantics = dict( 24 | transformations={ 25 | "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c": { # Deposit 26 | "wad": TransformationSemantics(transformation="wad / 10**18") 27 | }, 28 | "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65": { # Withdrawal 29 | "wad": TransformationSemantics(transformation="wad / 10**18") 30 | }, 31 | "0x2e1a7d4d": { # withdraw 32 | "wad": TransformationSemantics(transformation="wad / 10**18") 33 | }, 34 | } 35 | ) 36 | -------------------------------------------------------------------------------- /ethtx/semantics/protocols_router.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | import logging 18 | 19 | from ..models.semantics_model import ContractSemantics 20 | from ..semantics.router import Router 21 | 22 | log = logging.getLogger(__name__) 23 | 24 | 25 | def amend_contract_semantics(semantics: ContractSemantics, router_=Router()): 26 | if semantics.code_hash in router_: 27 | try: 28 | semantics_updates = router_[semantics.code_hash] 29 | if "name" in semantics_updates: 30 | semantics.name = semantics_updates["name"] 31 | if "events" in semantics_updates: 32 | for signature, event_semantics in semantics_updates["events"].items(): 33 | semantics.events[signature] = event_semantics 34 | if "functions" in semantics_updates: 35 | for signature, function_semantics in semantics_updates[ 36 | "functions" 37 | ].items(): 38 | semantics.functions[signature] = function_semantics 39 | if "transformations" in semantics_updates: 40 | for signature, transformation in semantics_updates[ 41 | "transformations" 42 | ].items(): 43 | semantics.transformations[signature] = transformation 44 | 45 | except Exception as exc: 46 | log.warning( 47 | "Semantics update load for %s failed.", 48 | semantics.code_hash, 49 | exc_info=exc, 50 | ) 51 | -------------------------------------------------------------------------------- /ethtx/semantics/rollups/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/ethtx/semantics/rollups/__init__.py -------------------------------------------------------------------------------- /ethtx/semantics/rollups/aztec.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | 18 | def decode_rollup_data(data): 19 | def get_32word_at(data, pos): 20 | word_length = 2 * 32 21 | dataStart = pos * 2 22 | return data[dataStart : dataStart + word_length] 23 | 24 | numberOfAssets = 4 25 | txNumPubInputs = 12 26 | rollupNumPubInputs = 10 + numberOfAssets 27 | # public inputs length for of each inner proof tx 28 | txPubInputLength = txNumPubInputs * 32 29 | rollupPubInputLength = rollupNumPubInputs * 32 30 | 31 | rollup = dict( 32 | rollupPubInputLength=rollupPubInputLength, 33 | rollupId=int(get_32word_at(data, 0), 16), 34 | rollupSize=int(get_32word_at(data, int("0x20", 16)), 16), 35 | dataStartIndex=int(get_32word_at(data, int("0x40", 16)), 16), 36 | oldDataRoot=get_32word_at(data, int("0x60", 16)), 37 | newDataRoot=get_32word_at(data, int("0x80", 16)), 38 | oldNullRoot=get_32word_at(data, int("0xa0", 16)), 39 | newNullRoot=get_32word_at(data, int("0xc0", 16)), 40 | oldRootRoot=get_32word_at(data, int("0xe0", 16)), 41 | newRootRoot=get_32word_at(data, int("0x100", 16)), 42 | ) 43 | 44 | rollupSize = int(get_32word_at(data, int("0x20", 16)), 16) 45 | numTxs = 1 if rollupSize == 0 else rollupSize 46 | 47 | proofDataPointer = rollupPubInputLength 48 | operations = [] 49 | for _ in range(numTxs): 50 | proofId = int(get_32word_at(data, proofDataPointer), 16) 51 | publicInput = get_32word_at(data, proofDataPointer + int("0x20", 16)) 52 | publicOutput = get_32word_at(data, proofDataPointer + int("0x40", 16)) 53 | nullifier1 = get_32word_at(data, proofDataPointer + int("0x100", 16)) 54 | 55 | if proofId == 0: 56 | assetId = int(get_32word_at(data, proofDataPointer + int("0x60", 16)), 16) 57 | inputOwner = ( 58 | "0x" + get_32word_at(data, proofDataPointer + int("0x140", 16))[-40:] 59 | ) 60 | outputOwner = ( 61 | "0x" + get_32word_at(data, proofDataPointer + int("0x160", 16))[-40:] 62 | ) 63 | 64 | if assetId == 0: 65 | if int(publicInput, 16): 66 | operation = dict( 67 | type="Deposits", 68 | address=inputOwner, 69 | amount=f"{int(publicInput, 16) / 10 ** 18:,} ETH", 70 | ) 71 | operations.append(operation) 72 | 73 | elif int(publicOutput, 16): 74 | operation = dict( 75 | type="Withdrawals", 76 | address=outputOwner, 77 | amount=f"{int(publicOutput, 16) / 10 ** 18:,} ETH", 78 | ) 79 | operations.append(operation) 80 | 81 | elif int(nullifier1, 16): 82 | operation = dict(type="Private", address="", amount="") 83 | operations.append(operation) 84 | 85 | elif proofId == 1: 86 | address = "0x" + publicInput + publicOutput 87 | operation = dict( 88 | type="Accounts", address=address[:60] + "..." + address[-6:], amount="" 89 | ) 90 | operations.append(operation) 91 | 92 | proofDataPointer = proofDataPointer + txPubInputLength 93 | 94 | return rollup, operations 95 | -------------------------------------------------------------------------------- /ethtx/semantics/router.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | import glob 18 | import importlib 19 | import os 20 | 21 | from ..semantics.base import BaseType, Base 22 | 23 | 24 | class Router: 25 | """ 26 | Semantics router. 27 | Returns all objects withs semantics to include. 28 | """ 29 | 30 | root_dir = os.path.dirname(__file__) 31 | root_module_name = ".".join(__name__.split(".")[:-1]) 32 | 33 | def __new__(cls) -> BaseType: 34 | return cls._get_semantics() 35 | 36 | @classmethod 37 | def _get_semantics(cls) -> BaseType: 38 | """ 39 | Get all available semantics. 40 | Match pattern: 41 | - .py file 42 | - object is a class type 43 | - object is a Base subclass 44 | """ 45 | rv = {} 46 | files = ( 47 | semantic 48 | for semantic in glob.iglob(cls.root_dir + "**/**", recursive=True) 49 | if os.path.isfile(semantic) 50 | and "__" not in semantic 51 | and semantic.endswith(".py") 52 | ) 53 | 54 | for filename in files: 55 | filename = filename.replace("/", ".").replace(".py", "") 56 | imported_module = importlib.import_module( 57 | f"{cls.root_module_name}{filename.split(cls.root_module_name)[-1]}" 58 | ) 59 | for item in dir(imported_module): 60 | obj = getattr(imported_module, item) 61 | if isinstance(obj, type) and issubclass(obj, Base) and obj != Base: 62 | rv[obj.code_hash] = obj.contract_semantics 63 | 64 | return rv 65 | -------------------------------------------------------------------------------- /ethtx/semantics/solidity/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/ethtx/semantics/solidity/__init__.py -------------------------------------------------------------------------------- /ethtx/semantics/solidity/precompiles.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | 18 | from ethtx.models.semantics_model import FunctionSemantics, ParameterSemantics 19 | 20 | precompiles = { 21 | 1: FunctionSemantics( 22 | signature="", 23 | name="ecrecover", 24 | inputs=[ 25 | ParameterSemantics(parameter_name="hash", parameter_type="bytes32"), 26 | ParameterSemantics(parameter_name="v", parameter_type="bytes8"), 27 | ParameterSemantics(parameter_name="r", parameter_type="bytes32"), 28 | ParameterSemantics(parameter_name="s", parameter_type="bytes32"), 29 | ], 30 | outputs=[ParameterSemantics(parameter_name="", parameter_type="address")], 31 | ), 32 | 2: FunctionSemantics( 33 | signature="", 34 | name="sha256", 35 | inputs=[ParameterSemantics(parameter_name="data", parameter_type="raw")], 36 | outputs=[ParameterSemantics(parameter_name="", parameter_type="bytes32")], 37 | ), 38 | 3: FunctionSemantics( 39 | signature="", 40 | name="ripemd160", 41 | inputs=[ParameterSemantics(parameter_name="data", parameter_type="raw")], 42 | outputs=[ParameterSemantics(parameter_name="", parameter_type="bytes32")], 43 | ), 44 | 4: FunctionSemantics( 45 | signature="", 46 | name="datacopy", 47 | inputs=[ParameterSemantics(parameter_name="data", parameter_type="raw")], 48 | outputs=[ParameterSemantics(parameter_name="", parameter_type="raw")], 49 | ), 50 | 5: FunctionSemantics( 51 | signature="", 52 | name="bigModExp", 53 | inputs=[ 54 | ParameterSemantics(parameter_name="base", parameter_type="bytes32"), 55 | ParameterSemantics(parameter_name="exp", parameter_type="bytes32"), 56 | ParameterSemantics(parameter_name="mod", parameter_type="bytes32"), 57 | ], 58 | outputs=[ParameterSemantics(parameter_name="", parameter_type="bytes32")], 59 | ), 60 | 6: FunctionSemantics( 61 | signature="", 62 | name="bn256Add", 63 | inputs=[ 64 | ParameterSemantics(parameter_name="ax", parameter_type="bytes32"), 65 | ParameterSemantics(parameter_name="ay", parameter_type="bytes32"), 66 | ParameterSemantics(parameter_name="bx", parameter_type="bytes32"), 67 | ParameterSemantics(parameter_name="by", parameter_type="bytes32"), 68 | ], 69 | outputs=[ParameterSemantics(parameter_name="", parameter_type="bytes32[2]")], 70 | ), 71 | 7: FunctionSemantics( 72 | signature="", 73 | name="bn256ScalarMul", 74 | inputs=[ 75 | ParameterSemantics(parameter_name="x", parameter_type="bytes32"), 76 | ParameterSemantics(parameter_name="y", parameter_type="bytes32"), 77 | ParameterSemantics(parameter_name="scalar", parameter_type="bytes32"), 78 | ], 79 | outputs=[ParameterSemantics(parameter_name="", parameter_type="bytes32[2]")], 80 | ), 81 | 8: FunctionSemantics( 82 | signature="", 83 | name="bn256Pairing", 84 | inputs=[ParameterSemantics(parameter_name="input", parameter_type="raw")], 85 | outputs=[ParameterSemantics(parameter_name="", parameter_type="bytes32")], 86 | ), 87 | } 88 | -------------------------------------------------------------------------------- /ethtx/semantics/standards/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/ethtx/semantics/standards/__init__.py -------------------------------------------------------------------------------- /ethtx/semantics/standards/eip1969.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from web3 import Web3 18 | 19 | from ethtx.utils.cache_tools import cache 20 | 21 | 22 | @cache 23 | def is_eip1969_proxy(chain, delegator, delegate): 24 | implementation_slot = hex( 25 | int(Web3.keccak(text="eip1967.proxy.implementation").hex(), 16) - 1 26 | ) 27 | try: 28 | implementation = ( 29 | "0x" 30 | + chain.eth.get_storage_at( 31 | Web3.to_checksum_address(delegator), implementation_slot 32 | ).hex()[-40:] 33 | ) 34 | return implementation == delegate 35 | except: 36 | return False 37 | 38 | 39 | @cache 40 | def is_eip1969_beacon_proxy(chain, delegator, delegate): 41 | ibeacon_abi = """[ 42 | { 43 | "inputs": [], 44 | "name": "implementation", 45 | "outputs": [ 46 | { 47 | "internalType": "address", 48 | "name": "", 49 | "type": "address" 50 | } 51 | ], 52 | "stateMutability": "view", 53 | "type": "function" 54 | } 55 | ]""" 56 | 57 | beacon_slot = hex(int(Web3.keccak(text="eip1967.proxy.beacon").hex(), 16) - 1) 58 | try: 59 | beacon = ( 60 | "0x" 61 | + chain.eth.get_storage_at( 62 | Web3.to_checksum_address(delegator), beacon_slot 63 | ).hex()[-40:] 64 | ) 65 | beacon = chain.eth.contract( 66 | address=Web3.to_checksum_address(beacon), abi=ibeacon_abi 67 | ) 68 | implementation = beacon.functions.implementation().call() 69 | return implementation == Web3.to_checksum_address(delegate) 70 | except: 71 | return False 72 | -------------------------------------------------------------------------------- /ethtx/semantics/standards/erc20.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from ethtx.models.semantics_model import ( 18 | EventSemantics, 19 | ParameterSemantics, 20 | TransformationSemantics, 21 | FunctionSemantics, 22 | ) 23 | 24 | erc20_transfer_event = EventSemantics( 25 | signature="0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", 26 | anonymous=False, 27 | name="Transfer", 28 | parameters=[ 29 | ParameterSemantics( 30 | parameter_name="src", parameter_type="address", indexed=True 31 | ), 32 | ParameterSemantics( 33 | parameter_name="dst", parameter_type="address", indexed=True 34 | ), 35 | ParameterSemantics( 36 | parameter_name="value", parameter_type="uint256", indexed=False 37 | ), 38 | ], 39 | ) 40 | 41 | erc20_transfer_event_transformation = { 42 | "__input2__": TransformationSemantics( 43 | transformation="__input2__ / 10**token_decimals(__contract__)" 44 | ) 45 | } 46 | 47 | erc20_approval_event = EventSemantics( 48 | signature="0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", 49 | anonymous=False, 50 | name="Approval", 51 | parameters=[ 52 | ParameterSemantics( 53 | parameter_name="src", parameter_type="address", indexed=True 54 | ), 55 | ParameterSemantics( 56 | parameter_name="dst", parameter_type="address", indexed=True 57 | ), 58 | ParameterSemantics( 59 | parameter_name="value", parameter_type="uint256", indexed=False 60 | ), 61 | ], 62 | ) 63 | 64 | erc20_approval_event_transformation = { 65 | "__input2__": TransformationSemantics( 66 | transformation="__input2__ / 10**token_decimals(__contract__)" 67 | ) 68 | } 69 | 70 | erc20_transfer_function = FunctionSemantics( 71 | signature="0xa9059cbb", 72 | name="transfer", 73 | inputs=[ 74 | ParameterSemantics(parameter_name="recipient", parameter_type="address"), 75 | ParameterSemantics(parameter_name="amount", parameter_type="uint256"), 76 | ], 77 | outputs=[ParameterSemantics(parameter_name="", parameter_type="bool")], 78 | ) 79 | 80 | erc20_transfer_function_transformation = { 81 | "__input1__": TransformationSemantics( 82 | transformation="__input1__ / 10**token_decimals(__contract__)" 83 | ) 84 | } 85 | 86 | erc20_transferFrom_function = FunctionSemantics( 87 | signature="0x23b872dd", 88 | name="transferFrom", 89 | inputs=[ 90 | ParameterSemantics(parameter_name="sender", parameter_type="address"), 91 | ParameterSemantics(parameter_name="recipient", parameter_type="address"), 92 | ParameterSemantics(parameter_name="amount", parameter_type="uint256"), 93 | ], 94 | outputs=[ParameterSemantics(parameter_name="", parameter_type="bool")], 95 | ) 96 | 97 | erc20_transferFrom_function_transformation = { 98 | "__input2__": TransformationSemantics( 99 | transformation="__input2__ / 10**token_decimals(__contract__)" 100 | ) 101 | } 102 | 103 | erc20_approve_function = FunctionSemantics( 104 | signature="0x095ea7b3", 105 | name="approve", 106 | inputs=[ 107 | ParameterSemantics(parameter_name="spender", parameter_type="address"), 108 | ParameterSemantics(parameter_name="amount", parameter_type="uint256"), 109 | ], 110 | outputs=[ParameterSemantics(parameter_name="", parameter_type="bool")], 111 | ) 112 | 113 | erc20_approve_function_transformation = { 114 | "__input1__": TransformationSemantics( 115 | transformation="__input1__ / 10**token_decimals(__contract__)" 116 | ) 117 | } 118 | 119 | erc20_balanceOf_function = FunctionSemantics( 120 | signature="0x70a08231", 121 | name="balanceOf", 122 | inputs=[ParameterSemantics(parameter_name="holder", parameter_type="address")], 123 | outputs=[ParameterSemantics(parameter_name="", parameter_type="uint256")], 124 | ) 125 | 126 | erc20_balanceOf_function_transformation = { 127 | "__output0__": TransformationSemantics( 128 | transformation="__output0__ / 10**token_decimals(__contract__)" 129 | ) 130 | } 131 | 132 | erc20_totalSupply_function = FunctionSemantics( 133 | signature="0x18160ddd", 134 | name="totalSupply", 135 | inputs=[], 136 | outputs=[ParameterSemantics(parameter_name="", parameter_type="uint256")], 137 | ) 138 | 139 | erc20_totalSupply_function_transformation = { 140 | "__output0__": TransformationSemantics( 141 | transformation="__output0__ / 10**token_decimals(__contract__)" 142 | ) 143 | } 144 | 145 | ERC20_EVENTS = { 146 | erc20_transfer_event.signature: erc20_transfer_event, 147 | erc20_approval_event.signature: erc20_approval_event, 148 | } 149 | 150 | ERC20_FUNCTIONS = { 151 | erc20_transfer_function.signature: erc20_transfer_function, 152 | erc20_transferFrom_function.signature: erc20_transferFrom_function, 153 | erc20_approve_function.signature: erc20_approve_function, 154 | erc20_balanceOf_function.signature: erc20_balanceOf_function, 155 | erc20_totalSupply_function.signature: erc20_totalSupply_function, 156 | } 157 | 158 | ERC20_TRANSFORMATIONS = { 159 | erc20_transfer_event.signature: erc20_transfer_event_transformation, 160 | erc20_approval_event.signature: erc20_approval_event_transformation, 161 | erc20_transfer_function.signature: erc20_transfer_function_transformation, 162 | erc20_transferFrom_function.signature: erc20_transferFrom_function_transformation, 163 | erc20_approve_function.signature: erc20_approve_function_transformation, 164 | erc20_balanceOf_function.signature: erc20_balanceOf_function_transformation, 165 | erc20_totalSupply_function.signature: erc20_totalSupply_function_transformation, 166 | } 167 | -------------------------------------------------------------------------------- /ethtx/semantics/utilities/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/ethtx/semantics/utilities/__init__.py -------------------------------------------------------------------------------- /ethtx/semantics/utilities/functions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | from functools import partial 18 | 19 | 20 | def token_decimals(transaction, repository, address): 21 | try: 22 | _, _, decimals, _ = repository.get_token_data(transaction.chain_id, address) 23 | except: 24 | decimals = 18 25 | 26 | return decimals 27 | 28 | 29 | def decode_nft(contract, token_id): 30 | if len(str(token_id)) > 8: 31 | token_symbol = f"NFT {str(token_id)[:6]}...{str(token_id)[-2:]}" 32 | else: 33 | token_symbol = f"NFT {token_id}" 34 | 35 | token_address = f"{contract}?a={token_id}#inventory" 36 | 37 | return dict(address=token_address, name=token_symbol) 38 | 39 | 40 | def string_from_bytes(raw_value): 41 | try: 42 | raw_value = raw_value[2:] if raw_value[:2] == "0x" else raw_value 43 | decoded_string = bytes.fromhex(raw_value).decode("utf-8").replace("\x00", "") 44 | except: 45 | decoded_string = "???" 46 | 47 | return decoded_string 48 | 49 | 50 | def add_utils_to_context(context): 51 | # register additional functions available for transformations 52 | context["token_decimals"] = partial( 53 | token_decimals, context["__transaction__"], context["__repository__"] 54 | ) 55 | context["decode_nft"] = partial(decode_nft, context["__contract__"]) 56 | context["string_from_bytes"] = string_from_bytes 57 | 58 | return 59 | -------------------------------------------------------------------------------- /ethtx/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/ethtx/utils/__init__.py -------------------------------------------------------------------------------- /ethtx/utils/attr_dict.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | 18 | class AttrDict(dict): 19 | def __init__(self, *args, **kwargs): 20 | super().__init__(*args, **kwargs) 21 | self.__dict__ = self 22 | -------------------------------------------------------------------------------- /ethtx/utils/cache_tools.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | import os 18 | from functools import WRAPPER_ASSIGNMENTS, wraps, lru_cache 19 | 20 | CACHE_SIZE = int(os.environ.get("CACHE_SIZE", 256)) 21 | 22 | 23 | def cache(func, cache_size: int = CACHE_SIZE): 24 | @lru_cache(maxsize=cache_size) 25 | def wrapper(*args, **kwargs): 26 | return func(*args, **kwargs) 27 | 28 | return wrapper 29 | 30 | 31 | def ignore_unhashable(func): 32 | uncached = func.__wrapped__ 33 | attributes = WRAPPER_ASSIGNMENTS + ("cache_info", "cache_clear") 34 | wraps(func, assigned=attributes) 35 | 36 | def wrapper(*args, **kwargs): 37 | try: 38 | return func(*args, **kwargs) 39 | except TypeError as error: 40 | if "unhashable type" in str(error): 41 | return uncached(*args, **kwargs) 42 | raise 43 | 44 | wrapper.__uncached__ = uncached 45 | return wrapper 46 | -------------------------------------------------------------------------------- /ethtx/utils/measurable.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | import logging 18 | import sys 19 | import time 20 | 21 | log = logging.getLogger(__name__) 22 | 23 | 24 | class ExecutionTimer: 25 | start_time: float 26 | part_name: str 27 | 28 | def __init__(self, part_name: str): 29 | self.part_name = part_name 30 | 31 | def __enter__(self): 32 | self.start_time = time.time() 33 | 34 | def __exit__(self, *kwargs): 35 | end_time = time.time() 36 | exec_time = (end_time - self.start_time) * 1000 37 | log.info("Executed %s in %s ms", self.part_name, exec_time) 38 | 39 | 40 | class RecursionLimit: 41 | def __init__(self, limit: int): 42 | self.limit = limit 43 | self.cur_limit = sys.getrecursionlimit() 44 | 45 | def __enter__(self) -> None: 46 | sys.setrecursionlimit(self.limit) 47 | 48 | def __exit__(self, *_) -> None: 49 | sys.setrecursionlimit(self.cur_limit) 50 | -------------------------------------------------------------------------------- /ethtx/utils/validators.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | import re 18 | 19 | from ethtx.exceptions import InvalidTransactionHash 20 | 21 | 22 | def assert_tx_hash(tx_hash) -> None: 23 | tx_hash_regex = "^(0x)?([A-Fa-f0-9]{64})$" 24 | if not re.match(tx_hash_regex, tx_hash): 25 | raise InvalidTransactionHash(tx_hash) 26 | -------------------------------------------------------------------------------- /scripts/get_last_changelog_entry.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | 3 | lines = open(os.path.dirname(__file__) + "/../CHANGELOG.md").readlines()[3:] 4 | was_last_line_blank = False 5 | log = "" 6 | for line in lines: 7 | if line == "\n": 8 | if was_last_line_blank: 9 | print(log) 10 | sys.exit(0) 11 | else: 12 | was_last_line_blank = True 13 | else: 14 | log += line 15 | was_last_line_blank = False 16 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description_file = README.md 3 | license_files = LICENSE -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 DAI FOUNDATION (the original version https://github.com/daifoundation/ethtx_ce) 2 | # Copyright 2021-2022 Token Flow Insights SA (modifications to the original software as recorded 3 | # in the changelog https://github.com/EthTx/ethtx/blob/master/CHANGELOG.md) 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and limitations under the License. 12 | # 13 | # The product contains trademarks and other branding elements of Token Flow Insights SA which are 14 | # not licensed under the Apache 2.0 license. When using or reproducing the code, please remove 15 | # the trademark and/or other branding elements. 16 | 17 | import io 18 | import os 19 | import subprocess 20 | import sys 21 | from shutil import rmtree 22 | 23 | import toml 24 | from setuptools import find_packages, setup, Command 25 | 26 | # root dir 27 | root = os.path.abspath(os.path.dirname(__file__)) 28 | 29 | # Package meta-data. 30 | NAME = "EthTx" 31 | DESCRIPTION = "EthTx transaction decoder." 32 | URL = "https://github.com/EthTx/ethtx" 33 | EMAIL = "karol@tfi.team, tomek@tfi.team, piotr.rudnik@tfi.team" 34 | AUTHOR = "Karol Chojnowski, Tomasz Mierzwa, Piotr Rudnik" 35 | REQUIRES_PYTHON = ">=3.7.0" 36 | 37 | REQUIRED = [] 38 | REQUIRED_TEST = [] 39 | 40 | about = { 41 | "__version__": subprocess.check_output( 42 | ["git", "describe", "--tags"], universal_newlines=True 43 | ).strip() 44 | } 45 | 46 | try: 47 | with io.open(os.path.join(root, "README.md"), encoding="utf-8") as f: 48 | long_description = "\n" + f.read() 49 | except FileNotFoundError: 50 | long_description = DESCRIPTION 51 | 52 | 53 | def load_requirements(fname): 54 | """Load requirements from file.""" 55 | try: 56 | with open(fname, "r") as fh: 57 | pipfile = fh.read() 58 | pipfile_toml = toml.loads(pipfile) 59 | except FileNotFoundError: 60 | return [] 61 | 62 | try: 63 | required_packages = pipfile_toml["packages"].items() 64 | except KeyError: 65 | return [] 66 | 67 | return [f"{pkg}{ver}" if ver != "*" else pkg for pkg, ver in required_packages] 68 | 69 | 70 | class UploadCommand(Command): 71 | """Support setup.py upload.""" 72 | 73 | description = "Build and publish the package." 74 | user_options = [] 75 | 76 | @staticmethod 77 | def status(s): 78 | """Prints things in bold.""" 79 | print("\033[1m{0}\033[0m".format(s)) 80 | 81 | def initialize_options(self): 82 | pass 83 | 84 | def finalize_options(self): 85 | pass 86 | 87 | def run(self): 88 | try: 89 | self.status("Removing previous builds…") 90 | rmtree(os.path.join(root, "dist")) 91 | except OSError: 92 | pass 93 | 94 | self.status("Building Source and Wheel (universal) distribution…") 95 | os.system("{0} setup.py sdist bdist_wheel --universal".format(sys.executable)) 96 | 97 | self.status("Uploading the package to PyPI via Twine…") 98 | os.system("twine upload dist/*") 99 | 100 | self.status("Pushing git tags…") 101 | os.system("git tag {0}".format(about["__version__"])) 102 | os.system("git push --tags") 103 | 104 | sys.exit() 105 | 106 | 107 | # *************** INSTALL ***************** 108 | setup( 109 | name=NAME, 110 | version=about["__version__"], 111 | description=DESCRIPTION, 112 | long_description=long_description, 113 | long_description_content_type="text/markdown", 114 | author=AUTHOR, 115 | author_email=EMAIL, 116 | python_requires=REQUIRES_PYTHON, 117 | url=URL, 118 | license="Apache-2.0 License", 119 | packages=find_packages(exclude=["tests"]), 120 | install_requires=load_requirements("Pipfile"), 121 | include_package_data=True, 122 | test_suite="tests", 123 | classifiers=[ 124 | "Operating System :: POSIX :: Linux", 125 | "Programming Language :: Python :: 3.8", 126 | "Programming Language :: Python :: 3.9", 127 | ], 128 | # $ setup.py publish support. 129 | cmdclass={"upload": UploadCommand}, 130 | ) 131 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from mongoengine import connect 3 | from pymongo import MongoClient 4 | 5 | from ethtx.providers.semantic_providers import MongoSemanticsDatabase 6 | 7 | 8 | @pytest.fixture 9 | def mongo_db(): 10 | db_name = "mongo_semantics_test" 11 | client: MongoClient = connect(db=db_name, host="mongomock://localhost") 12 | yield client.db 13 | client.drop_database(db_name) 14 | client.close() 15 | 16 | 17 | @pytest.fixture 18 | def mongo_semantics_database(mongo_db): 19 | yield MongoSemanticsDatabase(mongo_db) 20 | -------------------------------------------------------------------------------- /tests/decode_test.py: -------------------------------------------------------------------------------- 1 | from ethtx.decoders.decoders.parameters import decode_static_argument 2 | 3 | 4 | class TestParameterDecoder: 5 | def test_decode_static_argument_doesnt_double_0x_prefix(self): 6 | argument_type = "bytes32" 7 | raw_value = "0x0" 8 | decoded_value = decode_static_argument( 9 | raw_value=raw_value, argument_type=argument_type 10 | ) 11 | assert decoded_value == "0x0" 12 | -------------------------------------------------------------------------------- /tests/mocks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/tests/mocks/__init__.py -------------------------------------------------------------------------------- /tests/mocks/web3provider.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | 3 | from hexbytes import HexBytes 4 | 5 | from ethtx.models.w3_model import W3Transaction, W3Receipt, W3Block, W3Log 6 | from ethtx.utils.attr_dict import AttrDict 7 | 8 | 9 | class MockWeb3Provider: 10 | blocks = { 11 | 1: { 12 | "difficulty": 123, # int 13 | "extraData": "test", # HexBytes 14 | "gasLimit": 123, # int 15 | "gasUsed": 123, # int 16 | "hash": HexBytes( 17 | b"\x88\xe9mE7\xbe\xa4\xd9\xc0]\x12T\x99\x07\xb3%a\xd3\xbf1\xf4Z\xaesL\xdc\x11\x9f\x13@l\xb6" 18 | ), # str 19 | "logsBloom": "test", # HexBytes 20 | "miner": "test", # str 21 | "nonce": "test", # HexBytes 22 | "number": 123, # int 23 | "parentHash": HexBytes( 24 | b"\x88\xe9mE7\xbe\xa4\xd9\xc0]\x12T\x99\x07\xb3%a\xd3\xbf1\xf4Z\xaesL\xdc\x11\x9f\x13@l\xb6" 25 | ), # str 26 | "receiptsRoot": "test", # HexBytes 27 | "sha3Uncles": "test", # HexBytes 28 | "size": 123, # int 29 | "stateRoot": "test", # HexBytes 30 | "timestamp": 123, # int, 31 | "totalDifficulty": 123, # int 32 | "transactions": [], # List 33 | "transactionsRoot": "test", # HexBytes 34 | "uncles": [], # List 35 | } 36 | } 37 | 38 | txs = { 39 | "0xd7701a0fc05593aee3a16f20cab605db7183f752ae942cc75fd0975feaf1072e": { 40 | "blockHash": HexBytes( 41 | b"\x88\xe9mE7\xbe\xa4\xd9\xc0]\x12T\x99\x07\xb3%a\xd3\xbf1\xf4Z\xaesL\xdc\x11\x9f\x13@l\xb6" 42 | ), # str 43 | "blockNumber": 1, # int 44 | "from_address": "fromasd", # str 45 | "gas": 420, # int 46 | "gasPrice": 1, # int 47 | "hash": HexBytes( 48 | b"\x88\xe9mE7\xbe\xa4\xd9\xc0]\x12T\x99\x07\xb3%a\xd3\xbf1\xf4Z\xaesL\xdc\x11\x9f\x13@l\xb6" 49 | ), # HexBytes, 50 | "input": "jeszcze jak", # str 51 | "nonce": 123, # int 52 | "r": "ds", # HexBytes 53 | "s": "sdf", # HexBytes 54 | "to": "sdf", # str 55 | "transactionIndex": 1, # int 56 | "v": 1, # int 57 | "value": 1, # int 58 | } 59 | } 60 | 61 | def add_mocked_block_details(self, block_number, block_details: Dict): 62 | self.blocks[block_number] = block_details 63 | 64 | def get_transaction(self, tx_hash, chain_id="mainnet"): 65 | return W3Transaction(chain_id=chain_id, **self.txs[tx_hash]) 66 | 67 | def get_receipt(self, tx_hash, chain_id): 68 | log_values = AttrDict( 69 | { 70 | "tx_hash": tx_hash, 71 | "chain_id": chain_id, 72 | "address": "test", # str 73 | "blockHash": "test", # HexBytes 74 | "blockNumber": 123, # int 75 | "data": "test", # str 76 | "logIndex": 132, # int 77 | "removed": False, # bool, 78 | "topics": [HexBytes("d")], # List[HexBytes] 79 | "transactionHash": "test", # HexBytes 80 | "transactionIndex": 123, # int 81 | } 82 | ) 83 | 84 | log = W3Log(**log_values) 85 | values = { 86 | "blockHash": "test", # HexBytes 87 | "blockNumber": 123, # int 88 | "contractAddress": 123, # str 89 | "cumulativeGasUsed": 132, # int, 90 | "from_address": "from", # str 91 | "gasUsed": 123, # int 92 | "logs": [log], # List 93 | "logsBloom": "test", # HexBytes 94 | "root": "test", # str 95 | "status": 123, # int, 96 | "to_address": "test", # str 97 | "transactionHash": "test", # HexBytes 98 | "transactionIndex": 123, # int 99 | } 100 | return W3Receipt(tx_hash=tx_hash, chain_id=chain_id, **values) 101 | 102 | def get_block(self, block_number: int, chain_id: str = None) -> W3Block: 103 | return W3Block(chain_id=chain_id, **self.blocks[block_number]) 104 | -------------------------------------------------------------------------------- /tests/model_test.py: -------------------------------------------------------------------------------- 1 | from ethtx.providers import Web3Provider 2 | from .mocks.web3provider import MockWeb3Provider 3 | 4 | 5 | class TestModel: 6 | def test_create_transaction(self): 7 | tx_hash = "0xd7701a0fc05593aee3a16f20cab605db7183f752ae942cc75fd0975feaf1072e" 8 | mock_web3_provider: Web3Provider = MockWeb3Provider() 9 | w3receipt = mock_web3_provider.get_receipt(tx_hash, "mainnet") 10 | tx = mock_web3_provider.get_transaction(tx_hash).to_object(w3receipt) 11 | assert tx is not None 12 | assert tx.tx_hash is not None 13 | assert tx.from_address is not None 14 | assert tx.to_address is not None 15 | assert tx.tx_index is not None 16 | 17 | def test_create_block(self): 18 | mock_web3_provider = MockWeb3Provider() 19 | block = mock_web3_provider.get_block(1, chain_id="mainnet").to_object() 20 | assert block is not None 21 | assert block.block_number is not None 22 | assert block.block_hash is not None 23 | assert block.timestamp is not None 24 | assert block.miner is not None 25 | assert block.tx_count is not None 26 | 27 | def test_create_event_from_tx_hash(self): 28 | mock_web3_provider = MockWeb3Provider() 29 | tx = "0xd7701a0fc05593aee3a16f20cab605db7183f752ae942cc75fd0975feaf1072e" 30 | receipt = mock_web3_provider.get_receipt(tx, "mainnet") 31 | r = receipt.logs[0] 32 | e = r.to_object() 33 | 34 | assert e is not None 35 | assert e.contract is not None 36 | assert e.log_data is not None 37 | assert len(e.topics) == 1 38 | assert e.log_index is not None 39 | -------------------------------------------------------------------------------- /tests/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/tests/models/__init__.py -------------------------------------------------------------------------------- /tests/models/mock.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | import pytest 4 | 5 | from ethtx.models.decoded_model import AddressInfo, Argument, DecodedTransactionMetadata 6 | from ethtx.models.objects_model import BlockMetadata, TransactionMetadata, Call, Event 7 | from ethtx.models.semantics_model import ParameterSemantics, ContractSemantics 8 | 9 | FAKE_TIME = datetime.datetime(2020, 12, 25, 17, 5, 55) 10 | 11 | 12 | @pytest.fixture 13 | def patch_datetime_now(monkeypatch): 14 | class MyDatetime: 15 | @classmethod 16 | def now(cls): 17 | return FAKE_TIME 18 | 19 | monkeypatch.setattr(datetime, "datetime", MyDatetime) 20 | 21 | 22 | class DecodedModelMock: 23 | ADDRESS_INFO: AddressInfo = AddressInfo(address="address", name="name") 24 | ARGUMENT: Argument = Argument(name="name", type="type", value=1) 25 | DECODED_TRANSACTION_METADATA: DecodedTransactionMetadata = ( 26 | DecodedTransactionMetadata( 27 | tx_hash="0x", 28 | block_number=1, 29 | gas_price=2, 30 | from_address="0xa", 31 | to_address="0xb", 32 | tx_index=3, 33 | tx_value=4, 34 | gas_limit=5, 35 | gas_used=1, 36 | success=False, 37 | ) 38 | ) 39 | 40 | 41 | class ObjectModelMock: 42 | BLOCK_METADATA: BlockMetadata = BlockMetadata( 43 | block_number=15, 44 | block_hash="0x1", 45 | timestamp=FAKE_TIME, 46 | parent_hash="0x", 47 | miner="miner", 48 | gas_limit=12, 49 | gas_used=1, 50 | tx_count=5, 51 | ) 52 | 53 | TRANSACTION_METADATA: TransactionMetadata = TransactionMetadata( 54 | tx_hash="0x", 55 | block_number=1, 56 | gas_price=2, 57 | from_address="0xa", 58 | to_address="0xb", 59 | tx_index=3, 60 | tx_value=4, 61 | gas_limit=5, 62 | gas_used=1, 63 | success=False, 64 | ) 65 | 66 | CALL: Call = Call( 67 | call_type="call", 68 | from_address="0xa", 69 | to_address="0xb", 70 | call_value=10, 71 | call_data="0x00000000000000000", 72 | return_value="0xeeee", 73 | status=True, 74 | ) 75 | 76 | EVENT: Event = Event(contract="0x", topics=["", ""], log_index=1) 77 | 78 | 79 | class SemanticModelMock: 80 | PARAMETER_SEMANTICS: ParameterSemantics = ParameterSemantics( 81 | parameter_name="name", parameter_type="type" 82 | ) 83 | 84 | CONTRACT_SEMANTICS: ContractSemantics = ContractSemantics( 85 | code_hash="0x", name="name" 86 | ) 87 | -------------------------------------------------------------------------------- /tests/models/objects_model_test.py: -------------------------------------------------------------------------------- 1 | from ethtx.models.objects_model import ( 2 | BlockMetadata, 3 | TransactionMetadata, 4 | Event, 5 | Call, 6 | Transaction, 7 | Block, 8 | ) 9 | from tests.models.mock import FAKE_TIME, ObjectModelMock 10 | 11 | 12 | class TestObjectsModels: 13 | def test_block_metadata(self): 14 | bm = BlockMetadata( 15 | block_number=15, 16 | block_hash="0x1", 17 | timestamp=FAKE_TIME, 18 | parent_hash="0x", 19 | miner="miner", 20 | gas_limit=12, 21 | gas_used=1, 22 | tx_count=5, 23 | ) 24 | 25 | assert bm.block_number == 15 26 | assert bm.block_hash == "0x1" 27 | assert bm.timestamp == FAKE_TIME 28 | assert bm.parent_hash == "0x" 29 | assert bm.miner == "miner" 30 | assert bm.gas_limit == 12 31 | assert bm.gas_used == 1 32 | assert bm.tx_count == 5 33 | assert bm.canonical 34 | 35 | def test_transaction_metadata(self): 36 | tm = TransactionMetadata( 37 | tx_hash="0x", 38 | block_number=1, 39 | gas_price=2, 40 | from_address="0xa", 41 | to_address="0xb", 42 | tx_index=3, 43 | tx_value=4, 44 | gas_limit=5, 45 | gas_used=1, 46 | success=False, 47 | ) 48 | 49 | assert tm.tx_hash == "0x" 50 | assert tm.block_number == 1 51 | assert tm.gas_price == 2 52 | assert tm.from_address == "0xa" 53 | assert tm.to_address == "0xb" 54 | assert tm.tx_index == 3 55 | assert tm.tx_value == 4 56 | assert tm.gas_limit == 5 57 | assert tm.gas_used == 1 58 | assert not tm.success 59 | assert tm.gas_refund is None 60 | assert tm.return_value is None 61 | assert tm.exception_error is None 62 | assert tm.exception_error_type is None 63 | assert tm.revert_reason is None 64 | 65 | def test_event(self): 66 | e = Event(contract="0x", topics=["", ""], log_index=1) 67 | 68 | assert e.contract == "0x" 69 | assert e.topics == ["", ""] 70 | assert e.log_data is None 71 | assert e.log_index == 1 72 | assert e.call_id is None 73 | 74 | def test_call(self): 75 | c = Call( 76 | call_type="call", 77 | from_address="0xa", 78 | to_address="0xb", 79 | call_value=10, 80 | call_data="0x00000000000000000", 81 | return_value="0xeeee", 82 | status=True, 83 | ) 84 | 85 | assert c.call_type == "call" 86 | assert c.from_address == "0xa" 87 | assert c.to_address == "0xb" 88 | assert c.call_value == 10 89 | assert c.call_gas is None 90 | assert c.call_data == "0x00000000000000000" 91 | assert c.return_value == "0xeeee" 92 | assert c.status 93 | assert c.gas_used is None 94 | assert c.error is None 95 | assert c.subcalls == [] 96 | assert c.call_id is None 97 | assert c.created_address is None 98 | assert c.gas_refund is None 99 | assert c.exception_error is None 100 | assert c.exception_error_type is None 101 | assert c.revert_reason is None 102 | assert c.success is None 103 | 104 | def test_transaction(self): 105 | t = Transaction( 106 | metadata=ObjectModelMock.TRANSACTION_METADATA, 107 | root_call=ObjectModelMock.CALL, 108 | events=[ 109 | ObjectModelMock.EVENT, 110 | ObjectModelMock.EVENT, 111 | ObjectModelMock.EVENT, 112 | ], 113 | ) 114 | 115 | assert t.metadata == ObjectModelMock.TRANSACTION_METADATA 116 | assert t.root_call == ObjectModelMock.CALL 117 | assert t.events == [ 118 | ObjectModelMock.EVENT, 119 | ObjectModelMock.EVENT, 120 | ObjectModelMock.EVENT, 121 | ] 122 | 123 | def test_block(self): 124 | t = Transaction( 125 | metadata=ObjectModelMock.TRANSACTION_METADATA, 126 | root_call=ObjectModelMock.CALL, 127 | events=[ 128 | ObjectModelMock.EVENT, 129 | ObjectModelMock.EVENT, 130 | ObjectModelMock.EVENT, 131 | ], 132 | ) 133 | 134 | b = Block( 135 | chain_id="mainnet", 136 | metadata=ObjectModelMock.BLOCK_METADATA, 137 | transactions=[t, t], 138 | ) 139 | 140 | assert b.chain_id == "mainnet" 141 | assert b.metadata == ObjectModelMock.BLOCK_METADATA 142 | assert b.transactions == [t, t] 143 | -------------------------------------------------------------------------------- /tests/models/semantics_model_test.py: -------------------------------------------------------------------------------- 1 | from ethtx.models.semantics_model import ( 2 | TransformationSemantics, 3 | ParameterSemantics, 4 | EventSemantics, 5 | FunctionSemantics, 6 | SignatureArg, 7 | Signature, 8 | ERC20Semantics, 9 | ContractSemantics, 10 | AddressSemantics, 11 | ) 12 | from tests.models.mock import SemanticModelMock 13 | 14 | 15 | class TestSemanticsModels: 16 | def test_transformation_semantics(self): 17 | ts = TransformationSemantics() 18 | 19 | assert ts.transformed_name is None 20 | assert ts.transformed_type is None 21 | assert ts.transformation == "" 22 | 23 | def test_parameter_semantics(self): 24 | ps = ParameterSemantics(parameter_name="name", parameter_type="type") 25 | 26 | assert ps.parameter_name == "name" 27 | assert ps.parameter_type == "type" 28 | assert ps.components == [] 29 | assert not ps.indexed 30 | assert not ps.dynamic 31 | 32 | def test_event_semantics(self): 33 | es = EventSemantics( 34 | signature="0x", 35 | anonymous=True, 36 | name="name", 37 | parameters=[SemanticModelMock.PARAMETER_SEMANTICS], 38 | ) 39 | 40 | assert es.signature == "0x" 41 | assert es.anonymous 42 | assert es.name == "name" 43 | assert es.parameters == [SemanticModelMock.PARAMETER_SEMANTICS] 44 | 45 | def test_function_semantics(self): 46 | fs = FunctionSemantics( 47 | signature="0x", 48 | name="name", 49 | inputs=[SemanticModelMock.PARAMETER_SEMANTICS], 50 | outputs=[SemanticModelMock.PARAMETER_SEMANTICS], 51 | ) 52 | 53 | assert fs.signature == "0x" 54 | assert fs.name == "name" 55 | assert fs.inputs == [SemanticModelMock.PARAMETER_SEMANTICS] 56 | assert fs.outputs == [SemanticModelMock.PARAMETER_SEMANTICS] 57 | 58 | def test_signature_args(self): 59 | sa = SignatureArg(name="name", type="type") 60 | 61 | assert sa.name == "name" 62 | assert sa.type == "type" 63 | 64 | def test_signature(self): 65 | sa = SignatureArg(name="name", type="type") 66 | s = Signature(signature_hash="0x", name="name", args=[sa]) 67 | 68 | assert s.signature_hash == "0x" 69 | assert s.name == "name" 70 | assert s.args == [sa] 71 | assert s.count == 1 72 | assert not s.tuple 73 | assert not s.guessed 74 | 75 | def test_erc20_semantics(self): 76 | es = ERC20Semantics(name="name", symbol="symbol", decimals=18) 77 | 78 | assert es.name == "name" 79 | assert es.symbol == "symbol" 80 | assert es.decimals == 18 81 | 82 | def test_contract_semantics(self): 83 | cs = ContractSemantics(code_hash="0x", name="name") 84 | 85 | assert cs.code_hash == "0x" 86 | assert cs.name == "name" 87 | assert cs.events == {} 88 | assert cs.functions == {} 89 | assert cs.transformations == {} 90 | 91 | def test_address_semantics(self): 92 | ads = AddressSemantics( 93 | chain_id="mainnet", 94 | address="0x", 95 | name="name", 96 | is_contract=True, 97 | contract=SemanticModelMock.CONTRACT_SEMANTICS, 98 | ) 99 | assert ads.chain_id == "mainnet" 100 | assert ads.address == "0x" 101 | assert ads.name == "name" 102 | assert ads.is_contract 103 | assert ads.contract == SemanticModelMock.CONTRACT_SEMANTICS 104 | assert ads.standard is None 105 | assert ads.erc20 is None 106 | -------------------------------------------------------------------------------- /tests/models/w3_model_test.py: -------------------------------------------------------------------------------- 1 | from ethtx.models.w3_model import W3Block, W3Transaction, W3Receipt, W3Log, W3CallTree 2 | 3 | 4 | class TestW3Models: 5 | def test_w3_block(self, mocker): 6 | hex_bytes = mocker.patch( 7 | "hexbytes.main.HexBytes", 8 | return_value="0x", 9 | new_callable=mocker.PropertyMock, 10 | ) 11 | wb = W3Block( 12 | chain_id="mainnet", 13 | difficulty=1, 14 | extraData=hex_bytes, 15 | gasLimit=100, 16 | gasUsed=20, 17 | hash=hex_bytes, 18 | logsBloom=hex_bytes, 19 | miner="miner", 20 | nonce=hex_bytes, 21 | number=10, 22 | parentHash=hex_bytes, 23 | receiptsRoot=hex_bytes, 24 | sha3Uncles=hex_bytes, 25 | size=10, 26 | stateRoot=hex_bytes, 27 | timestamp=123123123123, 28 | totalDifficulty=1, 29 | transactions=[], 30 | transactionsRoot=hex_bytes, 31 | uncles=[], 32 | ) 33 | 34 | assert wb.chain_id == "mainnet" 35 | assert wb.difficulty == 1 36 | assert wb.extraData == hex_bytes 37 | assert wb.gasLimit == 100 38 | assert wb.gasUsed == 20 39 | assert wb.hash == hex_bytes 40 | assert wb.logsBloom == hex_bytes 41 | assert wb.miner == "miner" 42 | assert wb.nonce == hex_bytes 43 | assert wb.number == 10 44 | assert wb.parentHash == hex_bytes 45 | assert wb.receiptsRoot == hex_bytes 46 | assert wb.sha3Uncles == hex_bytes 47 | assert wb.size == 10 48 | assert wb.stateRoot == hex_bytes 49 | assert wb.timestamp == 123123123123 50 | assert wb.totalDifficulty == 1 51 | assert wb.transactions == [] 52 | assert wb.transactionsRoot == hex_bytes 53 | assert wb.uncles == [] 54 | 55 | def test_w3_transaction(self, mocker): 56 | hex_bytes = mocker.patch( 57 | "hexbytes.main.HexBytes", 58 | return_value="0x", 59 | new_callable=mocker.PropertyMock, 60 | ) 61 | 62 | wt = W3Transaction( 63 | chain_id="mainnet", 64 | blockHash=hex_bytes, 65 | blockNumber=5, 66 | from_address="0x", 67 | gas=5, 68 | gasPrice=5, 69 | hash=hex_bytes, 70 | input="0x", 71 | nonce=5, 72 | r=hex_bytes, 73 | s=hex_bytes, 74 | to="0x", 75 | transactionIndex=5, 76 | v=5, 77 | value=5, 78 | ) 79 | 80 | assert wt.chain_id == "mainnet" 81 | assert wt.blockHash == hex_bytes 82 | assert wt.blockNumber == 5 83 | assert wt.from_address == "0x" 84 | assert wt.gas == 5 85 | assert wt.gasPrice == 5 86 | assert wt.hash == hex_bytes 87 | assert wt.input == "0x" 88 | assert wt.nonce == 5 89 | assert wt.r == hex_bytes 90 | assert wt.s == hex_bytes 91 | assert wt.to == "0x" 92 | assert wt.transactionIndex == 5 93 | assert wt.v == 5 94 | assert wt.value == 5 95 | 96 | def test_w3_receipt(self, mocker): 97 | hex_bytes = mocker.patch( 98 | "hexbytes.main.HexBytes", 99 | return_value="0x", 100 | new_callable=mocker.PropertyMock, 101 | ) 102 | 103 | wr = W3Receipt( 104 | tx_hash="0x", 105 | chain_id="0x", 106 | blockHash=hex_bytes, 107 | blockNumber=10, 108 | contractAddress="0x", 109 | cumulativeGasUsed=10, 110 | from_address="0x", 111 | gasUsed=10, 112 | logsBloom=hex_bytes, 113 | status=10, 114 | to_address="0x", 115 | transactionHash=hex_bytes, 116 | transactionIndex=10, 117 | logs=[], 118 | ) 119 | 120 | assert wr.tx_hash == "0x" 121 | assert wr.chain_id == "0x" 122 | assert wr.blockHash == hex_bytes 123 | assert wr.blockNumber == 10 124 | assert wr.contractAddress == "0x" 125 | assert wr.cumulativeGasUsed == 10 126 | assert wr.from_address == "0x" 127 | assert wr.gasUsed == 10 128 | assert wr.logsBloom == hex_bytes 129 | assert wr.status == 10 130 | assert wr.to_address == "0x" 131 | assert wr.transactionHash == hex_bytes 132 | assert wr.transactionIndex == 10 133 | assert wr.logs == [] 134 | assert wr.root is None 135 | 136 | def test_w3_log(self, mocker): 137 | hex_bytes = mocker.patch( 138 | "hexbytes.main.HexBytes", 139 | return_value="0x", 140 | new_callable=mocker.PropertyMock, 141 | ) 142 | wl = W3Log( 143 | tx_hash="0x", 144 | chain_id="0x", 145 | address="0x", 146 | blockHash=hex_bytes, 147 | blockNumber=55, 148 | data="0x", 149 | logIndex=55, 150 | removed=False, 151 | topics=[hex_bytes], 152 | transactionHash=hex_bytes, 153 | transactionIndex=55, 154 | ) 155 | 156 | assert wl.tx_hash == "0x" 157 | assert wl.chain_id == "0x" 158 | assert wl.address == "0x" 159 | assert wl.blockHash == hex_bytes 160 | assert wl.blockNumber == 55 161 | assert wl.data == "0x" 162 | assert wl.logIndex == 55 163 | assert not wl.removed 164 | assert wl.topics == [hex_bytes] 165 | assert wl.transactionHash == hex_bytes 166 | assert wl.transactionIndex == 55 167 | 168 | def test_w3_call_tree(self): 169 | wct = W3CallTree( 170 | tx_hash="0xdas", 171 | chain_id="0xdas", 172 | type="0xdas", 173 | from_address="0xdas", 174 | to_address="0xdas", 175 | input="0xdas", 176 | output="0xdas", 177 | calls=[], 178 | ) 179 | 180 | assert wct.tx_hash == "0xdas" 181 | assert wct.chain_id == "0xdas" 182 | assert wct.type == "0xdas" 183 | assert wct.from_address == "0xdas" 184 | assert wct.to_address == "0xdas" 185 | assert wct.input == "0xdas" 186 | assert wct.output == "0xdas" 187 | assert wct.calls == [] 188 | assert wct.value is None 189 | assert wct.time is None 190 | assert wct.gas is None 191 | assert wct.gasUsed is None 192 | assert wct.error is None 193 | -------------------------------------------------------------------------------- /tests/providers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/tests/providers/__init__.py -------------------------------------------------------------------------------- /tests/providers/node/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/tests/providers/node/__init__.py -------------------------------------------------------------------------------- /tests/providers/node/connection_base_test.py: -------------------------------------------------------------------------------- 1 | from ethtx.providers.node.connection_base import NodeConnection 2 | 3 | CHAIN = "mainnet" 4 | NODE = "test" 5 | POA = False 6 | 7 | 8 | class TestConnectionBase: 9 | @classmethod 10 | def setup_class(cls): 11 | cls.connection = NodeConnection(CHAIN, NODE, POA) 12 | 13 | def test_connection_parameters(self): 14 | assert self.connection.chain == CHAIN 15 | assert self.connection.url == NODE 16 | assert self.connection.poa == POA 17 | 18 | def test_connection_representation(self): 19 | representation = f"" 20 | assert repr(self.connection) == representation 21 | assert str(self.connection) == representation 22 | 23 | def test_connection_dict(self): 24 | assert dict(self.connection) == {"chain": CHAIN, "url": NODE, "poa": POA} 25 | -------------------------------------------------------------------------------- /tests/providers/node/pool_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ethtx.providers.node.connection_base import NodeConnection 4 | from ethtx.providers.node.pool import NodeConnectionPool 5 | 6 | MAINNET_CHAIN = {"mainnet": {"hook": "a", "poa": True}} 7 | GOERLI_CHAIN = {"goerli": {"hook": "a, b, c", "poa": False}} 8 | 9 | GOERLI_NODE = NodeConnection("goerli", "B", False) 10 | 11 | 12 | class TestNodeConnectionPool: 13 | def test_number_of_connections(self): 14 | pool = NodeConnectionPool(nodes=MAINNET_CHAIN) 15 | assert len(pool) == 1, "Number of connections should equal 1." 16 | 17 | def test_add_connection(self): 18 | pool = NodeConnectionPool(nodes=MAINNET_CHAIN) 19 | pool.add_connection(connection=GOERLI_NODE) 20 | 21 | assert ( 22 | GOERLI_NODE in pool.connections 23 | ), f"{GOERLI_NODE} should be in pool of connections." 24 | 25 | def test_get_connection(self): 26 | pool = NodeConnectionPool(nodes=MAINNET_CHAIN) 27 | pool.add_connection(connection=GOERLI_NODE) 28 | connection = pool.get_connection("goerli") 29 | 30 | assert ( 31 | connection[0] == GOERLI_NODE 32 | ), f"{GOERLI_NODE} should be in pool of connections." 33 | 34 | def test_add_wrong_type_connection(self): 35 | pool = NodeConnectionPool(nodes=MAINNET_CHAIN) 36 | with pytest.raises(ValueError): 37 | pool.add_connection((1, 1, 1)) 38 | -------------------------------------------------------------------------------- /tests/providers/semantic_providers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthTx/ethtx/448b46ff8067b83cbb90e5373b4cae9319d13b48/tests/providers/semantic_providers/__init__.py -------------------------------------------------------------------------------- /tests/providers/semantic_providers/semantics_database_test.py: -------------------------------------------------------------------------------- 1 | from mongomock import Database 2 | 3 | from ethtx.providers.semantic_providers.const import MongoCollections 4 | 5 | 6 | class TestMongoSemanticsDatabase: 7 | def test_no_address_semantics(self, mongo_semantics_database): 8 | sema = mongo_semantics_database.get_address_semantics("mainnet", "not_existing") 9 | assert not sema 10 | 11 | def test_save_and_get_contract_semantic( 12 | self, mongo_semantics_database, mongo_db: Database 13 | ): 14 | code_hash = "test_code_hash" 15 | contract_data = {"code_hash": code_hash, "chain_id": "mainnet"} 16 | 17 | try: 18 | assert ( 19 | 0 20 | == mongo_db.get_collection( 21 | MongoCollections.CONTRACTS 22 | ).estimated_document_count() 23 | ) 24 | mongo_semantics_database.insert_contract(contract_data) 25 | assert ( 26 | 1 27 | == mongo_db.get_collection( 28 | MongoCollections.CONTRACTS 29 | ).estimated_document_count() 30 | ) 31 | contract_from_db = mongo_semantics_database.get_contract_semantics( 32 | code_hash 33 | ) 34 | assert contract_from_db == contract_data 35 | assert mongo_db.list_collection_names() == [MongoCollections.CONTRACTS] 36 | finally: 37 | mongo_db.drop_collection(MongoCollections.CONTRACTS) 38 | 39 | def test_save_and_get_address_semantic(self, mongo_db, mongo_semantics_database): 40 | address = "test_address" 41 | address_data = { 42 | "address": address, 43 | "chain_id": "mainnet", 44 | "erc20": { 45 | "name": "test_name", 46 | "symbol": "test_symbol", 47 | "decimals": "test_decimal", 48 | }, 49 | "contract": "test_contract", 50 | "name": "test_contract_name", 51 | "is_contract": False, 52 | "standard": False, 53 | } 54 | 55 | try: 56 | assert ( 57 | 0 58 | == mongo_db.get_collection( 59 | MongoCollections.ADDRESSES 60 | ).estimated_document_count() 61 | ) 62 | mongo_semantics_database.insert_address(address_data) 63 | assert ( 64 | 1 65 | == mongo_db.get_collection( 66 | MongoCollections.ADDRESSES 67 | ).estimated_document_count() 68 | ) 69 | address_from_db = mongo_semantics_database.get_address_semantics( 70 | "mainnet", address 71 | ) 72 | assert address_from_db == address_data 73 | assert mongo_db.list_collection_names() == [MongoCollections.ADDRESSES] 74 | finally: 75 | mongo_db.drop_collection(MongoCollections.ADDRESSES) 76 | -------------------------------------------------------------------------------- /tests/test_decimal_format.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from ethtx.decoders.semantic.helpers.utils import ( 4 | _handle_decimal_representations as handle_decimal_representations, 5 | ) 6 | 7 | 8 | class TestHandleDecimalRepresentations: 9 | def test_handle_decimal_representations(self): 10 | # Test for small int 11 | assert handle_decimal_representations(Decimal(10)) == "10" 12 | 13 | # Test for large int 14 | assert ( 15 | handle_decimal_representations(Decimal("100000000000000000000000000")) 16 | == "100000000000000000000000000" 17 | ) 18 | 19 | # Test for small float 20 | assert handle_decimal_representations(Decimal("0.5")) == "0.5" 21 | 22 | # Test for large float 23 | assert ( 24 | handle_decimal_representations(Decimal("100000000000000000000.5")) 25 | == "100000000000000000000.5" 26 | ) 27 | 28 | # Test for float with a lot of decimals 29 | assert ( 30 | handle_decimal_representations(Decimal("0.12345678901234567890123456789")) 31 | == "0.12345678901234567890123456789" 32 | ) 33 | 34 | # Test for float with trailing 0s 35 | assert handle_decimal_representations(Decimal("10.500")) == "10.5" 36 | 37 | # Test for float with trailing 0s 38 | assert handle_decimal_representations(Decimal("10.000")) == "10" 39 | 40 | # Test for float with trailing 0s and leading 0s 41 | assert handle_decimal_representations(Decimal("0.0500")) == "0.05" 42 | 43 | # Test cases from the original question 44 | assert ( 45 | handle_decimal_representations(Decimal("0.0461872460000")) == "0.046187246" 46 | ) 47 | assert handle_decimal_representations(Decimal("0.00000")) == "0" 48 | assert handle_decimal_representations(Decimal("650000000")) == "650000000" 49 | assert ( 50 | handle_decimal_representations(Decimal("650000000.0004214000")) 51 | == "650000000.0004214" 52 | ) 53 | assert handle_decimal_representations(Decimal("650000.00000000")) == "650000" 54 | 55 | # Additional test cases 56 | assert ( 57 | handle_decimal_representations(Decimal("0.0000000000000000000000001")) 58 | == "0.0000000000000000000000001" 59 | ) 60 | assert ( 61 | handle_decimal_representations( 62 | Decimal("0.000000000000000000000000187126874612874612784") 63 | ) 64 | == "0.000000000000000000000000187126874612874612784" 65 | ) 66 | assert ( 67 | handle_decimal_representations( 68 | Decimal("0.000000000000000000000000187126874612874612784000000000") 69 | ) 70 | == "0.000000000000000000000000187126874612874612784" 71 | ) 72 | 73 | # Test that the function returns a string 74 | assert isinstance(handle_decimal_representations(Decimal("1.234")), str) 75 | -------------------------------------------------------------------------------- /tests/validator_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ethtx.utils.validators import assert_tx_hash 4 | 5 | 6 | class TestValidator: 7 | def test_tx_valid_hash(self): 8 | tx_hash = "0xe9a781eea6b6dbb9354555fff3cfb4727d27eea78346f2ca341e3268037eb559" 9 | assert_tx_hash(tx_hash) 10 | 11 | def test_tx_valid_hash_without_0x(self): 12 | tx_hash = "e9a781eea6b6dbb9354555fff3cfb4727d27eea78346f2ca341e3268037eb559" 13 | assert_tx_hash(tx_hash) 14 | 15 | def test_tx_invalid_hashes(self): 16 | invalid_hashes = ["", "test", "Haxl337", None, 1] 17 | for invalid_hash in invalid_hashes: 18 | with pytest.raises(Exception): 19 | assert_tx_hash(invalid_hash) 20 | --------------------------------------------------------------------------------