Skip to content

Commit

Permalink
feat: trace api
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey committed Jan 31, 2024
1 parent 80df7a4 commit a7cf1fa
Show file tree
Hide file tree
Showing 37 changed files with 1,612 additions and 1,283 deletions.
25 changes: 13 additions & 12 deletions docs/userguides/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,8 @@ Similar to `pytest.raises()`, you can use `ape.reverts()` to assert that contrac
From our earlier example we can see this in action:

```python
import ape

def test_authorization(my_contract, owner, not_owner):
my_contract.set_owner(sender=owner)
assert owner == my_contract.owner()
Expand All @@ -297,6 +299,9 @@ If the message in the `ContractLogicError` raised by the transaction failure is
You may also supply an `re.Pattern` object to assert on a message pattern, rather than on an exact match.

```python
import ape
import re

# Matches explicitly "foo" or "bar"
with ape.reverts(re.compile(r"^(foo|bar)$")):
...
Expand Down Expand Up @@ -327,6 +332,8 @@ def check_value(_value: uint256) -> bool:
We can explicitly cause a transaction revert and check the failed line by supplying an expected `dev_message`:

```python
import ape

def test_authorization(my_contract, owner):
with ape.reverts(dev_message="dev: invalid value"):
my_contract.check_value(sender=owner)
Expand All @@ -345,6 +352,8 @@ Because `dev_message` relies on transaction tracing to function, you must use a
You may also supply an `re.Pattern` object to assert on a dev message pattern, rather than on an exact match.

```python
import ape

# Matches explictly "dev: foo" or "dev: bar"
with ape.reverts(dev_message=re.compile(r"^dev: (foo|bar)$")):
...
Expand Down Expand Up @@ -480,12 +489,10 @@ To run an entire test using a specific network / provider combination, use the `
```python
import pytest


@pytest.mark.use_network("fantom:local:test")
def test_my_fantom_test(chain):
assert chain.provider.network.ecosystem.name == "fantom"


@pytest.mark.use_network("ethereum:local:test")
def test_my_ethereum_test(chain):
assert chain.provider.network.ecosystem.name == "ethereum"
Expand Down Expand Up @@ -513,13 +520,11 @@ This is useful if certain fixtures must run in certain networks.
```python
import pytest


@pytest.fixture
def stark_contract(networks, project):
with networks.parse_network_choice("starknet:local"):
yield project.MyStarknetContract.deploy()


def test_starknet_thing(stark_contract, stark_account):
# Uses the starknet connection via the stark_contract fixture
receipt = stark_contract.my_method(sender=stark_account)
Expand All @@ -534,10 +539,11 @@ Thus, you can enter and exit a provider's context as much as you need in tests.
## Gas Reporting

To include a gas report at the end of your tests, you can use the `--gas` flag.
**NOTE**: This feature requires using a provider with tracing support, such as [ape-hardhat](https://github.com/ApeWorX/ape-hardhat).
**NOTE**: This feature works best when using a provider with tracing support, such as [ape-foundry](https://github.com/ApeWorX/ape-foundry).
When not using a provider with adequate tracing support, such as `EthTester`, gas reporting is limited to receipt-level data.

```bash
ape test --network ethereum:local:hardhat --gas
ape test --network ethereum:local:foundry --gas
```

At the end of test suite, you will see tables such as:
Expand All @@ -552,12 +558,6 @@ At the end of test suite, you will see tables such as:
changeOnStatus 2 23827 45739 34783 34783
getSecret 1 24564 24564 24564 24564

Transferring ETH Gas

Method Times called Min. Max. Mean Median
───────────────────────────────────────────────────────
to:test0 2 2400 9100 5750 5750

TestContract Gas

Method Times called Min. Max. Mean Median
Expand Down Expand Up @@ -618,6 +618,7 @@ ape test --coverage
```

**NOTE**: Some types of coverage require using a provider that supports transaction tracing, such as `ape-hardhat` or `ape-foundry`.
Without using a provider with adequate tracing support, coverage is limited to receipt-level data.

Afterwards, you should see a coverage report looking something like:

Expand Down
114 changes: 114 additions & 0 deletions docs/userguides/trace.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Traces

