Skip to content

Commit

Permalink
feat: handle type 106
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey committed Aug 22, 2023
1 parent 96c497a commit 0a2f6ff
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 14 deletions.
148 changes: 137 additions & 11 deletions ape_arbitrum/ecosystem.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import time
from enum import Enum
from typing import Optional, Type, Union, cast

from ape.api import TransactionAPI
from ape.api.config import PluginConfig
from ape.api.networks import LOCAL_NETWORK_NAME
from ape.exceptions import ApeException
from ape.api.transactions import ConfirmationsProgressBar, ReceiptAPI, TransactionAPI
from ape.exceptions import ApeException, TransactionError
from ape.logging import logger
from ape.types import TransactionSignature
from ape.utils import DEFAULT_LOCAL_TRANSACTION_ACCEPTANCE_TIMEOUT
from ape_ethereum.ecosystem import Ethereum, NetworkConfig
from ape_ethereum.transactions import DynamicFeeTransaction, StaticFeeTransaction, TransactionType
from ape_ethereum.transactions import (
DynamicFeeTransaction,
Receipt,
StaticFeeTransaction,
TransactionStatusEnum,
TransactionType,
)
from eth_utils import decode_hex
from ethpm_types import HexBytes
from pydantic.fields import Field

NETWORKS = {
# chain_id, network_id
Expand All @@ -17,12 +28,84 @@
}


class ArbitrumTransactionType(Enum):
STATIC = TransactionType.STATIC.value
ACCESS_LIST = TransactionType.ACCESS_LIST.value # EIP-2930
DYNAMIC = TransactionType.DYNAMIC.value # EIP-1559
INTERNAL = 106 # Arbitrum only


class InternalTransaction(StaticFeeTransaction):
type: int = Field(ArbitrumTransactionType.INTERNAL.value, exclude=True)


class InternalReceipt(Receipt):
gas_limit = 0


class ApeArbitrumError(ApeException):
"""
Raised in the ape-arbitrum plugin.
"""


class ArbitrumReceipt(Receipt):
def await_confirmations(self) -> "ReceiptAPI":
"""
Overridden to handle skipping nonce-check for internal txns.
"""

try:
self.raise_for_status()
except TransactionError:
# Skip waiting for confirmations when the transaction has failed.
return self

iterations_timeout = 20
iteration = 0
# Wait for nonce from provider to increment.
if self.sender and self.type != ArbitrumTransactionType.INTERNAL.value:
sender_nonce = self.provider.get_nonce(self.sender)

while sender_nonce == self.nonce:
time.sleep(1)
sender_nonce = self.provider.get_nonce(self.sender)
iteration += 1
if iteration == iterations_timeout:
raise TransactionError("Timeout waiting for sender's nonce to increase.")

if self.required_confirmations == 0:
# The transaction might not yet be confirmed but
# the user is aware of this. Or, this is a development environment.
return self

confirmations_occurred = self._confirmations_occurred
if self.required_confirmations and confirmations_occurred >= self.required_confirmations:
return self

# If we get here, that means the transaction has been recently submitted.
if explorer_url := self._explorer and self._explorer.get_transaction_url(self.txn_hash):
log_message = f"Submitted {explorer_url}"
else:
log_message = f"Submitted {self.txn_hash}"

logger.info(log_message)

if self.required_confirmations:
with ConfirmationsProgressBar(self.required_confirmations) as progress_bar:
while confirmations_occurred < self.required_confirmations:
confirmations_occurred = self._confirmations_occurred
progress_bar.confs = confirmations_occurred

if confirmations_occurred == self.required_confirmations:
break

time_to_sleep = int(self._block_time / 2)
time.sleep(time_to_sleep)

return self


def _create_network_config(
required_confirmations: int = 1, block_time: int = 1, **kwargs
) -> NetworkConfig:
Expand Down Expand Up @@ -94,20 +177,63 @@ def create_transaction(self, **kwargs) -> TransactionAPI:

return txn_class.parse_obj(kwargs)

def get_transaction_type(self, _type: Optional[Union[int, str, bytes]]) -> TransactionType:
def get_transaction_type(
self, _type: Optional[Union[int, str, bytes]]
) -> ArbitrumTransactionType:
if _type is None:
version = TransactionType.STATIC
version = ArbitrumTransactionType.STATIC
elif not isinstance(_type, int):
version = TransactionType(self.conversion_manager.convert(_type, int))
version = ArbitrumTransactionType(self.conversion_manager.convert(_type, int))
else:
version = TransactionType(_type)
return version
version = ArbitrumTransactionType(_type)

return version

def _get_transaction_cls(transaction_type: TransactionType) -> Type[TransactionAPI]:
def decode_receipt(self, data: dict) -> ReceiptAPI:
"""
NOTE: Overridden to use custom receipt class.
"""
status = data.get("status")
if status:
status = self.conversion_manager.convert(status, int)
status = TransactionStatusEnum(status)

txn_hash = None
hash_key_choices = ("hash", "txHash", "txnHash", "transactionHash", "transaction_hash")
for choice in hash_key_choices:
if choice in data:
txn_hash = data[choice]
break

if txn_hash:
txn_hash = txn_hash.hex() if isinstance(txn_hash, HexBytes) else txn_hash

data_bytes = data.get("data", b"")
if data_bytes and isinstance(data_bytes, str):
data["data"] = HexBytes(data_bytes)

elif "input" in data and isinstance(data["input"], str):
data["input"] = HexBytes(data["input"])

receipt = ArbitrumReceipt(
block_number=data.get("block_number") or data.get("blockNumber"),
contract_address=data.get("contract_address") or data.get("contractAddress"),
gas_limit=data.get("gas", data.get("gas_limit", data.get("gasLimit"))) or 0,
gas_price=data.get("gas_price", data.get("gasPrice")) or 0,
gas_used=data.get("gas_used", data.get("gasUsed")) or 0,
logs=data.get("logs", []),
status=status,
txn_hash=txn_hash,
transaction=self.create_transaction(**data),
)
return receipt


def _get_transaction_cls(transaction_type: ArbitrumTransactionType) -> Type[TransactionAPI]:
transaction_types = {
TransactionType.STATIC: StaticFeeTransaction,
TransactionType.DYNAMIC: DynamicFeeTransaction,
ArbitrumTransactionType.STATIC: StaticFeeTransaction,
ArbitrumTransactionType.DYNAMIC: DynamicFeeTransaction,
ArbitrumTransactionType.INTERNAL: InternalTransaction,
}
if transaction_type not in transaction_types:
raise ApeArbitrumError(f"Transaction type '{transaction_type}' not supported.")
Expand Down
53 changes: 50 additions & 3 deletions tests/test_ecosystem.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import pytest
from ape_ethereum.transactions import TransactionType
from ethpm_types import HexBytes

from ape_arbitrum.ecosystem import ArbitrumReceipt, ArbitrumTransactionType


def test_gas_limit(arbitrum):
Expand All @@ -8,5 +10,50 @@ def test_gas_limit(arbitrum):

@pytest.mark.parametrize("type", (0, "0x0"))
def test_create_transaction(arbitrum, type):
txn = arbitrum.create_transaction(type=type)
assert txn.type == TransactionType.STATIC.value
tx = arbitrum.create_transaction(type=type)
assert tx.type == ArbitrumTransactionType.STATIC.value


def test_internal_tx(arbitrum):
tx = arbitrum.create_transaction(type=106)
assert tx.type == ArbitrumTransactionType.INTERNAL.value


def test_decode_receipt(arbitrum):
data = {
"required_confirmations": 0,
"blockHash": HexBytes("0x01b9030516454bbb3d846bfa31fca8bf5cbdfb735879dcee27fd61f2ae3776b3"),
"blockNumber": 121166619,
"hash": HexBytes("0x8b8c74711aa2e117a307f8a96a93350e5ca7e01a7bf39dbb7a824e6a6fc3736f"),
"chainId": 42161,
"from": "0x00000000000000000000000000000000000A4B05",
"gas": 0,
"gasPrice": 0,
"input": HexBytes(
"0x6bf6a42d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011148fc000000000000000000000000000000000000000000000000000000000738db1b0000000000000000000000000000000000000000000000000000000000000000" # noqa: E501
),
"nonce": 0,
"r": HexBytes("0x00"),
"s": HexBytes("0x00"),
"to": "0x00000000000000000000000000000000000A4B05",
"transactionIndex": 0,
"type": 106,
"v": 0,
"value": 0,
"transactionHash": HexBytes(
"0x8b8c74711aa2e117a307f8a96a93350e5ca7e01a7bf39dbb7a824e6a6fc3736f"
),
"logs": [],
"contractAddress": None,
"effectiveGasPrice": 100000000,
"cumulativeGasUsed": 0,
"gasUsed": 0,
"logsBloom": HexBytes(
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" # noqa: E501
),
"status": 1,
"l1BlockNumber": "0x11148fc",
"gasUsedForL1": "0x0",
}
actual = arbitrum.decode_receipt(data)
assert isinstance(actual, ArbitrumReceipt)

0 comments on commit 0a2f6ff

Please sign in to comment.