Skip to content

Commit e41f635

Browse files
committed
Merge remote-tracking branch 'origin/main' into fix/local-default-provider-config
2 parents fd14c94 + 374bc63 commit e41f635

9 files changed

Lines changed: 371 additions & 28 deletions

File tree

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- accounts
1010
- compile
1111
- contracts
12+
- brownie-migration
1213
- testing
1314
- reverts
1415
- networks
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
# Brownie to Ape Migration
2+
3+
[Brownie is no longer actively maintained. The Brownie README directs Python Ethereum developers to Ape Framework.](https://github.com/eth-brownie/brownie#readme) This guide documents a practical migration path for Brownie projects moving to Ape and references [ApeWorX/ape issue #640](https://github.com/ApeWorX/ape/issues/640), which originally tracked Brownie project migration support.
4+
5+
## Automated Migration with ApeShift
6+
7+
Run ApeShift to apply deterministic Brownie-to-Ape rewrites, validation, reports, and manual-review TODOs.
8+
9+
```bash
10+
npx apeshift migrate .
11+
```
12+
13+
ApeShift is published in the Codemod registry: https://app.codemod.com/registry/apeshift
14+
15+
> **Note on tooling:** ApeShift is distributed through the Codemod registry, which uses npm packaging for codemod distribution.
16+
> The migration itself operates on Python files using ast-grep rules that parse and transform Python AST directly.
17+
> No JavaScript runtime is involved in the user's Python project code.
18+
> A Python-native wrapper may be added in the future.
19+
20+
## Imports
21+
22+
Brownie scripts often import accounts, contracts, config, and networks from `brownie`. Migrated Ape scripts import public Ape APIs and access contracts through `project`.
23+
24+
```python
25+
# Before
26+
from brownie import accounts, config, SimpleStorage, network
27+
28+
# After
29+
from ape import accounts, config, networks, project
30+
```
31+
32+
Official docs: [Contracts](./contracts.html)
33+
34+
## Accounts
35+
36+
Use `accounts.test_accounts` for local generated accounts and `accounts.load()` for named account aliases. The alias must be imported by the user before live-network use.
37+
38+
```python
39+
# Before
40+
if network.show_active() == "development":
41+
return accounts[0]
42+
return accounts.add(config["wallets"]["from_key"])
43+
44+
# After
45+
if networks.provider.network.name == "development":
46+
return accounts.test_accounts[0]
47+
return accounts.add(config["wallets"]["from_key"]) # TODO(apeshift): accounts.add(key) not valid in Ape; use accounts.load("account-name") after: ape accounts import <name>
48+
```
49+
50+
Official docs: [Accounts](./accounts.html)
51+
52+
## Contract Deployment and Transactions
53+
54+
Brownie transaction dictionaries become explicit Ape keyword arguments such as `sender=` and `value=`.
55+
56+
```python
57+
# Before
58+
simple_storage = SimpleStorage.deploy({"from": account})
59+
transaction = simple_storage.store(15, {"from": account})
60+
61+
# After
62+
simple_storage = project.SimpleStorage.deploy(sender=account)
63+
transaction = simple_storage.store(15, sender=account)
64+
```
65+
66+
For payable calls:
67+
68+
```python
69+
# Before
70+
tx = fund_me.fund({"from": account, "value": entrance_fee})
71+
72+
# After
73+
tx = fund_me.fund(sender=account, value=entrance_fee)
74+
```
75+
76+
Official docs: [Contracts](./contracts.html)
77+
78+
## Networks
79+
80+
Brownie's active-network helper maps to Ape's active provider network metadata.
81+
82+
```python
83+
# Before
84+
print(f"The active network is {network.show_active()}")
85+
86+
# After
87+
print(f"The active network is {networks.provider.network.name}")
88+
```
89+
90+
Official docs: [Networks](./networks.html)
91+
92+
## Testing and Reverts
93+
94+
Brownie revert helpers and exceptions map to Ape testing helpers and exceptions.
95+
96+
```python
97+
# Before
98+
with brownie.reverts("Ownable: caller is not the owner"):
99+
fund_me.withdraw({"from": bad_actor})
100+
101+
# After
102+
with ape.reverts("Ownable: caller is not the owner"):
103+
fund_me.withdraw(sender=bad_actor)
104+
```
105+
106+
```python
107+
# Before
108+
with pytest.raises(exceptions.VirtualMachineError):
109+
fund_me.withdraw({"from": bad_actor})
110+
111+
# After
112+
from ape.exceptions import ContractLogicError
113+
114+
with pytest.raises(ContractLogicError):
115+
fund_me.withdraw(sender=bad_actor)
116+
```
117+
118+
Official docs: [Testing](./testing.html)
119+
120+
## Testing: pytest Fixtures
121+
122+
Ape tests use pytest fixtures from the `ape-test` plugin.
123+
In Ape, contract types are not injected as pytest fixtures.
124+
Use the `project` fixture and access contracts as `project.ContractName`.
125+
126+
Remove Brownie's `fn_isolation` fixture when migrating tests:
127+
128+
```python
129+
# Before
130+
@pytest.fixture(autouse=True)
131+
def isolate(fn_isolation):
132+
pass
133+
134+
# After
135+
# Remove entirely — Ape handles test isolation through its pytest plugin.
136+
```
137+
138+
Use Ape's `accounts` fixture with `project` for deployments:
139+
140+
```python
141+
# Before
142+
def test_deploy(Token, accounts):
143+
account = accounts[0]
144+
token = Token.deploy({"from": account})
145+
146+
# After
147+
def test_deploy(project, accounts):
148+
account = accounts[0]
149+
token = project.Token.deploy(sender=account)
150+
```
151+
152+
Update contract fixture patterns the same way:
153+
154+
```python
155+
# Before
156+
@pytest.fixture
157+
def token(Token, accounts):
158+
return Token.deploy({"from": accounts[0]})
159+
160+
# After
161+
@pytest.fixture
162+
def token(project, accounts):
163+
return project.Token.deploy(sender=accounts[0])
164+
```
165+
166+
If tests manually use chain snapshots, `chain.snapshot()` remains similar, but Brownie's `chain.revert()` should become Ape's `chain.restore()`.
167+
168+
```python
169+
# Before
170+
chain.snapshot()
171+
chain.revert()
172+
173+
# After
174+
chain.snapshot()
175+
chain.restore()
176+
```
177+
178+
Brownie provides a `web3` pytest fixture for direct Web3.py access in tests.
179+
Ape does not provide a `web3` fixture.
180+
181+
If direct Web3 access is needed, use the active provider:
182+
183+
```python
184+
# Before
185+
def test_block_number(web3):
186+
assert web3.eth.block_number >= 0
187+
188+
# After
189+
from ape import networks
190+
191+
def test_block_number():
192+
assert networks.provider.web3.eth.block_number >= 0
193+
```
194+
195+
Official docs: [Testing](./testing.html)
196+
197+
## Config Files
198+
199+
Brownie configuration values should move into Ape's `ape-config.yaml` structure. Wallet private keys are not copied into config; import an Ape account alias instead.
200+
201+
```yaml
202+
# Before: brownie-config.yaml
203+
dependencies:
204+
- smartcontractkit/chainlink-brownie-contracts@1.1.1
205+
compiler:
206+
solc:
207+
remappings:
208+
- "@chainlink=smartcontractkit/chainlink-brownie-contracts@1.1.1"
209+
wallets:
210+
from_key: ${PRIVATE_KEY}
211+
networks:
212+
development:
213+
verify: false
214+
```
215+
216+
```yaml
217+
# After: ape-config.yaml
218+
name: migrated-ape-project
219+
plugins:
220+
- name: solidity
221+
solidity:
222+
version: 0.8.20
223+
import_remapping:
224+
- "@chainlink=smartcontractkit/chainlink-brownie-contracts@1.1.1"
225+
ethereum:
226+
default_network: local
227+
# Import keys with `ape accounts import <alias>` and use `accounts.load("<alias>")`.
228+
networks:
229+
development:
230+
verify: false
231+
```
232+
233+
Official docs: [Config](./config.html)
234+
235+
## Real-World Results
236+
237+
| Repo | Files | Patterns Before | Patterns After | Auto% | FP | FN | Syntax OK | Runtime Safe | Classification |
238+
|------|-------|----------------|----------------|-------|----|----|-----------|--------------|----------------|
239+
| brownie_simple_storage | 4 | 12 | 1 | 92% | 0 | 0 | ✅ | ✅ | PASS |
240+
| brownie_fund_me | 7 | 23 | 1 | 96% | 0 | 1 | ✅ | ✅ | DEPENDENCY_SOURCE_LAYOUT_BLOCKED |
241+
| chainlink-mix | 21 | 104 | 4 | 96% | 0 | 7 | ✅ | ✅ | DEPENDENCY_SOURCE_LAYOUT_BLOCKED |
242+
| brownie-nft-course | 18 | 76 | 11 | 86% | 0 | 3 | ✅ | ✅ | DEPENDENCY_SOURCE_LAYOUT_BLOCKED |
243+
| token-mix | 6 | 64 | 2 | 97% | 0 | 0 | ✅ | ✅ | PROJECT_TEST_SETUP_REVIEW |
244+
| **Combined** | 56 | 279 | 19 | 93% | **0** | 11 | ✅ | ✅ | |
245+
246+
Real Ape runtime validation was also run locally with Ape 0.8.48:
247+
248+
| Repo | Ape Compile | Ape Test | Notes |
249+
|------|-------------|----------|-------|
250+
| brownie_simple_storage | ✅ PASS | ✅ 2 passed | Fully validated |
251+
| brownie_fund_me | ❌ FAIL | ❌ 2 failed | Chainlink dependency source layout unresolved |
252+
| chainlink-mix | ❌ FAIL | ❌ collection error | Chainlink dependency source layout and import-time provider access unresolved |
253+
| brownie-nft-course | ❌ FAIL | ❌ collection error | Chainlink/OpenZeppelin dependency source layout unresolved |
254+
| token-mix | ✅ PASS | ❌ collection error | Brownie `fn_isolation` fixture requires Ape pytest isolation fixture migration |
255+
256+
## What Remains Manual
257+
258+
1. `web3.eth.contract(...)` is deterministically rewritten to `Contract(address, abi=...)` with a TODO if the ABI source is unclear.
259+
2. `accounts.load()` aliases require a human to choose and import the account name.
260+
3. Complex event filters receive TODO comments with exact guidance.
261+
4. `from brownie.network import priority_fee` receives a TODO because Ape has no safe deterministic equivalent.

0 commit comments

Comments
 (0)