• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

safe-global / safe-eth-py / 10793540350

10 Sep 2024 01:31PM UTC coverage: 93.551% (-0.3%) from 93.892%
10793540350

push

github

falvaradorodriguez
Fix cowswap test

8777 of 9382 relevant lines covered (93.55%)

3.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.58
/safe_eth/eth/tests/test_ethereum_client.py
1
from typing import Any, Dict, List, Sequence
4✔
2
from unittest import mock
4✔
3
from unittest.mock import MagicMock
4✔
4

5
from django.test import TestCase
4✔
6

7
import pytest
4✔
8
import requests
4✔
9
from eth_account import Account
4✔
10
from eth_typing import URI, HexStr
4✔
11
from hexbytes import HexBytes
4✔
12
from web3.eth import Eth
4✔
13
from web3.types import TxParams
4✔
14

15
from ..constants import GAS_CALL_DATA_BYTE, NULL_ADDRESS
4✔
16
from ..contracts import get_erc20_contract
4✔
17
from ..ethereum_client import (
4✔
18
    EthereumClient,
19
    EthereumNetwork,
20
    FromAddressNotFound,
21
    InsufficientFunds,
22
    InvalidNonce,
23
    SenderAccountNotFoundInNode,
24
    TracingManager,
25
    get_auto_ethereum_client,
26
)
27
from ..exceptions import BatchCallException, ChainIdIsRequired, InvalidERC20Info
4✔
28
from .ethereum_test_case import EthereumTestCaseMixin
4✔
29
from .mocks.mock_internal_txs import creation_internal_txs, internal_txs_errored
4✔
30
from .mocks.mock_log_receipts import invalid_log_receipt, log_receipts
4✔
31
from .mocks.mock_trace_block import (
4✔
32
    trace_block_2191709_mock,
33
    trace_block_13191781_mock,
34
    trace_block_15630274_mock,
35
)
36
from .mocks.mock_trace_filter import trace_filter_mock_1
4✔
37
from .mocks.mock_trace_transaction import trace_transaction_mocks
4✔
38
from .utils import just_test_if_mainnet_node
4✔
39

40

41
class TestERC20Module(EthereumTestCaseMixin, TestCase):
4✔
42
    def test_decode_transfer_log(self):
4✔
43
        decoded_logs = self.ethereum_client.erc20.decode_logs(
4✔
44
            log_receipts + [invalid_log_receipt]
45
        )
46
        self.assertEqual(len(decoded_logs), 6)
4✔
47
        self.assertEqual(
4✔
48
            len([event for event in decoded_logs if "tokenId" in event["args"]]), 2
49
        )
50
        self.assertEqual(
4✔
51
            len([event for event in decoded_logs if "value" in event["args"]]), 3
52
        )
53
        self.assertEqual(
4✔
54
            len([event for event in decoded_logs if "unknown" in event["args"]]), 1
55
        )
56

57
        expected_log_1 = {
4✔
58
            "address": "0x39C4BFa00b6edecCDd00fA9589E1BE76DE63e862",
59
            "topics": [
60
                HexBytes(
61
                    "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
62
                ),
63
                HexBytes(
64
                    "0x00000000000000000000000094e01661ebaef430fe862f958c03200b0f483f27"
65
                ),
66
                HexBytes(
67
                    "0x00000000000000000000000064da772dd84965f0ee58174941d78a9dfbccca2e"
68
                ),
69
            ],
70
            "data": "0x000000000000000000000000000000000000000000000001a055690d9db80000",
71
            "blockNumber": 4357126,
72
            "transactionHash": HexBytes(
73
                "0x21381484d8f69dcd782560d1fd3cd818e743c79767985d01aec7e61c2a7f1de9"
74
            ),
75
            "transactionIndex": 14,
76
            "blockHash": HexBytes(
77
                "0x677ada1a306fc50751001bca6eeaa3f5a87a0bf2c9f6fa27899bfbaf999cca4f"
78
            ),
79
            "logIndex": 14,
80
            "removed": False,
81
            "args": {
82
                "from": "0x94E01661eBaef430fE862f958c03200b0F483f27",
83
                "to": "0x64DA772DD84965f0Ee58174941d78a9DfBccca2e",
84
                "value": 30000000000000000000,
85
            },
86
        }
87
        expected_log_3 = {
4✔
88
            "address": "0x99b9F9BA62002a9b43aF6e540428277D5E52EF47",
89
            "topics": [
90
                HexBytes(
91
                    "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
92
                ),
93
                HexBytes(
94
                    "0x00000000000000000000000099b9f9ba62002a9b43af6e540428277d5e52ef47"
95
                ),
96
                HexBytes(
97
                    "0x00000000000000000000000094e01661ebaef430fe862f958c03200b0f483f27"
98
                ),
99
                HexBytes(
100
                    "0xcc292d3dab2c0fbbf616670ac57ec51162959c2d9cbe938819b6e8bc1c757335"
101
                ),
102
            ],
103
            "data": "0x",
104
            "blockNumber": 4357126,
105
            "transactionHash": HexBytes(
106
                "0x21381484d8f69dcd782560d1fd3cd818e743c79767985d01aec7e61c2a7f1de9"
107
            ),
108
            "transactionIndex": 14,
109
            "blockHash": HexBytes(
110
                "0x677ada1a306fc50751001bca6eeaa3f5a87a0bf2c9f6fa27899bfbaf999cca4f"
111
            ),
112
            "logIndex": 17,
113
            "removed": False,
114
            "args": {
115
                "from": "0x99b9F9BA62002a9b43aF6e540428277D5E52EF47",
116
                "to": "0x94E01661eBaef430fE862f958c03200b0F483f27",
117
                "tokenId": 92344574081811136966607054691835999266725413506859602442775390826469350798133,
118
            },
119
        }
120

121
        expected_log_5 = {
4✔
122
            "address": "0xe35F3B71CA90eE2606F64b645D8F4f8DCaA914Bf",
123
            "topics": [
124
                HexBytes(
125
                    "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
126
                )
127
            ],
128
            "data": "0x0000000000000000000000008683f9c4e856be65f8a38a3a768e8fd6de94d30a000000000000000000000000fd1017c3284a12ac33bc65df12d71721c85931e00000000000000000000000000000000000000000000000000000000000000001",
129
            "blockNumber": 7240557,
130
            "transactionHash": HexBytes(
131
                "0x1962e296457b16d5221d33623f2db5f617cb54221deb7cfd73611f761ac526a3"
132
            ),
133
            "transactionIndex": 6,
134
            "blockHash": HexBytes(
135
                "0xdf3c33d034f1b342820afdfb2612d0794d4a1c15518184d71a047ef1eb151d10"
136
            ),
137
            "logIndex": 5,
138
            "removed": False,
139
            "args": {
140
                "from": "0x8683f9c4e856be65f8a38a3a768e8fd6de94d30a",
141
                "to": "0xfd1017c3284a12ac33bc65df12d71721c85931e0",
142
                "unknown": 1,
143
            },
144
        }
145

146
        self.assertEqual(decoded_logs[1], expected_log_1)
4✔
147
        self.assertEqual(decoded_logs[3], expected_log_3)
4✔
148
        self.assertEqual(decoded_logs[5], expected_log_5)
4✔
149

150
    def test_decode_invalid_transfer_log(self):
4✔
151
        invalid_transfer_logs = [invalid_log_receipt]
4✔
152
        self.assertEqual(
4✔
153
            self.ethereum_client.erc20.decode_logs(invalid_transfer_logs), []
154
        )
155

156
    def test_get_name_symbol_balance(self):
4✔
157
        amount = 1000
4✔
158
        address = Account.create().address
4✔
159
        erc20_contract = self.deploy_example_erc20(amount, address)
4✔
160
        token_balance = self.ethereum_client.erc20.get_balance(
4✔
161
            address, erc20_contract.address
162
        )
163
        self.assertTrue(token_balance, amount)
4✔
164

165
        another_account = Account.create().address
4✔
166
        token_balance = self.ethereum_client.erc20.get_balance(
4✔
167
            another_account, erc20_contract.address
168
        )