A transaction's trace frames are the individual steps the transaction took.
Using traces, Ape is able to offer features like:

1. Showing a pretty call-tree from a transaction receipt
2. Gas reporting in `ape test`
3. Coverage tools in `ape test`

Some network providers, such as Alchemy and Foundry, implement `debug_traceTransaction` and Parity's `trace_transaction` affording tracing capabilities in Ape.
**WARN**: Without RPCs for obtaining traces, some features such as gas-reporting and coverage are limited.

To see a transaction trace, use the [show_trace()](../methoddocs/api.html#ape.api.transactions.ReceiptAPI.show_trace) method on a receipt API object.

Here is an example using `show_trace()` in Python code to print out a transaction's trace.
**NOTE**: This code runs assuming you are connected to `ethereum:mainnet` using a provider with tracing RPCs.
To learn more about networks in Ape, see the [networks guide](./networks.html).

```python
from ape import chain

tx = chain.provider.get_receipt('0xb7d7f1d5ce7743e821d3026647df486f517946ef1342a1ae93c96e4a8016eab7')

# Show the steps the transaction took.
tx.show_trace()
```

You should see a (less-abridged) trace like:

```
Call trace for '0xb7d7f1d5ce7743e821d3026647df486f517946ef1342a1ae93c96e4a8016eab7'
tx.origin=0x5668EAd1eDB8E2a4d724C8fb9cB5fFEabEB422dc
DSProxy.execute(_target=LoanShifterTaker, _data=0x35..0000) -> "" [1421947 gas]
└── (delegate) LoanShifterTaker.moveLoan(
_exchangeData=[
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE,
ZERO_ADDRESS,
...
# Abridged because is super long #
...
│ └── LendingRateOracle.getMarketBorrowRate(_asset=DAI) ->
│ 35000000000000000000000000 [1164 gas]
├── DSProxy.authority() -> DSGuard [1291 gas]
├── DSGuard.forbid(src=LoanShifterReceiver, dst=DSProxy, sig=0x1c..0000) [5253 gas]
└── DefisaverLogger.Log(
_contract=DSProxy,
_caller=tx.origin,
_logName="LoanShifter",
_data=0x00..0000
) [6057 gas]
```

Similarly, you can use the provider directly to get a trace.
This is useful if you want to interact with the trace or change some parameters for creating the trace.

```python
from ape import chain

# Change the `debug_traceTransaction` parameter dictionary
trace = chain.provider.get_transaction_trace(
"0x...", debug_trace_transaction_parameters={"enableMemory": False}
)

# You can still print the pretty call-trace (as we did in the example above)
print(trace)

# Interact with low-level logs for deeper analysis.
struct_logs = trace.get_raw_frames()
```

## Tracing Calls

Some network providers trace calls in addition to transactions.
EVM-based providers best achieve this by implementing the `debug_traceCall` RPC.

If you want to see the trace of call when making the call, use the `show_trace=` flag:

```python
token.balanceOf(account, show_trace=True)
```

**WARN**: If your provider does not properly support call-tracing (e.g. doesn't implement `debug_traceCall`), traces are limited to the top-level call.

Ape traces calls automatically when using `--gas` or `--coverage` in tests to build reports.
Learn more about testing in Ape in the [testing guide](./testing.html) and in the following sections.

## Gas Reports

To view the gas report of a transaction receipt, use the [ReceiptAPI.show_gas_report()](../methoddocs/api.html?highlight=receiptapi#ape.api.transactions.ReceiptAPI.show_gas_report) method:

```python
from ape import networks

txn_hash = "0x053cba5c12172654d894f66d5670bab6215517a94189a9ffc09bc40a589ec04d"
receipt = networks.provider.get_receipt(txn_hash)
receipt.show_gas_report()
```

It outputs tables of contracts and methods with gas usages that look like this:

```
DAI Gas
Method Times called Min. Max. Mean Median
────────────────────────────────────────────────────────────────
balanceOf 4 1302 13028 1302 1302
allowance 2 1377 1377 1337 1337
│ approve 1 22414 22414 22414 22414
│ burn 1 11946 11946 11946 11946
│ mint 1 25845 25845 25845 25845
```
Loading

0 comments on commit a7cf1fa

Please sign in to comment.