Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: support decoding multiple ABIs at the same time, including ds-note library logs #757

Merged
merged 62 commits into from
Jul 27, 2022
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
66ff53e
feat: add receipt events and ds-note decoding
banteg Jul 6, 2022
fc20c38
refactor: move ds-note decoding to ecosystem api
banteg Jul 6, 2022
41347d3
fix: consistent exceptions
banteg Jul 6, 2022
4b4539a
feat: optimize ds-log decoding
banteg Jul 6, 2022
2695454
test: add makerdao vat contract
banteg Jul 6, 2022
946536a
test: ds note
banteg Jul 7, 2022
cc8be00
feat: make abi optional for decode_logs, add ds-note decoding
banteg Jul 7, 2022
1622abc
test: decode events without providing abi
banteg Jul 7, 2022
1ec6214
refactor: simplify finding selector for ds-note
banteg Jul 7, 2022
0c6666e
fix: black/flake8 clash
banteg Jul 7, 2022
7952e32
feat: lazy fetching of contract types
banteg Jul 7, 2022
8fdc8de
fix: catch decoding error
banteg Jul 7, 2022
10c25df
fix: handle None contract types
antazoey Jul 7, 2022
9e7f370
chore: Merge branch 'main' into feat/decode-events
antazoey Jul 7, 2022
10d062a
chore: rm optional annotation
antazoey Jul 7, 2022
4cdc91d
refactor: move stuff to ethereum and handle anon logs more
antazoey Jul 7, 2022
d876f3d
test: add simple tests
antazoey Jul 7, 2022
1660f86
test: make fixture
antazoey Jul 7, 2022
c5ed093
test: tests for getting ds notes from receipt
antazoey Jul 8, 2022
0185ab2
chore: del unneeded file
antazoey Jul 8, 2022
eafb03f
chore: merge main
antazoey Jul 10, 2022
e033050
fix: decode lib log optimization
antazoey Jul 10, 2022
44eee65
refactor: move decode lib logs to ecosystem
antazoey Jul 10, 2022
7d12143
chore: put back fixture use
antazoey Jul 10, 2022
563b2cf
Merge branch 'main' into feat/decode-events
fubuloubu Jul 14, 2022
903ba84
chore: merge main
antazoey Jul 20, 2022
83536ff
chore: resolve merge conflicts
antazoey Jul 20, 2022
eddf2d3
fix: address regressions from merge
antazoey Jul 20, 2022
69f9f22
refator: make list of abis
antazoey Jul 20, 2022
3125cdd
chore: pr feedback
antazoey Jul 20, 2022
25d9288
fix: base method param type correct
antazoey Jul 20, 2022
c22bb8a
fix: skip unfound contracttypes
antazoey Jul 20, 2022
0b924bc
refactor: use return from pool map
antazoey Jul 20, 2022
fbc160c
refactor: make get_all method in contract cache
antazoey Jul 20, 2022
d1b8c89
fix: handle conversion and non contract types
antazoey Jul 20, 2022
afd7915
refactor: get contract type
banteg Jul 20, 2022
659191a
feat: exit early for empty code
banteg Jul 20, 2022
0901366
fix: exit early
banteg Jul 20, 2022
2b6d167
refactor: get multiple
banteg Jul 20, 2022
832616e
fix: decode in correct order
banteg Jul 20, 2022
9a20e30
feat: add event selector to ecosystem
banteg Jul 22, 2022
ab67dd9
Merge branch 'main' into feat/decode-events
banteg Jul 22, 2022
08323a6
refactor: make decode_logs method in ape_eth txn
antazoey Jul 22, 2022
57bb7b3
fix: add abstract method
antazoey Jul 22, 2022
ccccb5c
Merge branch 'main' into feat/decode-events
antazoey Jul 22, 2022
2861def
feat: rm get lib logs method
antazoey Jul 25, 2022
25c12fb
chore: resolve conflicts
antazoey Jul 25, 2022
5dc6276
fix: contract cache issue suddenly
antazoey Jul 25, 2022
c8d0e67
fix: cache before return
antazoey Jul 25, 2022
56b272f
chore: Merge branch 'main' into feat/decode-events
antazoey Jul 26, 2022
64cb17d
refactor: use * not for event abis
antazoey Jul 26, 2022
55625f2
refactor: rename name to event_name
antazoey Jul 26, 2022
d320ded
feat: support transaction_index
antazoey Jul 26, 2022
cc0a676
fix: to int
banteg Jul 26, 2022
a6b3232
feat: handle raw responses
antazoey Jul 26, 2022
ae68813
test: improve test
antazoey Jul 26, 2022
084f5af
fix: issue when not using web3 provideR
antazoey Jul 26, 2022
8f223c4
fix: revert unneeded change
antazoey Jul 26, 2022
d2ee852
chore: new found mypy issues
antazoey Jul 26, 2022
299b048
chore: rm unused import
antazoey Jul 27, 2022
e5b764b
feat: remove support for snake_case
antazoey Jul 27, 2022
083ca81
Merge branch 'main' into feat/decode-events
antazoey Jul 27, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions src/ape/api/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from tqdm import tqdm # type: ignore

