From 91bb2d34c137b52dfcf761945dc5ee67438b4494 Mon Sep 17 00:00:00 2001 From: darwintree <17946284+darwintree@users.noreply.github.com> Date: Tue, 24 Sep 2024 18:46:53 +0800 Subject: [PATCH] feat: implement cip-1559 changes --- conflux_web3/_utils/method_formatters.py | 30 +++++- conflux_web3/_utils/rpc_abi.py | 3 + conflux_web3/client.py | 25 +++++ conflux_web3/types/__init__.py | 132 +++++++++++------------ tests/_test_helpers/type_check.py | 15 +-- tests/rpcs/test_cfx_rpcs.py | 15 +++ tests/transaction/test_base_tx.py | 3 +- 7 files changed, 146 insertions(+), 77 deletions(-) diff --git a/conflux_web3/_utils/method_formatters.py b/conflux_web3/_utils/method_formatters.py index f29337d..9e120bc 100644 --- a/conflux_web3/_utils/method_formatters.py +++ b/conflux_web3/_utils/method_formatters.py @@ -123,6 +123,10 @@ def from_hex_to_drip(val: Any): RPC.cfx_getSkippedBlocksByEpoch: apply_formatter_at_index(to_hex_if_integer, 0), RPC.cfx_getBlockByHashWithPivotAssumption: apply_formatter_at_index(to_hex_if_integer, 2), RPC.cfx_getEpochReceipts: apply_formatter_at_index(to_hex_if_integer, 0), + RPC.cfx_feeHistory: compose( + apply_formatter_at_index(to_hex_if_integer, 0), + apply_formatter_at_index(to_hex_if_integer, 1), + ), RPC.cfx_getCode: apply_formatter_at_index(to_hex_if_integer, 1), RPC.cfx_getStorageAt: apply_formatter_at_index(to_hex_if_integer, 2), @@ -229,6 +233,7 @@ def from_hex_to_drip(val: Any): log_entry_formatter = apply_formatters_to_dict(LOG_ENTRY_FORMATTERS) RECEIPT_FORMATTERS = { + "type": to_integer_if_hex, "transactionHash": to_hash32, "index": to_integer_if_hex, "blockHash": apply_formatter_if(is_not_null, to_hash32), @@ -247,11 +252,17 @@ def from_hex_to_drip(val: Any): "outcomeStatus": to_integer_if_hex, "logsBloom": to_hexbytes(256), # type: ignore "logs": apply_list_to_array_formatter(log_entry_formatter), + "burntGasFee": from_hex_to_drip, + "effectiveGasPrice": from_hex_to_drip, } # receipt_formatter = apply_formatters_to_dict(RECEIPT_FORMATTERS) +ACCESS_LIST_ENTRY_FORMATTERS = { + "address": from_trust_to_base32, +} TRANSACTION_DATA_FORMATTERS = { + "type": to_integer_if_hex, "blockHash": apply_formatter_if(is_not_null, to_hash32), "chainId": apply_formatter_if(is_not_null, to_integer_if_hex), "contractCreated": apply_formatter_if(is_not_null, from_trust_to_base32), @@ -270,6 +281,10 @@ def from_hex_to_drip(val: Any): "transactionIndex": apply_formatter_if(is_not_null, to_integer_if_hex), "v": apply_formatter_if(is_not_null, to_integer_if_hex), "value": from_hex_to_drip, + "maxFeePerGas": apply_formatter_if(is_not_null, from_hex_to_drip), + "maxPriorityFeePerGas": apply_formatter_if(is_not_null, from_hex_to_drip), + "accessList": apply_formatter_if(is_not_null, apply_list_to_array_formatter(apply_formatters_to_dict(ACCESS_LIST_ENTRY_FORMATTERS))), + "yParity": apply_formatter_if(is_not_null, to_integer_if_hex), } transaction_data_formatter = apply_formatters_to_dict(TRANSACTION_DATA_FORMATTERS) @@ -311,7 +326,8 @@ def from_hex_to_drip(val: Any): ), (is_array_of_strings, apply_list_to_array_formatter(to_hash32)), ) - ) + ), + "baseFeePerGas": apply_formatter_if(is_not_null, from_hex_to_drip), } block_formatter = apply_formatters_to_dict(BLOCK_FORMATTERS) @@ -395,6 +411,7 @@ def fixed64_to_float(val: str) -> float: "powBaseReward": from_hex_to_drip, "interestRate": to_integer_if_hex, "storagePointProp": to_integer_if_hex, + "baseFeeShareProp": to_integer_if_hex, } SUPPLY_INFO_FORMATTERS = { @@ -419,6 +436,14 @@ def fixed64_to_float(val: str) -> float: ) } +FEE_HISTORY_FORMATTERS = { + "baseFeePerGas": apply_list_to_array_formatter(from_hex_to_drip), + # "gasUsedRatio": lambda x: x, # do nothing + "oldestEpoch": to_integer_if_hex, + "reward": apply_list_to_array_formatter(apply_list_to_array_formatter(from_hex_to_drip)), +} + + SIMPLE_RESULT_FORMATTER_MAPPING: Dict[Type[Any], Callable[..., Any]] = { int: to_integer_if_hex, Drip: from_hex_to_drip, @@ -449,6 +474,7 @@ def create_dict_result_formatter(typed_dict_class: Type[TypedDict]) -> Callable[ RPC.cfx_estimateGasAndCollateral: apply_formatters_to_dict(ESTIMATE_FORMATTERS), RPC.cfx_getConfirmationRiskByHash: fixed64_to_float, RPC.cfx_gasPrice: from_hex_to_drip, + RPC.cfx_maxPriorityFeePerGas: from_hex_to_drip, RPC.cfx_getBalance: from_hex_to_drip, RPC.cfx_getStakingBalance: from_hex_to_drip, RPC.cfx_getNextNonce: to_integer_if_hex, @@ -463,6 +489,7 @@ def create_dict_result_formatter(typed_dict_class: Type[TypedDict]) -> Callable[ is_not_null, apply_formatters_to_dict(RECEIPT_FORMATTERS), ))), + RPC.cfx_feeHistory: apply_formatters_to_dict(FEE_HISTORY_FORMATTERS), RPC.cfx_getLogs: filter_result_formatter, RPC.cfx_getFilterLogs: filter_result_formatter, @@ -501,6 +528,7 @@ def create_dict_result_formatter(typed_dict_class: Type[TypedDict]) -> Callable[ ), RPC.cfx_getSupplyInfo: apply_formatters_to_dict(SUPPLY_INFO_FORMATTERS), RPC.cfx_getCollateralInfo: create_dict_result_formatter(CollateralInfo), + RPC.cfx_getFeeBurnt: from_hex_to_drip, RPC.cfx_getAccountPendingInfo: apply_formatters_to_dict(PENDING_INFO_FORMATTERS), RPC.cfx_getAccountPendingTransactions: apply_formatters_to_dict(PENDING_TRANSACTIONS_INFO_FORMATTERS), diff --git a/conflux_web3/_utils/rpc_abi.py b/conflux_web3/_utils/rpc_abi.py index fab3312..33b6e28 100644 --- a/conflux_web3/_utils/rpc_abi.py +++ b/conflux_web3/_utils/rpc_abi.py @@ -23,6 +23,9 @@ class RPC: cfx_getBlockRewardInfo = RPCEndpoint("cfx_getBlockRewardInfo") cfx_getBlocksByEpoch = RPCEndpoint("cfx_getBlocksByEpoch") cfx_getEpochReceipts = RPCEndpoint("cfx_getEpochReceipts") + cfx_maxPriorityFeePerGas = RPCEndpoint("cfx_maxPriorityFeePerGas") + cfx_feeHistory = RPCEndpoint("cfx_feeHistory") + cfx_getFeeBurnt = RPCEndpoint("cfx_getFeeBurnt") cfx_getCode = RPCEndpoint("cfx_getCode") cfx_getCollateralForStorage = RPCEndpoint("cfx_getCollateralForStorage") diff --git a/conflux_web3/client.py b/conflux_web3/client.py index 66ecea7..f0545f2 100644 --- a/conflux_web3/client.py +++ b/conflux_web3/client.py @@ -85,6 +85,7 @@ ) from conflux_web3.types import ( EstimateResult, + FeeHistory, TxReceipt, TxData, NodeStatus, @@ -396,6 +397,18 @@ def default_account_munger( RPC.cfx_checkBalanceAgainstTransaction ) + _max_priority_fee_per_gas: ConfluxMethod[Callable[[], Drip]] = ConfluxMethod( + RPC.cfx_maxPriorityFeePerGas + ) + + _fee_history: ConfluxMethod[Callable[[int, EpochNumberParam, Sequence[float]], FeeHistory]] = ConfluxMethod( + RPC.cfx_feeHistory + ) + + _get_fee_burnt: ConfluxMethod[Callable[[], Drip]] = ConfluxMethod( + RPC.cfx_getFeeBurnt + ) + _get_logs: ConfluxMethod[Callable[[FilterParams], List[LogReceipt]]] = ConfluxMethod( RPC.cfx_getLogs ) @@ -1668,3 +1681,15 @@ def uninstall_filter(self, filter_id: _FilterId) -> bool: def get_admin(self, address: AddressParam, block_identifier: Optional[EpochNumberParam] = None) -> Union[None, Base32Address]: return self._get_admin(address, block_identifier) + + def fee_history(self, epoch_count: int, block_identifier: EpochNumberParam, reward_percentiles: Sequence[float]) -> FeeHistory: + return self._fee_history(epoch_count, block_identifier, reward_percentiles) + + def get_fee_burnt(self) -> Drip: + return self._get_fee_burnt() + + @property + def max_priority_fee_per_gas(self) -> Drip: + return self._max_priority_fee_per_gas() + + \ No newline at end of file diff --git a/conflux_web3/types/__init__.py b/conflux_web3/types/__init__.py index 7c7eb3e..74939ba 100644 --- a/conflux_web3/types/__init__.py +++ b/conflux_web3/types/__init__.py @@ -202,6 +202,7 @@ class EventData(TransactionEventData, total=False): TxReceipt = TypedDict( "TxReceipt", { + "type": int, "transactionHash": Hash32, "index": int, "blockHash": Hash32, @@ -220,6 +221,8 @@ class EventData(TransactionEventData, total=False): "logsBloom": HexBytes, "logs": List[TransactionLogReceipt], "txExecErrorMsg": Union[str, None], + "effectiveGasPrice": Drip, + "burntGasFee": Drip, }, ) """ @@ -227,6 +230,7 @@ class EventData(TransactionEventData, total=False): Parameters ---------- +| "type": int, | "transactionHash": Hash32, | "index": int, | "blockHash": Hash32, @@ -245,16 +249,25 @@ class EventData(TransactionEventData, total=False): | "logsBloom": HexBytes, | "logs": List[TransactionLogReceipt], | "txExecErrorMsg": Union[str, None] +| "effectiveGasPrice": Drip, +| "burntGasFee": Drip, """ class TxReceiptWithSpace(TxReceipt): space: Literal["native", "evm"] +class AccessListEntry(TypedDict): + address: Base32Address + storageKeys: Sequence[HexStr] + +AccessList = NewType("AccessList", Sequence[AccessListEntry]) + # syntax b/c "from" keyword not allowed w/ class construction -TxData = TypedDict( - "TxData", +LegacyTxData = TypedDict( + "LegacyTxData", { + "type": int, "blockHash": Union[None, Hash32], "chainId": int, "contractCreated": Union[None, Base32Address], @@ -276,30 +289,37 @@ class TxReceiptWithSpace(TxReceipt): }, total=False, ) -""" -Transaction data as a dict -Parameters ----------- -| "blockHash": Union[None, Hash32], -| "chainId": int, -| "contractCreated": Union[None, Base32Address], -| "data": HexBytes, -| "epochHeight": int, -| "from": Base32Address, -| "gas": int, -| "gasPrice": Drip, -| "hash": Hash32, -| "nonce": Nonce, -| "r": HexBytes, -| "s": HexBytes, -| "status": Union[None, int], -| "storageLimit": Storage, -| "to": Union[None, Base32Address], -| "transactionIndex": Union[None, int], -| "v": int, -| "value": Drip, -""" +class TxData(LegacyTxData, total=False): + """ + Transaction data as a dict + + Parameters + ---------- + | "type": int, + | "blockHash": Union[None, Hash32], + | "chainId": int, + | "contractCreated": Union[None, Base32Address], + | "data": HexBytes, + | "epochHeight": int, + | "from": Base32Address, + | "gas": int, + | "gasPrice": Drip, + | "hash": Hash32, + | "nonce": Nonce, + | "r": HexBytes, + | "s": HexBytes, + | "status": Union[None, int], + | "storageLimit": Storage, + | "to": Union[None, Base32Address], + | "transactionIndex": Union[None, int], + | "v": int, + | "value": Drip, + """ + maxFeePerGas: Optional[Drip] + maxPriorityFeePerGas: Optional[Drip] + accessList: Optional[AccessList] + yParity: Optional[int] class BlockData(TypedDict): """ @@ -354,6 +374,7 @@ class BlockData(TypedDict): custom: Sequence[HexBytes] posReference: Hash32 transactions: Sequence[Union[Hash32, TxData]] + baseFeePerGas: Drip Middleware = Callable[[Callable[[RPCEndpoint, Any], RPCResponse], "Web3"], Any] @@ -505,10 +526,12 @@ class DAOVoteInfo(TypedDict): | powBaseReward: Drip | interestRate: int | storagePointProp: int + | baseFeeShareProp: int """ powBaseReward: Drip interestRate: int storagePointProp: int + baseFeeShareProp: int class SupplyInfo(TypedDict): """ @@ -590,47 +613,18 @@ class CollateralInfo(TypedDict): _FilterId = Union[LogFilterId, BlockFilterId, TxFilterId, str] -__all__ = [ - "TxDict", - "TxParam", - "HexAddress", - "Drip", - "CFX", - "GDrip", - "AddressParam", - "Storage", - "EpochNumberParam", - "EpochLiteral", - "EpochNumber", - "NodeStatus", - "EstimateResult", - "FilterParams", - "TransactionLogReceipt", - "LogReceipt", - "TransactionEventData", - "EventData", - "TxReceipt", - "TxReceiptWithSpace" - "TxData", - "BlockData", - "MiddlewareOnion", - "StorageRoot", - "SponsorInfo", - "AccountInfo", - "DepositInfo", - "VoteInfo", - "BlockRewardInfo", - "PoSEconomicsInfo", - "PoSAccountRewardsInfo", - "PoSEpochRewardInfo", - "DAOVoteInfo", - "SupplyInfo", - "PendingInfo", - "PendingTransactionsInfo", - "TransactionPaymentInfo", - "CollateralInfo", - "LogFilterId", - "BlockFilterId", - "TxFilterId", - "_FilterId", -] +class FeeHistory(TypedDict): + """ + Fee history information + + Parameters + ---------- + | baseFeePerGas: Sequence[Drip] - List of base fee per gas values for each epoch + | gasUsedRatio: Sequence[float] - List of gas used ratios for each epoch pivot block + | oldestEpoch: int - The oldest epoch number in the returned range + | reward: Sequence[Sequence[Drip]] - List of effective priority fee per gas values for each epoch + """ + baseFeePerGas: Sequence[Drip] + gasUsedRatio: Sequence[float] + oldestEpoch: int + reward: Sequence[Sequence[Drip]] diff --git a/tests/_test_helpers/type_check.py b/tests/_test_helpers/type_check.py index e6e419f..48e2804 100644 --- a/tests/_test_helpers/type_check.py +++ b/tests/_test_helpers/type_check.py @@ -90,13 +90,16 @@ def isinstance(val: Any, field_type: Type[Any]) -> bool: @staticmethod def assert_instance(val: Any, field_type: Type[Any]) -> Literal[True]: + from pydantic import BaseModel, ValidationError + if is_typeddict(field_type): - annotations = field_type.__annotations__ - for key, sub_field_type in annotations.items(): - if key not in val: - raise TypeError(f"missed key for typed_dict: {key} not in {val}") - if not TypeValidator.isinstance(val[key], sub_field_type): - raise TypeError(f"unexpected value type for typed_dict field: {val[key]} should be type {sub_field_type}") + class PydanticModel(BaseModel, arbitrary_types_allowed=True): + __annotations__ = field_type.__annotations__ + + try: + PydanticModel(**val) + except ValidationError as e: + raise TypeError(f"Validation error for typed_dict: {e}") return True if get_origin(field_type) is Union: for t in get_args(field_type): diff --git a/tests/rpcs/test_cfx_rpcs.py b/tests/rpcs/test_cfx_rpcs.py index b36e98a..b173145 100644 --- a/tests/rpcs/test_cfx_rpcs.py +++ b/tests/rpcs/test_cfx_rpcs.py @@ -263,6 +263,21 @@ def preprocess_block_data(block_data: BlockData, use_testnet: bool) -> BlockData block_data['posReference'] = HexBytes("0x0") # type: ignore return block_data +def test_get_fee_burnt(moduled_w3: Web3): + w3 = moduled_w3 + fee_burnt = w3.cfx.get_fee_burnt() + assert isinstance(fee_burnt, Drip) + +def test_fee_history(moduled_w3: Web3): + w3 = moduled_w3 + fee_history = w3.cfx.fee_history(5, "latest_state", [20,50]) + TypeValidator.validate_typed_dict(fee_history, "FeeHistory") + +def test_max_priority_fee_per_gas(moduled_w3: Web3): + w3 = moduled_w3 + fee = w3.cfx.max_priority_fee_per_gas + assert isinstance(fee, Drip) + class TestBlock: @pytest.fixture def block_hash(self, w3: Web3, tx_hash: HexBytes): diff --git a/tests/transaction/test_base_tx.py b/tests/transaction/test_base_tx.py index 541aa2e..4f9dc64 100644 --- a/tests/transaction/test_base_tx.py +++ b/tests/transaction/test_base_tx.py @@ -24,7 +24,8 @@ def test_send_raw_transaction(w3: Web3, account: LocalAccount): rawTx = signed.rawTransaction r = w3.cfx.send_raw_transaction(rawTx) assert isinstance(r, bytes) - w3.cfx.wait_for_transaction_receipt(r) + receipt = w3.cfx.wait_for_transaction_receipt(r) + TypeValidator.validate_typed_dict(receipt, "TxReceipt") def test_basetx_estimate(w3: Web3, address: Base32Address): """