169
        self.assertEqual(token_balance, 0)
4✔
170

171
        self.assertTrue(self.ethereum_client.erc20.get_name(erc20_contract.address))
4✔
172
        self.assertTrue(self.ethereum_client.erc20.get_symbol(erc20_contract.address))
4✔
173

174
    def test_get_balances(self):
4✔
175
        account_address = Account.create().address
4✔
176
        self.assertEqual(
4✔
177
            self.ethereum_client.erc20.get_balances(account_address, []),
178
            [{"token_address": None, "balance": 0}],
179
        )
180

181
        value = 7
4✔
182
        self.send_ether(account_address, 7)
4✔
183
        self.assertEqual(
4✔
184
            self.ethereum_client.erc20.get_balances(account_address, []),
185
            [{"token_address": None, "balance": value}],
186
        )
187

188
        tokens_value = 12
4✔
189
        erc20 = self.deploy_example_erc20(tokens_value, account_address)
4✔
190
        self.assertCountEqual(
4✔
191
            self.ethereum_client.erc20.get_balances(account_address, [erc20.address]),
192
            [
193
                {"token_address": None, "balance": value},
194
                {"token_address": erc20.address, "balance": tokens_value},
195
            ],
196
        )
197

198
        tokens_value_2 = 19
4✔
199
        erc20_2 = self.deploy_example_erc20(tokens_value_2, account_address)
4✔
200
        self.assertCountEqual(
4✔
201
            self.ethereum_client.erc20.get_balances(
202
                account_address, [erc20.address, erc20_2.address]
203
            ),
204
            [
205
                {"token_address": None, "balance": value},
206
                {"token_address": erc20.address, "balance": tokens_value},
207
                {"token_address": erc20_2.address, "balance": tokens_value_2},
208
            ],
209
        )
210

211
        self.assertCountEqual(
4✔
212
            self.ethereum_client.erc20.get_balances(
213
                account_address,
214
                [erc20.address, erc20_2.address],
215
                include_native_balance=False,
216
            ),
217
            [
218
                {"token_address": erc20.address, "balance": tokens_value},
219
                {"token_address": erc20_2.address, "balance": tokens_value_2},
220
            ],
221
        )
222

223
        with mock.patch.object(
4✔
224
            EthereumClient,
225
            "batch_call_same_function",
226
            return_value=[
227
                b"\x08\xc3y\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17Only the proxy can call\x00\x00\x00\x00\x00\x00\x00\x00\x00",
228
                5,
229
            ],
230
        ):
231
            token_addresses = [
4✔
232
                "0x57Ab1E02fEE23774580C119740129eAC7081e9D3",
233
                "0x6810e776880C02933D47DB1b9fc05908e5386b96",
234
            ]
235
            self.assertCountEqual(
4✔
236
                self.ethereum_client.erc20.get_balances(
237
                    account_address, token_addresses
238
                ),
239
                [
240
                    {"token_address": None, "balance": value},
241
                    {
242
                        "token_address": "0x57Ab1E02fEE23774580C119740129eAC7081e9D3",
243
                        "balance": 0,
244
                    },
245
                    {
246
                        "token_address": "0x6810e776880C02933D47DB1b9fc05908e5386b96",
247
                        "balance": 5,
248
                    },
249
                ],
250
            )
251

252
    def test_get_total_transfer_history(self):
4✔
253
        amount = 50
4✔
254
        owner_account = self.create_and_fund_account(initial_ether=0.01)
4✔
255
        account_1 = self.create_and_fund_account(initial_ether=0.01)
4✔
256
        account_2 = self.create_and_fund_account(initial_ether=0.01)
4✔
257
        account_3 = self.create_and_fund_account(initial_ether=0.01)
4✔
258
        erc20_contract = self.deploy_example_erc20(amount, owner_account.address)
4✔
259
        # `owner` sends `amount // 2` to `account_1` and `account_3`
260
        self.send_tx(
4✔
261
            erc20_contract.functions.transfer(
262
                account_1.address, amount // 2
263
            ).build_transaction({"from": owner_account.address}),
264
            owner_account,
265
        )
266
        self.send_tx(
4✔
267
            erc20_contract.functions.transfer(
268
                account_3.address, amount // 2
269
            ).build_transaction({"from": owner_account.address}),
270
            owner_account,
271
        )
272
        logs = self.ethereum_client.erc20.get_total_transfer_history(
4✔
273
            [account_1.address]
274
        )
275
        self.assertEqual(len(logs), 1)
4✔
276
        self.assertEqual(logs[0]["args"]["from"], owner_account.address)
4✔
277
        self.assertEqual(logs[0]["args"]["to"], account_1.address)