from ape.api.explorers import ExplorerAPI
from ape.exceptions import TransactionError
from ape.exceptions import DecodingError, TransactionError
from ape.logging import logger
from ape.types import ContractLog, TransactionSignature
from ape.utils import BaseInterfaceModel, abstractmethod, raises_not_implemented
Expand Down Expand Up @@ -221,7 +221,9 @@ def raise_for_status(self):
:class:`~api.providers.TransactionStatusEnum`.
"""

def decode_logs(self, abi: Union[EventABI, "ContractEvent"]) -> Iterator[ContractLog]:
def decode_logs(
self, abi: Optional[Union[EventABI, "ContractEvent"]] = None
) -> Iterator[ContractLog]:
"""
Decode the logs on the receipt.

Expand All @@ -231,10 +233,23 @@ def decode_logs(self, abi: Union[EventABI, "ContractEvent"]) -> Iterator[Contrac
Returns:
Iterator[:class:`~ape.types.ContractLog`]
"""
if not isinstance(abi, EventABI):
abi = abi.abi
if abi:
if not isinstance(abi, EventABI):
abi = abi.abi

yield from self.provider.network.ecosystem.decode_logs(abi, self.logs)
yield from self.provider.network.ecosystem.decode_logs(abi, self.logs)
else:
# if abi is not provided, decode all events
for log in self.logs:
contract_type = self.chain_manager.contracts.get(log["address"])
try:
event_abi = contract_type.events[log["topics"][0]] # type: ignore
antazoey marked this conversation as resolved.
Show resolved Hide resolved
antazoey marked this conversation as resolved.
Show resolved Hide resolved
yield from self.provider.network.ecosystem.decode_logs(event_abi, [log])
except (StopIteration, KeyError, DecodingError):
try:
yield self.provider.network.ecosystem.decode_ds_note(log) # type: ignore
banteg marked this conversation as resolved.
Show resolved Hide resolved
except (DecodingError, AttributeError):
yield log # type: ignore

def await_confirmations(self) -> "ReceiptAPI":
"""
Expand Down
34 changes: 34 additions & 0 deletions src/ape_ethereum/ecosystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -490,3 +490,37 @@ def decode_value(t, v) -> Any:
block_hash=log["blockHash"],
block_number=log["blockNumber"],
) # type: ignore

def decode_ds_note(self, log: dict) -> Optional[ContractLog]:
antazoey marked this conversation as resolved.
Show resolved Hide resolved
"""
Decode anonymous events emitted by the DSNote library.
"""
# the first topic encodes the function selector
selector, tail = log["topics"][0][:4], log["topics"][0][4:]
if sum(tail):
raise DecodingError("ds-note: non-zero bytes found after selector")

contract_type = self.chain_manager.contracts.get(log["address"])
if contract_type is None:
raise DecodingError(f"ds-note: contract type for {log['address']} not found")

try:
abi = contract_type.mutable_methods[selector]
except KeyError:
raise DecodingError(f"ds-note: selector {selector.hex()} not found in {log['address']}")

# ds-note data field uses either (uint256,bytes) or (bytes) encoding
# instead of guessing, assume the payload begins right after the selector
data = decode_hex(log["data"])
input_types = [i.canonical_type for i in abi.inputs]
start_index = data.index(selector) + 4
values = decode_abi(input_types, data[start_index:])

return ContractLog( # type: ignore
name=abi.name,
event_arguments={input.name: value for input, value in zip(abi.inputs, values)},
transaction_hash=log["transactionHash"],
block_number=log["blockNumber"],
block_hash=log["blockHash"],
index=log["logIndex"],
)
18 changes: 18 additions & 0 deletions tests/functional/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,21 @@ def dependency_config(temp_config):
@pytest.fixture
def base_projects_directory():
return BASE_PROJECTS_DIRECTORY


@pytest.fixture
def mainnet_contract(chain):
def contract_getter(address):
path = (
Path(__file__).parent
/ "data"
/ "contracts"
/ "ethereum"
/ "mainnet"
/ f"{address}.json"
)
contract = ContractType.parse_file(path)
chain.contracts._local_contracts[address] = contract
return contract

return contract_getter
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":true,"inputs":[{"indexed":true,"internalType":"bytes4","name":"sig","type":"bytes4"},{"indexed":true,"internalType":"bytes32","name":"arg1","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"arg2","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"arg3","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"LogNote","type":"event"},{"inputs":[],"name":"Line","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"can","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"dai","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"debt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"deny","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"ilk","type":"bytes32"},{"internalType":"bytes32","name":"what","type":"bytes32"},{"internalType":"uint256","name":"data","type":"uint256"}],"name":"file","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"what","type":"bytes32"},{"internalType":"uint256","name":"data","type":"uint256"}],"name":"file","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"ilk","type":"bytes32"},{"internalType":"address","name":"src","type":"address"},{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"flux","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"i","type":"bytes32"},{"internalType":"address","name":"u","type":"address"},{"internalType":"int256","name":"rate","type":"int256"}],"name":"fold","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"ilk","type":"bytes32"},{"internalType":"address","name":"src","type":"address"},{"internalType":"address","name":"dst","type":"address"},{"internalType":"int256","name":"dink","type":"int256"},{"internalType":"int256","name":"dart","type":"int256"}],"name":"fork","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"i","type":"bytes32"},{"internalType":"address","name":"u","type":"address"},{"internalType":"address","name":"v","type":"address"},{"internalType":"address","name":"w","type":"address"},{"internalType":"int256","name":"dink","type":"int256"},{"internalType":"int256","name":"dart","type":"int256"}],"name":"frob","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"name":"gem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"i","type":"bytes32"},{"internalType":"address","name":"u","type":"address"},{"internalType":"address","name":"v","type":"address"},{"internalType":"address","name":"w","type":"address"},{"internalType":"int256","name":"dink","type":"int256"},{"internalType":"int256","name":"dart","type":"int256"}],"name":"grab","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"rad","type":"uint256"}],"name":"heal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"hope","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"ilks","outputs":[{"internalType":"uint256","name":"Art","type":"uint256"},{"internalType":"uint256","name":"rate","type":"uint256"},{"internalType":"uint256","name":"spot","type":"uint256"},{"internalType":"uint256","name":"line","type":"uint256"},{"internalType":"uint256","name":"dust","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"ilk","type":"bytes32"}],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"live","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"src","type":"address"},{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"rad","type":"uint256"}],"name":"move","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"nope","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"rely","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"sin","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"ilk","type":"bytes32"},{"internalType":"address","name":"usr","type":"address"},{"internalType":"int256","name":"wad","type":"int256"}],"name":"slip","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"u","type":"address"},{"internalType":"address","name":"v","type":"address"},{"internalType":"uint256","name":"rad","type":"uint256"}],"name":"suck","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"name":"urns","outputs":[{"internalType":"uint256","name":"ink","type":"uint256"},{"internalType":"uint256","name":"art","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"wards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}],"contractName":"Vat"}
8 changes: 8 additions & 0 deletions tests/functional/test_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ def assert_receipt_logs(receipt: ReceiptAPI, num: int):
assert_receipt_logs(receipt_2, 3)


def test_contract_decode_logs_no_abi(owner, contract_instance):
receipt = contract_instance.setNumber(1, sender=owner)
events = list(receipt.decode_logs()) # no abi
assert len(events) == 1
assert events[0].name == "NumberChange"
assert events[0].newNum == 1


def test_contract_logs_from_event_type(contract_instance, owner, assert_log_values):
event_type = contract_instance.NumberChange

Expand Down
32 changes: 32 additions & 0 deletions tests/functional/test_ethereum.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
from eth_abi import encode_single
from eth_typing import HexAddress, HexStr
from hexbytes import HexBytes

Expand Down Expand Up @@ -109,3 +110,34 @@ def test_block_handles_snake_case_parent_hash(eth_tester_provider, sender, recei

redefined_block = Block.parse_obj(latest_block_dict)
assert redefined_block.parent_hash == latest_block.parent_hash


def test_decode_ds_note(ethereum, mainnet_contract, chain):
mainnet_contract("0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B")
log = {
"address": "0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B",
"topics": [
HexBytes("0x7608870300000000000000000000000000000000000000000000000000000000"),
HexBytes("0x5946492d41000000000000000000000000000000000000000000000000000000"),
HexBytes("0x0000000000000000000000000abb839063ef747c8432b2acc60bf8f70ec09a45"),
HexBytes("0x0000000000000000000000000abb839063ef747c8432b2acc60bf8f70ec09a45"),
],
"data": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e0760887035946492d410000000000000000000000000000000000000000000000000000000000000000000000000000000abb839063ef747c8432b2acc60bf8f70ec09a450000000000000000000000000abb839063ef747c8432b2acc60bf8f70ec09a450000000000000000000000000abb839063ef747c8432b2acc60bf8f70ec09a450000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffa050e82a57b7fc6b6020c00000000000000000000000000000000000000000000000000000000", # noqa: E501
"blockNumber": 14623434,
"transactionHash": HexBytes(
"0xa322a9fd0e627e22bfe1b0877cca1d1f2e697d076007231d0b7a366d1a0fdd51"
),
"transactionIndex": 333,
"blockHash": HexBytes("0x0fd77b0af3fa471aa040a02d4fcd1ec0a35122a4166d0bb7c31354e23823de49"),
"logIndex": 376,
"removed": False,
}
ilk = encode_single("bytes32", b"YFI-A")
assert ethereum.decode_ds_note(log).event_arguments == {
"i": ilk,
"u": "0x0abb839063ef747c8432b2acc60bf8f70ec09a45",
"v": "0x0abb839063ef747c8432b2acc60bf8f70ec09a45",
"w": "0x0abb839063ef747c8432b2acc60bf8f70ec09a45",
"dink": 0,
"dart": -7229675416790010075676148,
}