4✔
278
        self.assertEqual(logs[0]["args"]["value"], amount // 2)
4✔
279
        log_0_block_number = logs[0]["blockNumber"]
4✔
280

281
        # `account1` sends `amount // 2` (all) to `account_2`
282
        self.send_tx(
4✔
283
            erc20_contract.functions.transfer(
284
                account_2.address, amount // 2
285
            ).build_transaction({"from": account_1.address}),
286
            account_1,
287
        )
288
        # Test `token_address` and `to_block` parameters
289
        logs = self.ethereum_client.erc20.get_total_transfer_history(
4✔
290
            [account_1.address], token_address=erc20_contract.address, to_block="latest"
291
        )
292
        self.assertEqual(len(logs), 2)
4✔
293
        self.assertEqual(logs[1]["args"]["from"], account_1.address)
4✔
294
        self.assertEqual(logs[1]["args"]["to"], account_2.address)
4✔
295
        self.assertEqual(logs[1]["args"]["value"], amount // 2)
4✔
296
        self.assertGreaterEqual(logs[1]["blockNumber"], log_0_block_number)
4✔
297

298
        logs = self.ethereum_client.erc20.get_total_transfer_history(
4✔
299
            [account_3.address]
300
        )
301
        self.assertEqual(len(logs), 1)
4✔
302
        self.assertEqual(logs[0]["args"]["from"], owner_account.address)
4✔
303
        self.assertEqual(logs[0]["args"]["to"], account_3.address)
4✔
304
        self.assertEqual(logs[0]["args"]["value"], amount // 2)
4✔
305

306
        logs = self.ethereum_client.erc20.get_total_transfer_history(
4✔
307
            [account_2.address, account_3.address]
308
        )
309
        self.assertEqual(len(logs), 2)
4✔
310

311
        logs = self.ethereum_client.erc20.get_total_transfer_history()
4✔
312
        self.assertGreaterEqual(len(logs), 3)
4✔
313

314
    def test_get_transfer_history(self):
4✔
315
        amount = 1000
4✔
316
        owner_account = self.create_and_fund_account(initial_ether=0.01)
4✔
317

318
        # Owner will send amount / 2 to receiver and receiver2. Then receiver1 and receiver 2
319
        # will send amount / 4 to receiver3
320
        receiver_account = self.create_and_fund_account(initial_ether=0.01)
4✔
321
        receiver2_account = self.create_and_fund_account(initial_ether=0.01)
4✔
322
        receiver3_account = self.create_and_fund_account(initial_ether=0.01)
4✔
323
        erc20_contract = self.deploy_example_erc20(amount, owner_account.address)
4✔
324
        block_number = self.w3.eth.block_number
4✔
325
        events = self.ethereum_client.erc20.get_transfer_history(
4✔
326
            block_number, token_address=erc20_contract.address
327
        )
328
        self.assertEqual(len(events), 1)  # Event is triggered on minting
4✔
329

330
        self.send_tx(
4✔
331
            erc20_contract.functions.transfer(
332
                receiver_account.address, amount // 2
333
            ).build_transaction({"from": owner_account.address}),
334
            owner_account,
335
        )
336
        self.send_tx(
4✔
337
            erc20_contract.functions.transfer(
338
                receiver2_account.address, amount // 2
339
            ).build_transaction({"from": owner_account.address}),
340
            owner_account,
341
        )
342

343
        self.send_tx(
4✔
344
            erc20_contract.functions.transfer(
345
                receiver3_account.address, amount // 4
346
            ).build_transaction({"from": receiver_account.address}),
347
            receiver_account,
348
        )
349
        self.send_tx(
4✔
350
            erc20_contract.functions.transfer(
351
                receiver3_account.address, amount // 4
352
            ).build_transaction({"from": receiver2_account.address}),
353
            receiver2_account,
354
        )
355

356
        events = self.ethereum_client.erc20.get_transfer_history(
4✔
357
            block_number, token_address=erc20_contract.address
358
        )
359
        self.assertEqual(len(events), 5)
4✔
360

361
        events = self.ethereum_client.erc20.get_transfer_history(
4✔
362
            block_number, from_address=owner_account.address
363
        )
364
        self.assertEqual(len(events), 2)
4✔
365

366
        events = self.ethereum_client.erc20.get_transfer_history(
4✔
367
            block_number, from_address=receiver_account.address
368
        )
369
        self.assertEqual(len(events), 1)
4✔
370

371
        events = self.ethereum_client.erc20.get_transfer_history(
4✔
372
            block_number, from_address=receiver2_account.address
373
        )
374
        self.assertEqual(len(events), 1)
4✔
375

376
        events = self.ethereum_client.erc20.get_transfer_history(
4✔
377
            block_number, from_address=receiver3_account.address
378
        )
379
        self.assertEqual(len(events), 0)
4✔
380

381
        events = self.ethereum_client.erc20.get_transfer_history(
4✔
382
            block_number, to_address=receiver2_account.address
383
        )
384
        self.assertEqual(len(events), 1)
4✔
385

386
        events = self.ethereum_client.erc20.get_transfer_history(
4✔
387
            block_number, to_address=receiver3_account.address
388
        )
389
        self.assertEqual(len(events), 2)
4✔
390
        for event in events:
4✔
391
            self.assertEqual(event["args"]["value"], amount // 4)
4✔
392
            self.assertEqual(event["args"]["to"], receiver3_account.address)
4✔
393

394
        events = self.ethereum_client.erc20.get_transfer_history(
4✔
395
            block_number,
396
            from_address=receiver2_account.address,
397
            to_address=receiver3_account.address,
398
        )
399
        self.assertEqual(len(events), 1)
4✔
400
        event = events[0]
4✔
401
        self.assertEqual(event["args"]["value"], amount // 4)
4✔
402
        self.assertEqual(event["args"]["from"], receiver2_account.address)
4✔
403
        self.assertEqual(event["args"]["to"], receiver3_account.address)
4✔
404

405
    def test_get_info(self):
4✔
406
        amount = 1
4✔
407
        owner = Account.create().address
4✔
408
        erc20_contract = self.deploy_example_erc20(amount, owner)
4✔
409
        erc20_info = self.ethereum_client.erc20.get_info(erc20_contract.address)
4✔
410
        self.assertEqual(erc20_info.decimals, 18)
4✔
411

412
        with self.assertRaises(InvalidERC20Info):
4✔
413
            self.ethereum_client.erc20.get_info(Account.create().address)
4✔
414

415
    def test_send_tokens(self):
4✔
416
        amount = 5
4✔
417
        owner = self.ethereum_test_account
4✔
418
        owner_2 = Account.create()
4✔
419
        erc20 = self.deploy_example_erc20(amount, owner.address)
4✔
420
        self.assertEqual(
4✔
421
            self.ethereum_client.erc20.get_balance(owner.address, erc20.address), amount
422
        )
423

424
        amount_2 = 3
4✔
425
        self.ethereum_client.erc20.send_tokens(
4✔
426
            owner_2.address, amount_2, erc20.address, owner.key
427
        )
428
        self.assertEqual(
4✔
429
            self.ethereum_client.erc20.get_balance(owner.address, erc20.address),
430
            amount - amount_2,
431
        )
432
        self.assertEqual(
4✔
433
            self.ethereum_client.erc20.get_balance(owner_2.address, erc20.address),
434
            amount_2,
435
        )
436

437

438
class TestTracingManager(EthereumTestCaseMixin, TestCase):
4✔
439
    def test_filter_out_errored_traces(self):
4✔
440
        self.assertEqual(self.ethereum_client.tracing.filter_out_errored_traces([]), [])
4✔
441
        traces = internal_txs_errored
4✔
442
        expected = [
4✔
443
            {
444
                "action": {
445
                    "from": "0x667dEb5A98f77052cf561658575cF1530Ee42C7a",
446
                    "gas": 60066,
447
                    "value": 0,
448
                    "callType": "call",
449
                    "input": HexBytes(
450
                        "0x6a76120200000000000000000000000090c6e02acc0ff725c0127feac32a53c4b10b03b700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000092000000000000000000000000000000000000000000000000000000000000007a44f84885b000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000007600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000003200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006200000000000000000000000002eaa9d77ae4d8f9cdd9faacd44016e746485bddb00000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000006d7f0754ffeb405d23c51ce938289d4835be3b1400000000000000000000000052201ff1720134bbbbb2f6bc97bf3715490ec19b000000000000000000000000ebf1a11532b93a529b5bc942b4baa98647913002000000000000000000000000ebe09eb3411d18f4ff8d859e096c533cac5c6b60000000000000000000000000d6801a1dffcd0a410336ef88def4320d6df1883e0000000000000000000000005b281a6dda0b271e91ae35de655ad301c976edb10000000000000000000000001a32b1734d964b039320c7712aa65b43c826d4dd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000002448ee2641d78cc42d7ad76498917359d961a78300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000006d7f0754ffeb405d23c51ce938289d4835be3b14000000000000000000000000000000000000000000000000000000000000012c0000000000000000000000001a32b1734d964b039320c7712aa65b43c826d4dd0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000012c0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000048e13085b1efda7283d958100e4cbfacb0ce5012000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012c00000000000000000000000000000000000000000000000000000000000000020000000000000000000000001a32b1734d964b039320c7712aa65b43c826d4dd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041000000000000000000000000667deb5a98f77052cf561658575cf1530ee42c7a00000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000"
451
                    ),
452
                    "to": "0x47F61944efdB020829caead65AfF8AC024600580",
453
                },
454
                "blockHash": "0x4c49052fc99be82b91f8a35320826304dfe278dbd7d756edde000d331606358f",
455
                "blockNumber": 4735890,
456
                "result": {
457
                    "gasUsed": 21838,
458
                    "output": HexBytes(
459
                        "0x0000000000000000000000000000000000000000000000000000000000000000"
460
                    ),
461
                },
462
                "subtraces": 1,
463
                "traceAddress": [],
464
                "transactionHash": "0xf097d5e5dd39a6799fc13dfa49732a115b457386520dc92f99f0135a1d196851",
465
                "transactionPosition": 3,
466
                "type": "call",
467
            },
468
            {
469
                "action": {
470
                    "from": "0x47F61944efdB020829caead65AfF8AC024600580",
471
                    "gas": 57726,
472
                    "value": 0,
473
                    "callType": "delegatecall",
474
                    "input": HexBytes(
475
                        "0x6a76120200000000000000000000000090c6e02acc0ff725c0127feac32a53c4b10b03b700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000092000000000000000000000000000000000000000000000000000000000000007a44f84885b000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000007600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000003200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006200000000000000000000000002eaa9d77ae4d8f9cdd9faacd44016e746485bddb00000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000006d7f0754ffeb405d23c51ce938289d4835be3b1400000000000000000000000052201ff1720134bbbbb2f6bc97bf3715490ec19b000000000000000000000000ebf1a11532b93a529b5bc942b4baa98647913002000000000000000000000000ebe09eb3411d18f4ff8d859e096c533cac5c6b60000000000000000000000000d6801a1dffcd0a410336ef88def4320d6df1883e0000000000000000000000005b281a6dda0b271e91ae35de655ad301c976edb10000000000000000000000001a32b1734d964b039320c7712aa65b43c826d4dd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000002448ee2641d78cc42d7ad76498917359d961a78300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000006d7f0754ffeb405d23c51ce938289d4835be3b14000000000000000000000000000000000000000000000000000000000000012c0000000000000000000000001a32b1734d964b039320c7712aa65b43c826d4dd0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000012c0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000048e13085b1efda7283d958100e4cbfacb0ce5012000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012c00000000000000000000000000000000000000000000000000000000000000020000000000000000000000001a32b1734d964b039320c7712aa65b43c826d4dd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041000000000000000000000000667deb5a98f77052cf561658575cf1530ee42c7a00000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000"
476
                    ),
477
                    "to": "0xb6029EA3B2c51D09a50B53CA8012FeEB05bDa35A",
478
                },
479
                "blockHash": "0x4c49052fc99be82b91f8a35320826304dfe278dbd7d756edde000d331606358f",
480
                "blockNumber": 4735890,
481
                "result": {
482
                    "gasUsed": 20369,
483
                    "output": HexBytes(
484
                        "0x0000000000000000000000000000000000000000000000000000000000000000"
485
                    ),
486
                },
487
                "subtraces": 1,
488
                "traceAddress": [0],
489
                "transactionHash": "0xf097d5e5dd39a6799fc13dfa49732a115b457386520dc92f99f0135a1d196851",
490
                "transactionPosition": 3,
491
                "type": "call",
492
            },
493
        ]
494
        self.assertEqual(
4✔
495
            self.ethereum_client.tracing.filter_out_errored_traces(traces), expected
496
        )
497

498
        traces = [
4✔
499
            {
500
                "traceAddress": [],
501
            },
502
            {
503
                "traceAddress": [0],
504
            },
505
            {
506
                "traceAddress": [0, 0],
507
            },
508
            {
509
                "error": "reverted",
510
                "traceAddress": [0, 0, 0],
511
            },
512
            {
513
                "traceAddress": [0, 0, 0, 0],
514
            },
515
            {
516
                "traceAddress": [0, 0, 0, 1],
517
            },
518
            {
519
                "traceAddress": [0, 0, 1],
520
            },
521
            {
522
                "traceAddress": [0, 0, 1, 0],
523
            },
524
            {
525
                "error": "reverted",
526
                "traceAddress": [0, 0, 1, 0, 0],
527
            },
528
            {
529
                "traceAddress": [0, 0, 1, 0, 1],
530
            },
531
            {
532
                "traceAddress": [0, 0, 1, 0, 2],
533
            },
534
            {
535
                "error": "reverted",
536
                "traceAddress": [0, 0, 1, 0, 3],
537
            },
538
            {
539
                "traceAddress": [0, 0, 2],
540
            },
541
        ]
542
        expected = [
4✔
543
            {
544
                "traceAddress": [],
545
            },
546
            {
547
                "traceAddress": [0],
548
            },
549
            {
550
                "traceAddress": [0, 0],
551
            },
552
            {
553
                "traceAddress": [0, 0, 1],
554
            },
555
            {
556
                "traceAddress": [0, 0, 1, 0],
557
            },
558
            {
559
                "traceAddress": [0, 0, 1, 0, 1],
560
            },
561
            {
562
                "traceAddress": [0, 0, 1, 0, 2],
563
            },
564
            {
565
                "traceAddress": [0, 0, 2],
566
            },
567
        ]
568
        self.assertEqual(
4✔
569
            sorted(traces, key=lambda trace: trace["traceAddress"]), traces
570
        )
571
        self.assertEqual(
4✔
572
            self.ethereum_client.tracing.filter_out_errored_traces(traces), expected
573
        )
574

575
    @mock.patch.object(
4✔
576
        TracingManager, "trace_transaction", return_value=internal_txs_errored
577
    )
578
    def test_get_previous_trace(self, trace_transaction_mock: MagicMock):
4✔
579
        trace_result = self.ethereum_client.tracing.get_previous_trace(
4✔
580
            HexStr("0x12"), [0, 0]
581
        )
582
        assert trace_result is not None
4✔
583
        assert trace_result.get("traceAddress") is not None
4✔
584
        self.assertEqual(trace_result.get("traceAddress"), [0])
4✔
585

586
        trace_result_2_traces = self.ethereum_client.tracing.get_previous_trace(
4✔
587
            HexStr("0x12"), [0, 0], number_traces=2
588
        )
589
        assert trace_result_2_traces is not None
4✔
590
        assert trace_result_2_traces.get("traceAddress") is not None
4✔
591
        self.assertEqual(trace_result_2_traces.get("traceAddress"), [])
4✔
592

593
        trace_result_skip_delegate_calls = (
4✔
594
            self.ethereum_client.tracing.get_previous_trace(
595
                HexStr("0x12"), [0, 0], skip_delegate_calls=True
596
            )
597
        )
598
        assert trace_result_skip_delegate_calls is not None
4✔
599
        assert trace_result_skip_delegate_calls.get("traceAddress") is not None
4✔
600
        self.assertEqual(trace_result_skip_delegate_calls.get("traceAddress"), [])
4✔
601

602
        trace_result_3_traces = self.ethereum_client.tracing.get_previous_trace(
4✔
603
            HexStr("0x12"), [0, 0], number_traces=3
604
        )
605
        self.assertIsNone(trace_result_3_traces)
4✔
606

607
        trace_result_00_trace_address = self.ethereum_client.tracing.get_previous_trace(
4✔
608
            HexStr("0x12"), [0, 0, 0], skip_delegate_calls=True
609
        )
610
        assert trace_result_00_trace_address is not None
4✔
611
        assert trace_result_00_trace_address.get("traceAddress") is not None
4✔
612
        self.assertEqual(trace_result_00_trace_address.get("traceAddress"), [0, 0])
4✔
613

614
    @mock.patch.object(
4✔
615
        TracingManager, "trace_transaction", return_value=creation_internal_txs
616
    )
617
    def test_get_next_traces(self, trace_transaction_mock: MagicMock):
4✔
618
        def trace_addresses(traces: Sequence[Dict[str, Any]]) -> List[List[int]]:
4✔
619
            return [trace["traceAddress"] for trace in traces]
4✔
620

621
        self.assertEqual(
4✔
622
            trace_addresses(
623
                self.ethereum_client.tracing.get_next_traces(HexStr("0x12"), [])
624
            ),
625
            [[0], [1]],
626
        )
627
        self.assertEqual(
4✔
628
            trace_addresses(
629
                self.ethereum_client.tracing.get_next_traces(
630
                    HexStr("0x12"), [], remove_delegate_calls=True
631
                )
632
            ),
633
            [[1]],
634
        )
635
        self.assertEqual(
4✔
636
            trace_addresses(
637
                self.ethereum_client.tracing.get_next_traces(
638
                    HexStr("0x12"), [], remove_calls=True
639
                )
640
            ),
641
            [[0]],
642
        )
643
        self.assertEqual(
4✔
644
            trace_addresses(
645
                self.ethereum_client.tracing.get_next_traces(
646
                    HexStr("0x12"), [], remove_delegate_calls=True, remove_calls=True
647
                )
648
            ),
649
            [],
650
        )
651
        self.assertEqual(
4✔
652
            self.ethereum_client.tracing.get_next_traces(HexStr("0x12"), [0]), []
653
        )
654
        self.assertEqual(
4✔
655
            trace_addresses(
656
                self.ethereum_client.tracing.get_next_traces(HexStr("0x12"), [1])
657
            ),
658
            [[1, 0]],
659
        )
660

661
    def test_trace_filter(self):
4✔
662
        with self.assertRaisesMessage(AssertionError, "at least"):
4✔
663
            self.ethereum_client.tracing.trace_filter()
4✔
664

665
        with self.assertRaisesMessage(
4✔
666
            ValueError, "The method trace_filter does not exist/is not available"
667
        ):
668
            self.ethereum_client.tracing.trace_filter(
4✔
669
                to_address=[Account.create().address]
670
            )
671

672
    @mock.patch.object(requests.Response, "json")
4✔
673
    def test_raw_batch_request(self, session_post_mock: MagicMock):
4✔
674
        # Ankr
675
        session_post_mock.return_value = {
4✔
676
            "jsonrpc": "2.0",
677
            "error": {
678
                "code": 0,
679
                "message": "you can't send more than 1000 requests in a batch",
680
            },
681
            "id": None,
682
        }
683
        payload = [
4✔
684
            {
685
                "id": 0,
686
                "jsonrpc": "2.0",
687
                "method": "eth_getTransactionByHash",
688
                "params": "0x5afea3f32970a22f4e63a815c174fa989e3b659826e5f52496662bb256baf3b2",
689
            },
690
            {
691
                "id": 1,
692
                "jsonrpc": "2.0",
693
                "method": "eth_getTransactionByHash",
694
                "params": "0x12ab96991ddd4ac55c28ace4e7b59bc64c514b55747e1b0ea3f5b269fbb39f6b",
695
            },
696
        ]
697
        with self.assertRaisesMessage(
4✔
698
            ValueError,
699
            "Batch request error: {'jsonrpc': '2.0', 'error': {'code': 0, 'message': \"you can't send more than 1000 requests in a batch\"}, 'id': None}",
700
        ):
701
            list(self.ethereum_client.raw_batch_request(payload))
4✔
702

703
        # Nodereal
704
        session_post_mock.return_value = [
4✔
705
            {
706
                "jsonrpc": "2.0",
707
                "id": None,
708
                "error": {
709
                    "code": -32000,
710
                    "message": "batch length does not support more than 500",
711
                },
712
            }
713
        ]
714

715
        with self.assertRaisesMessage(
4✔
716
            ValueError,
717
            "Problem with payload=`{'id': 0, 'jsonrpc': '2.0', 'method': 'eth_getTransactionByHash', 'params': '0x5afea3f32970a22f4e63a815c174fa989e3b659826e5f52496662bb256baf3b2'}` result={'jsonrpc': '2.0', 'id': None, 'error': {'code': -32000, 'message': 'batch length does not support more than 500'}}",
718
        ):
719
            list(self.ethereum_client.raw_batch_request(payload))
4✔
720

721
        # Test batching chunks
722
        session_post_mock.return_value = [
4✔
723
            {
724
                "jsonrpc": "2.0",
725
                "id": 0,
726
                "result": {
727
                    "blockHash": "0x13e9e3262d9cf1c4d07d7324d95e6bddf27f07d7bddbdcc7df4e4ffb42a2e921",
728
                    "blockNumber": "0xa81a59",
729
                    "from": "0x136ec956eb32364f5016f3f84f56dbff59c6ead5",
730
                    "gas": "0x493e0",
731
                    "gasPrice": "0x3b9aca0e",
732
                    "maxPriorityFeePerGas": "0x3b9aca00",
733
                    "maxFeePerGas": "0x3b9aca1e",
734
                    "hash": "0x92898917d7bd7a51d40a903f4c55ae988cbac7c661c3e271c54bbda21415501b",
735
                    "input": "0x8ea59e1de547ab59caab9379b4b307450a29a0137c7dbbfc7b18c3cd6179d927efbab9ee",
736
                    "nonce": "0x1242f",
737
                    "to": "0x7e22c795325e76306920293f62a02f353536280b",
738
                    "transactionIndex": "0x1e",
739
                    "value": "0x0",
740
                    "type": "0x2",
741
                    "accessList": [],
742
                    "chainId": "0x4",
743
                    "v": "0x1",
744
                    "r": "0x5aaaa2a32326ca4add9a602ffba968c3d991219fde93a2531eb7a82fc61919ed",
745
                    "s": "0x1c4bff2abcc671ad2a1dd09f92a9720ac595138c666e59153711056811c1c95c",
746
                },
747
            }
748
        ]
749

750
        results = list(self.ethereum_client.raw_batch_request(payload, batch_size=1))
4✔
751
        self.assertEqual(len(results), 2)
4✔
752

753

754
class TestEthereumNetwork(EthereumTestCaseMixin, TestCase):
4✔
755
    def test_unknown_ethereum_network_name(self):
4✔
756
        self.assertEqual(EthereumNetwork(123456789), EthereumNetwork.UNKNOWN)
4✔
757

758
    def test_mainnet_ethereum_network_name(self):
4✔
759
        self.assertEqual(EthereumNetwork(1), EthereumNetwork.MAINNET)
4✔
760

761
    def test_rinkeby_ethereum_network_name(self):
4✔
762
        self.assertEqual(EthereumNetwork(4), EthereumNetwork.RINKEBY)
4✔
763

764

765
class TestEthereumClient(EthereumTestCaseMixin, TestCase):
4✔
766
    def test_ethereum_client_str(self):
4✔
767
        self.assertTrue(str(self.ethereum_client))
4✔
768

769
    def test_current_block_number(self):
4✔
770
        self.assertGreaterEqual(self.ethereum_client.current_block_number, 0)
4✔
771

772
    def test_get_transaction(self):
4✔
773
        to = Account.create().address
4✔
774
        value = 123
4✔
775
        tx_hash = self.send_ether(to, value)
4✔
776
        tx = self.ethereum_client.get_transaction(tx_hash)
4✔
777
        self.assertEqual(tx["to"], to)
4✔
778
        self.assertEqual(tx["value"], value)
4✔
779
        block = self.ethereum_client.get_block(tx["blockNumber"])
4✔
780
        self.assertEqual(block["number"], tx["blockNumber"])
4✔
781

782
    def test_get_transactions(self):
4✔
783
        self.assertEqual(self.ethereum_client.get_transactions([]), [])
4✔
784
        to = Account.create().address
4✔
785
        values = [123, 234, 567]
4✔
786
        tx_hashes = [self.send_ether(to, values[i]) for i in range(3)]
4✔
787
        txs = self.ethereum_client.get_transactions(tx_hashes)
4✔
788
        for i, tx in enumerate(txs):
4✔
789
            self.assertEqual(tx["to"], to)
4✔
790
            self.assertEqual(tx["value"], values[i])
4✔
791

792
        self.assertEqual(self.ethereum_client.get_transaction_receipts([]), [])
4✔
793
        receipts = self.ethereum_client.get_transaction_receipts(tx_hashes)
4✔
794
        for i, receipt in enumerate(receipts):
4✔
795
            self.assertEqual(receipt["status"], 1)
4✔
796
            self.assertGreaterEqual(receipt["gasUsed"], 21000)
4✔
797

798
    def test_check_tx_with_confirmations(self):
4✔
799
        value = 1
4✔
800
        to = Account.create().address
4✔
801

802
        tx_hash = self.ethereum_client.send_eth_to(
4✔
803
            self.ethereum_test_account.key, to=to, gas_price=self.gas_price, value=value
804
        )
805
        self.assertFalse(self.ethereum_client.check_tx_with_confirmations(tx_hash, 2))
4✔
806

807
        _ = self.ethereum_client.send_eth_to(
4✔
808
            self.ethereum_test_account.key, to=to, gas_price=self.gas_price, value=value
809
        )
810
        self.assertFalse(self.ethereum_client.check_tx_with_confirmations(tx_hash, 2))
4✔
811

812
        _ = self.ethereum_client.send_eth_to(
4✔
813
            self.ethereum_test_account.key, to=to, gas_price=self.gas_price, value=value
814
        )
815
        self.assertTrue(self.ethereum_client.check_tx_with_confirmations(tx_hash, 2))
4✔
816

817
    def test_estimate_gas(self):
4✔
818
        send_ether_gas = 21000
4✔
819
        from_ = self.ethereum_test_account.address
4✔
820
        to = Account.create().address
4✔
821
        gas = self.ethereum_client.estimate_gas(
4✔
822
            to, from_=from_, value=5, data=None, block_identifier="pending"
823
        )
824
        self.assertEqual(gas, send_ether_gas)
4✔
825

826
        gas = self.ethereum_client.estimate_gas(to, from_=from_, value=5, data=b"")
4✔
827
        self.assertEqual(gas, send_ether_gas)
4✔
828

829
        gas = self.ethereum_client.estimate_gas(to, from_=from_, value=5, data=None)
4✔
830
        self.assertEqual(gas, send_ether_gas)
4✔
831

832
    def test_estimate_gas_with_erc20(self):
4✔
833
        send_ether_gas = 21000
4✔
834
        amount_tokens = 5000
4✔
835
        amount_to_send = amount_tokens // 2
4✔
836
        from_account = self.ethereum_test_account
4✔
837
        from_ = from_account.address
4✔
838
        to = Account.create().address
4✔
839

840
        erc20_contract = self.deploy_example_erc20(amount_tokens, from_)
4✔
841
        transfer_tx = erc20_contract.functions.transfer(
4✔
842
            to, amount_to_send
843
        ).build_transaction({"from": from_})
844
        data = transfer_tx["data"]
4✔
845

846
        # Ganache does not cares about all this anymore
847
        # ---------------------------------------------
848
        # Ganache returns error because there is data but address is not a contract
849
        # with self.assertRaisesMessage(ValueError, 'transaction which calls a contract function'):
850
        #    self.ethereum_client.estimate_gas(from_, to, 0, data, block_identifier='pending')
851
        # `to` is not a contract, no functions will be triggered
852
        # gas = self.ethereum_client.estimate_gas(from_, to, 0, data, block_identifier='pending')
853
        # self.assertGreater(gas, send_ether_gas)
854
        # self.assertLess(gas, send_ether_gas + 2000)
855

856
        gas = self.ethereum_client.estimate_gas(
4✔
857
            erc20_contract.address,
858
            from_=from_,
859
            value=0,
860
            data=data,
861
            block_identifier="pending",
862
        )
863
        self.assertGreater(gas, send_ether_gas)
4✔
864

865
        # We do the real erc20 transfer for the next test case
866
        self.send_tx(transfer_tx, from_account)
4✔
867
        token_balance = self.ethereum_client.erc20.get_balance(
4✔
868
            to, erc20_contract.address
869
        )
870
        self.assertTrue(token_balance, amount_to_send)
4✔
871

872
        # Gas will be lowest now because you don't have to pay for storage
873
        gas2 = self.ethereum_client.estimate_gas(
4✔
874
            erc20_contract.address,
875
            from_=from_,
876
            value=0,
877
            data=data,
878
            block_identifier="pending",
879
        )
880
        self.assertLess(gas2, gas)
4✔
881

882
    def test_get_ethereum_network(self):
4✔
883
        self.assertEqual(self.ethereum_client.get_network(), EthereumNetwork.GANACHE)
4✔
884

885
        with mock.patch.object(
4✔
886
            Eth, "chain_id", return_value=1, new_callable=mock.PropertyMock
887
        ):
888
            # Test caching
889
            self.assertEqual(
4✔
890
                self.ethereum_client.get_network(), EthereumNetwork.GANACHE
891
            )
892
            self.ethereum_client.get_chain_id.cache_clear()
4✔
893
            self.assertEqual(
4✔
894
                self.ethereum_client.get_network(), EthereumNetwork.MAINNET
895
            )
896
            self.ethereum_client.get_chain_id.cache_clear()
4✔
897

898
        with mock.patch.object(
4✔
899
            Eth, "chain_id", return_value=4, new_callable=mock.PropertyMock
900
        ):
901
            self.assertEqual(
4✔
902
                self.ethereum_client.get_network(), EthereumNetwork.RINKEBY
903
            )
904
            self.ethereum_client.get_chain_id.cache_clear()
4✔
905

906
        with mock.patch.object(
4✔
907
            Eth, "chain_id", return_value=4815162342, new_callable=mock.PropertyMock
908
        ):
909
            self.assertEqual(
4✔
910
                self.ethereum_client.get_network(), EthereumNetwork.UNKNOWN
911
            )
912
            self.ethereum_client.get_chain_id.cache_clear()
4✔
913

914
    def test_get_nonce(self):
4✔
915
        address = Account.create().address
4✔
916
        nonce = self.ethereum_client.get_nonce_for_account(address)
4✔
917
        self.assertEqual(nonce, 0)
4✔
918

919
        nonce = self.ethereum_client.get_nonce_for_account(
4✔
920
            address, block_identifier="pending"
921
        )
922
        self.assertEqual(nonce, 0)
4✔
923

924
    def test_estimate_data_gas(self):
4✔
925
        self.assertEqual(self.ethereum_client.estimate_data_gas(HexBytes("")), 0)
4✔
926
        self.assertEqual(self.ethereum_client.estimate_data_gas(HexBytes("0x00")), 4)
4✔
927
        self.assertEqual(
4✔
928
            self.ethereum_client.estimate_data_gas(HexBytes("0x000204")),
929
            4 + GAS_CALL_DATA_BYTE * 2,
930
        )
931
        self.assertEqual(
4✔
932
            self.ethereum_client.estimate_data_gas(HexBytes("0x050204")),
933
            GAS_CALL_DATA_BYTE * 3,
934
        )
935
        self.assertEqual(
4✔
936
            self.ethereum_client.estimate_data_gas(HexBytes("0x0502040000")),
937
            GAS_CALL_DATA_BYTE * 3 + 4 * 2,
938
        )
939
        self.assertEqual(
4✔
940
            self.ethereum_client.estimate_data_gas(HexBytes("0x050204000001")),
941
            GAS_CALL_DATA_BYTE * 4 + 4 * 2,
942
        )
943
        self.assertEqual(
4✔
944
            self.ethereum_client.estimate_data_gas(HexBytes("0x00050204000001")),
945
            4 + GAS_CALL_DATA_BYTE * 4 + 4 * 2,
946
        )
947

948
    def test_provider_singleton(self):
4✔
949
        ethereum_client1 = get_auto_ethereum_client()
4✔
950
        ethereum_client2 = get_auto_ethereum_client()
4✔
951
        self.assertEqual(ethereum_client1, ethereum_client2)
4✔
952

953
    def test_send_eth_to(self):
4✔
954
        address = Account.create().address
4✔
955
        value = 1
4✔
956
        self.ethereum_client.send_eth_to(
4✔
957
            self.ethereum_test_account.key, address, self.gas_price, value
958
        )
959
        self.assertEqual(self.ethereum_client.get_balance(address), value)
4✔
960

961
    def test_send_eth_without_key(self):
4✔
962
        with self.settings(SAFE_FUNDER_PRIVATE_KEY=None):
4✔
963
            self.test_send_eth_to()
4✔
964

965
    def test_send_transaction(self):
4✔
966
        account = self.ethereum_test_account
4✔
967
        address = account.address
4✔
968
        to = Account.create().address
4✔
969
        value = 1
4✔
970
        tx = {
4✔
971
            "to": to,
972
            "value": value,
973
            "gas": 23000,
974
            "gasPrice": self.gas_price,
975
            "nonce": self.ethereum_client.get_nonce_for_account(address),
976
        }
977

978
        with self.assertRaises(FromAddressNotFound):
4✔
979
            self.ethereum_client.send_transaction(tx)
4✔
980

981
        # Random address not in the node
982
        tx["from"] = Account.create().address
4✔
983
        with self.assertRaises(SenderAccountNotFoundInNode):
4✔
984
            self.ethereum_client.send_transaction(tx)
4✔
985

986
        tx["from"] = address
4✔
987
        self.ethereum_client.send_transaction(tx)
4✔
988
        self.assertEqual(self.ethereum_client.get_balance(to), 1)
4✔
989

990
        with self.assertRaises(InvalidNonce):
4✔
991
            self.ethereum_client.send_transaction(tx)
4✔
992

993
        tx["value"] = self.ethereum_client.get_balance(address) + 1
4✔
994
        tx["nonce"] = self.ethereum_client.get_nonce_for_account(address)
4✔
995
        with self.assertRaises(InsufficientFunds):
4✔
996
            self.ethereum_client.send_transaction(tx)
4✔
997

998
    def test_send_unsigned_transaction(self):
4✔
999
        account = self.ethereum_test_account
4✔
1000
        address = account.address
4✔
1001
        to = Account.create().address
4✔
1002
        value = 4
4✔
1003

1004
        tx = {
4✔
1005
            "to": to,
1006
            "value": value,
1007
            "gas": 23000,
1008
        }
1009

1010
        with self.assertRaisesMessage(
4✔
1011
            ValueError, "Ethereum account was not configured"
1012
        ):
1013
            self.ethereum_client.send_unsigned_transaction(tx)
4✔
1014

1015
        self.ethereum_client.send_unsigned_transaction(tx, public_key=address)
4✔
1016
        self.assertEqual(self.ethereum_client.get_balance(to), value)
4✔
1017
        first_nonce = tx["nonce"]
4✔
1018
        self.assertGreaterEqual(first_nonce, 0)
4✔
1019

1020
        # Will use the same nonce
1021
        with self.assertRaisesMessage(InvalidNonce, "correct nonce"):
4✔
1022
            self.ethereum_client.send_unsigned_transaction(tx, public_key=address)
4✔
1023

1024
        # With retry, everything should work
1025
        self.ethereum_client.send_unsigned_transaction(
4✔
1026
            tx, public_key=address, retry=True
1027
        )
1028
        self.assertEqual(tx["nonce"], first_nonce + 1)
4✔
1029
        self.assertEqual(self.ethereum_client.get_balance(to), value * 2)
4✔
1030

1031
        # We try again with the first nonce, and should work too
1032
        tx["nonce"] = first_nonce
4✔
1033
        self.ethereum_client.send_unsigned_transaction(
4✔
1034
            tx, public_key=address, retry=True
1035
        )
1036
        self.assertEqual(tx["nonce"], first_nonce + 2)
4✔
1037
        self.assertEqual(self.ethereum_client.get_balance(to), value * 3)
4✔
1038

1039
    @pytest.mark.xfail(reason="Last ganache-cli version broke the test")
4✔
1040
    def test_send_unsigned_transaction_with_private_key(self):
4✔
1041
        account = self.create_and_fund_account(initial_ether=0.1)
4✔
1042
        key = account.key
4✔
1043
        to = Account.create().address
4✔
1044
        value = 4
4✔
1045

1046
        tx = {
4✔
1047
            "to": to,
1048
            "value": value,
1049
            "gas": 23000,
1050
            "gasPrice": self.gas_price,
1051
        }
1052

1053
        self.ethereum_client.send_unsigned_transaction(tx, private_key=key)
4✔
1054
        self.assertEqual(self.ethereum_client.get_balance(to), value)
4✔
1055
        first_nonce = tx["nonce"]
4✔
1056
        self.assertGreaterEqual(first_nonce, 0)
4✔
1057

1058
        # Will use the same nonce
1059
        with self.assertRaisesMessage(InvalidNonce, "correct nonce"):
4✔
1060
            self.ethereum_client.send_unsigned_transaction(tx, private_key=key)
4✔
1061

1062
        # With retry, everything should work
1063
        self.ethereum_client.send_unsigned_transaction(tx, private_key=key, retry=True)
×
1064
        self.assertEqual(tx["nonce"], first_nonce + 1)
×
1065
        self.assertEqual(self.ethereum_client.get_balance(to), value * 2)
×
1066

1067
        # We try again with the first nonce, and should work too
1068
        tx["nonce"] = first_nonce
×
1069
        self.ethereum_client.send_unsigned_transaction(tx, private_key=key, retry=True)
×
1070
        self.assertEqual(tx["nonce"], first_nonce + 2)
×
1071
        self.assertEqual(self.ethereum_client.get_balance(to), value * 3)
×
1072

1073
    def test_wait_for_tx_receipt(self):
4✔
1074
        value = 1
4✔
1075
        to = Account.create().address
4✔
1076

1077
        tx_hash = self.ethereum_client.send_eth_to(
4✔
1078
            self.ethereum_test_account.key, to=to, gas_price=self.gas_price, value=value
1079
        )
1080
        receipt1 = self.ethereum_client.get_transaction_receipt(tx_hash, timeout=None)
4✔
1081
        receipt2 = self.ethereum_client.get_transaction_receipt(tx_hash, timeout=20)
4✔
1082
        self.assertIsNotNone(receipt1)
4✔
1083
        self.assertEqual(receipt1, receipt2)
4✔
1084

1085
        fake_tx_hash = self.w3.keccak(0)
4✔
1086
        self.assertIsNone(
4✔
1087
            self.ethereum_client.get_transaction_receipt(fake_tx_hash, timeout=None)
1088
        )
1089
        self.assertIsNone(
4✔
1090
            self.ethereum_client.get_transaction_receipt(fake_tx_hash, timeout=1)
1091
        )
1092

1093
    def test_batch_call(self):
4✔
1094
        account_address = Account.create().address
4✔
1095
        tokens_value = 12
4✔
1096
        tokens_value_2 = 25
4✔
1097
        erc20 = self.deploy_example_erc20(tokens_value, account_address)
4✔
1098
        erc20_2 = self.deploy_example_erc20(tokens_value_2, account_address)
4✔
1099

1100
        for batch_call_manager in (
4✔
1101
            self.ethereum_client,
1102
            self.ethereum_client.batch_call_manager,
1103
        ):
1104
            with self.subTest(batch_call_manager=batch_call_manager):
4✔
1105
                results = batch_call_manager.batch_call(
4✔
1106
                    [
1107
                        erc20.functions.decimals(),
1108
                        erc20.functions.symbol(),
1109
                        erc20.functions.balanceOf(account_address),
1110
                    ]
1111
                )
1112
                decimals, symbol, balance_of = results
4✔
1113
                self.assertEqual(decimals, erc20.functions.decimals().call())
4✔
1114
                self.assertEqual(symbol, erc20.functions.symbol().call())
4✔
1115
                self.assertEqual(balance_of, tokens_value)
4✔
1116

1117
                results = batch_call_manager.batch_call_same_function(
4✔
1118
                    erc20.functions.balanceOf(account_address),
1119
                    [erc20.address, erc20_2.address],
1120
                )
1121
                self.assertEqual([tokens_value, tokens_value_2], results)
4✔
1122

1123
                invalid_erc20 = get_erc20_contract(
4✔
1124
                    self.ethereum_client.w3, Account.create().address
1125
                )
1126
                with self.assertRaises(BatchCallException):
4✔
1127
                    batch_call_manager.batch_call(
4✔
1128
                        [
1129
                            invalid_erc20.functions.decimals(),
1130
                            invalid_erc20.functions.symbol(),
1131
                            erc20.functions.balanceOf(account_address),
1132
                            invalid_erc20.functions.balanceOf(account_address),
1133
                        ]
1134
                    )
1135

1136
                # It shouldn't raise error and instead return None
1137
                self.assertEqual(
4✔
1138
                    batch_call_manager.batch_call(
1139
                        [
1140
                            invalid_erc20.functions.decimals(),
1141
                            invalid_erc20.functions.symbol(),
1142
                            erc20.functions.balanceOf(account_address),
1143
                            invalid_erc20.functions.balanceOf(account_address),
1144
                        ],
1145
                        raise_exception=False,
1146
                    ),
1147
                    [None, None, tokens_value, None],
1148
                )
1149

1150
                with self.assertRaises(BatchCallException):
4✔
1151
                    batch_call_manager.batch_call(
4✔
1152
                        [
1153
                            erc20.functions.decimals(),
1154
                            erc20.functions.transfer(NULL_ADDRESS, 1),
1155
                            erc20.functions.symbol(),
1156
                            erc20.functions.balanceOf(account_address),
1157
                        ],
1158
                        raise_exception=True,
1159
                    )
1160

1161
    def test_get_blocks(self):
4✔
1162
        self.assertEqual(self.ethereum_client.get_blocks([]), [])
4✔
1163
        # Generate 3 blocks
1164
        to = Account.create().address
4✔
1165
        value = 345
4✔
1166
        for _ in range(3):
4✔
1167
            self.send_ether(to, value)
4✔
1168

1169
        block_numbers = [
4✔
1170
            self.ethereum_client.current_block_number - i for i in range(3)
1171
        ]
1172
        blocks = self.ethereum_client.get_blocks(block_numbers, full_transactions=True)
4✔
1173
        block_hashes = [block["hash"] for block in blocks]
4✔
1174
        block_hashes_hex = [block_hash.hex() for block_hash in block_hashes]
4✔
1175
        for block_number, block in zip(block_numbers, blocks):
4✔
1176
            self.assertEqual(block["number"], block_number)
4✔
1177
            self.assertEqual(len(block["hash"]), 32)
4✔
1178
            self.assertEqual(len(block["parentHash"]), 32)
4✔
1179
            self.assertGreaterEqual(len(block["transactions"]), 0)
4✔
1180

1181
        blocks = self.ethereum_client.get_blocks(block_hashes, full_transactions=True)
4✔
1182
        for block_number, block in zip(block_numbers, blocks):
4✔
1183
            self.assertEqual(block["number"], block_number)
4✔
1184
            self.assertEqual(len(block["hash"]), 32)
4✔
1185
            self.assertEqual(len(block["parentHash"]), 32)
4✔
1186
            self.assertGreaterEqual(len(block["transactions"]), 0)
4✔
1187

1188
        blocks = self.ethereum_client.get_blocks(
4✔
1189
            block_hashes_hex, full_transactions=True
1190
        )
1191
        for block_number, block in zip(block_numbers, blocks):
4✔
1192
            self.assertEqual(block["number"], block_number)
4✔
1193
            self.assertEqual(len(block["hash"]), 32)
4✔
1194
            self.assertEqual(len(block["parentHash"]), 32)
4✔
1195
            self.assertGreaterEqual(len(block["transactions"]), 0)
4✔
1196

1197
    def test_is_contract(self):
4✔
1198
        self.assertFalse(
4✔
1199
            self.ethereum_client.is_contract(self.ethereum_test_account.address)
1200
        )
1201

1202
        erc20 = self.deploy_example_erc20(2, self.ethereum_test_account.address)
4✔
1203
        self.assertTrue(self.ethereum_client.is_contract(erc20.address))
4✔
1204

1205
    def test_is_eip1559_supported(self):
4✔
1206
        self.assertTrue(self.ethereum_client.is_eip1559_supported())
4✔
1207

1208
    def test_set_eip1559_fees(self):
4✔
1209
        with mock.patch.object(
4✔
1210
            EthereumClient, "estimate_fee_eip1559", return_value=(2, 5)
1211
        ):
1212
            tx: TxParams = {"to": Account.create(), "value": 1, "gasPrice": 5}
4✔
1213
            eip_1559_tx = self.ethereum_client.set_eip1559_fees(tx)
4✔
1214
            self.assertIn("gasPrice", tx)  # Provided tx is not modified
4✔
1215
            self.assertNotIn("gasPrice", eip_1559_tx)
4✔
1216
            self.assertEqual(
4✔
1217
                eip_1559_tx["chainId"], self.ethereum_client.get_network().value
1218
            )
1219
            self.assertEqual(eip_1559_tx["maxPriorityFeePerGas"], 5)
4✔
1220
            self.assertEqual(eip_1559_tx["maxFeePerGas"], 7)
4✔
1221

1222

1223
class TestEthereumClientWithMainnetNode(EthereumTestCaseMixin, TestCase):
4✔
1224
    @classmethod
4✔
1225
    def setUpClass(cls) -> None:
4✔
1226
        super().setUpClass()
4✔
1227
        mainnet_node = just_test_if_mainnet_node()
4✔
1228
        cls.ethereum_client = EthereumClient(URI(mainnet_node))
4✔
1229

1230
    def test_is_eip1559_supported(self):
4✔
1231
        self.assertTrue(self.ethereum_client.is_eip1559_supported())
4✔
1232

1233
    def test_estimate_fee_eip1559(self):
4✔
1234
        """
1235
        EIP1559 is still not supported on Ganache
1236
        """
1237
        (
4✔
1238
            base_fee_per_gas,
1239
            max_priority_fee_per_gas,
1240
        ) = self.ethereum_client.estimate_fee_eip1559()
1241
        self.assertGreater(base_fee_per_gas, 0)
4✔
1242
        self.assertGreaterEqual(max_priority_fee_per_gas, 0)
4✔
1243

1244
    def test_send_unsigned_transaction(self):
4✔
1245
        random_address = Account.create().address
4✔
1246
        random_sender_account = Account.create()
4✔
1247
        tx = {
4✔
1248
            "to": random_address,
1249
            "value": 0,
1250
            "data": b"",
1251
            "gas": 25000,
1252
            "gasPrice": self.ethereum_client.w3.eth.gas_price,
1253
        }
1254
        with self.assertRaises(ChainIdIsRequired):
4✔
1255
            self.ethereum_client.send_unsigned_transaction(
4✔
1256
                tx, private_key=random_sender_account.key
1257
            )
1258

1259
        tx["chainId"] = 1
4✔
1260
        with self.assertRaises(InsufficientFunds):
4✔
1261
            self.ethereum_client.send_unsigned_transaction(
4✔
1262
                tx, private_key=random_sender_account.key
1263
            )
1264

1265
    def test_trace_block(self):
4✔
1266
        block_numbers = [13191781, 2191709, 15630274]
4✔
1267
        block_mocks = [
4✔
1268
            trace_block_13191781_mock,
1269
            trace_block_2191709_mock,
1270
            trace_block_15630274_mock,
1271
        ]
1272
        for block_number, trace_block_mock in zip(block_numbers, block_mocks):
4✔
1273
            with self.subTest(block_number=block_number):
4✔
1274
                self.assertEqual(
4✔
1275
                    self.ethereum_client.tracing.trace_block(block_number),
1276
                    trace_block_mock,
1277
                )
1278

1279
    def test_trace_blocks(self):
4✔
1280
        block_numbers = [13191781, 2191709, 15630274]
4✔
1281
        block_mocks = [
4✔
1282
            trace_block_13191781_mock,
1283
            trace_block_2191709_mock,
1284
            trace_block_15630274_mock,
1285
        ]
1286
        self.assertEqual(
4✔
1287
            self.ethereum_client.tracing.trace_blocks(block_numbers),
1288
            block_mocks,
1289
        )
1290

1291
    def test_trace_transaction(self):
4✔
1292
        for tx_hash in [
4✔
1293
            # Safe 1.3.0 deployment
1294
            "0x0b04589bdc11585fb98f270b1bfeff0fb3bbb3c56d35b104f62d8115d6f7c57f",
1295
            # Erigon v2.31.0 traceAddress issue https://github.com/ledgerwatch/erigon/issues/6375
1296
            "0xc27273dc6e631d275baa527e1b07cd9097887317c26034bf8ea7bbe38c9353f0",
1297
        ]:
1298
            with self.subTest(tx_hash=tx_hash):
4✔
1299
                self.assertEqual(
4✔
1300
                    self.ethereum_client.tracing.trace_transaction(tx_hash),
1301
                    trace_transaction_mocks[tx_hash],
1302
                )
1303

1304
    def test_trace_transactions(self):
4✔
1305
        tx_hashes = [
4✔
1306
            "0x0b04589bdc11585fb98f270b1bfeff0fb3bbb3c56d35b104f62d8115d6f7c57f",  # Safe 1.3.0 deployment
1307
            "0xf325b4e52d0649593e8c82f35bd389c13c13b21b61bc17de295979a21e5cfdc0",  # Safe 1.1.0 setup
1308
            # Erigon v2.31.0 traceAddress issue https://github.com/ledgerwatch/erigon/issues/6375
1309
            "0xc27273dc6e631d275baa527e1b07cd9097887317c26034bf8ea7bbe38c9353f0",
1310
        ]
1311
        self.assertEqual(
4✔
1312
            self.ethereum_client.tracing.trace_transactions(tx_hashes),
1313
            [trace_transaction_mocks[tx_hash] for tx_hash in tx_hashes],
1314
        )
1315

1316
    def test_trace_filter(self):
4✔
1317
        safe_1_3_0_address = "0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552"
4✔
1318
        self.assertListEqual(
4✔
1319
            self.ethereum_client.tracing.trace_filter(
1320
                from_block=12504268, to_block=12504268, to_address=[safe_1_3_0_address]
1321
            ),
1322
            trace_filter_mock_1,
1323
        )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc