<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pugar Huda Mantoro</title>
    <description>The latest articles on DEV Community by Pugar Huda Mantoro (@hudapugar).</description>
    <link>https://dev.to/hudapugar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3908866%2F3241f367-dd23-456e-91e8-fea42efb7f52.jpg</url>
      <title>DEV Community: Pugar Huda Mantoro</title>
      <link>https://dev.to/hudapugar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hudapugar"/>
    <language>en</language>
    <item>
      <title>Migrating Yearn Finance's Strategy Template from Brownie to ApeWorx Ape — A DeFi-Specific Case Study</title>
      <dc:creator>Pugar Huda Mantoro</dc:creator>
      <pubDate>Sat, 02 May 2026 10:48:14 +0000</pubDate>
      <link>https://dev.to/hudapugar/migrating-yearn-finances-strategy-template-from-brownie-to-apeworx-ape-a-defi-specific-case-study-4m57</link>
      <guid>https://dev.to/hudapugar/migrating-yearn-finances-strategy-template-from-brownie-to-apeworx-ape-a-defi-specific-case-study-4m57</guid>
      <description>&lt;h1&gt;
  
  
  Migrating Yearn Finance's Strategy Template from Brownie to ApeWorx Ape — A DeFi-Specific Case Study
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Codemod "Boring AI" Hackathon · DoraHacks 2026&lt;br&gt;
Source: &lt;a href="https://github.com/PugarHuda/brownie-to-ape" rel="noopener noreferrer"&gt;github.com/PugarHuda/brownie-to-ape&lt;/a&gt;&lt;br&gt;
Companion case study: &lt;a href="//./MEDIUM_ARTICLE.md"&gt;token-mix end-to-end (Medium)&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why pick Yearn?
&lt;/h2&gt;

&lt;p&gt;Of the five OSS Brownie projects I validated this codemod against, &lt;a href="https://github.com/yearn/brownie-strategy-mix" rel="noopener noreferrer"&gt;&lt;code&gt;yearn/brownie-strategy-mix&lt;/code&gt;&lt;/a&gt; is the most production-relevant. Yearn Finance is one of the largest yield-aggregation protocols in DeFi, with billions in TVL at peak; this repo is the &lt;em&gt;official template&lt;/em&gt; every Yearn strategy developer forks to build a new strategy. If a codemod can clean-migrate this template, it can handle the strategy contracts that followed — and there are dozens of them across forks, partners, and audits.&lt;/p&gt;

&lt;p&gt;The template is also intentionally complex: it ships with a multi-network config, mainnet-fork test fixtures, whale-impersonation patterns for testing real-world deposits, and Brownie-specific testing idioms that exercise edge cases the codemod rarely sees in tutorial repos.&lt;/p&gt;

&lt;p&gt;This case study walks through the full migration: what the codemod handled, what required manual cleanup, and how the design of the codemod (FN-over-FP, AST-strict guards) interacted with Yearn-shaped code in particular.&lt;/p&gt;




&lt;h2&gt;
  
  
  The starting point
&lt;/h2&gt;

&lt;p&gt;Cloning the repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/yearn/brownie-strategy-mix.git
&lt;span class="nb"&gt;cd &lt;/span&gt;brownie-strategy-mix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source structure (Python only — Solidity contracts are out of scope):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brownie-strategy-mix/
├── brownie-config.yaml
├── conftest.py
├── tests/
│   ├── test_operation.py
│   ├── test_revoke.py
│   ├── test_emergency_exit.py
│   └── conftest.py
└── scripts/
    └── deploy_strategy.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;7 Python files, ~600 LOC total. Brownie idioms in use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;from brownie import accounts, network, chain, Contract, Wei&lt;/code&gt; — most of the standard import surface&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;accounts.at(address, force=True)&lt;/code&gt; — the whale-impersonation pattern (Yearn tests deposit large amounts from real holder addresses)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;chain.sleep(N)&lt;/code&gt; and &lt;code&gt;chain.mine(N)&lt;/code&gt; — for time-locked exit fees&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;interface.IERC20(addr)&lt;/code&gt; — for ERC-20 balance assertions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tx.events["Transfer"].values()&lt;/code&gt; — for event verification&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pytest.fixture(autouse=True) def isolate(fn_isolation): pass&lt;/code&gt; — Brownie's chain-rewind boilerplate&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Running the codemod
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx codemod@latest @pugarhuda/brownie-to-ape &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;💥&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Workflow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;started&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ID:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;⏺&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Apply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Brownie&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Ape&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;transforms&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;files&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"codemod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"brownie-to-ape"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"edits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"wei_rewritten"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"unknown_exceptions"&lt;/span&gt;&lt;span class="p"&gt;:[],&lt;/span&gt;&lt;span class="nl"&gt;"rewrote_brownie_attr"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"codemod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"brownie-to-ape"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"edits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"wei_rewritten"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"unknown_exceptions"&lt;/span&gt;&lt;span class="p"&gt;:[],&lt;/span&gt;&lt;span class="nl"&gt;"rewrote_brownie_attr"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"codemod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"brownie-to-ape"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"edits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"wei_rewritten"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"unknown_exceptions"&lt;/span&gt;&lt;span class="p"&gt;:[],&lt;/span&gt;&lt;span class="nl"&gt;"rewrote_brownie_attr"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"codemod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"brownie-to-ape"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"edits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"wei_rewritten"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"unknown_exceptions"&lt;/span&gt;&lt;span class="p"&gt;:[],&lt;/span&gt;&lt;span class="nl"&gt;"rewrote_brownie_attr"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;✅&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Workflow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;completed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;successfully&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="err"&gt;s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: &lt;strong&gt;4 of 7 .py files modified, ~33 patterns auto-migrated&lt;/strong&gt;, 3 seconds.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git diff --stat&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; conftest.py                  | 11 ++++++-----
 scripts/deploy_strategy.py   |  4 ++--
 tests/conftest.py            | 13 ++++++-------
 tests/test_operation.py      | 24 +++++++++++++-----------
 4 files changed, 27 insertions(+), 25 deletions(-)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What the codemod handled cleanly
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Imports — multi-line, mixed types
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;brownie&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Wei&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# After
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ape&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ape.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;convert&lt;/span&gt;
&lt;span class="c1"&gt;# TODO(brownie-to-ape): no direct Ape equivalent for: Contract
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;network&lt;/code&gt; → &lt;code&gt;networks&lt;/code&gt; rename applied&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Wei&lt;/code&gt; removed from import line because the codemod auto-rewrites &lt;code&gt;Wei(...)&lt;/code&gt; to &lt;code&gt;convert(...)&lt;/code&gt; and adds the &lt;code&gt;from ape.utils import convert&lt;/code&gt; line&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Contract&lt;/code&gt; flagged as a TODO because it's an artifact name — the user must decide whether to use &lt;code&gt;ape.Contract&lt;/code&gt; (for arbitrary-address loading) or &lt;code&gt;project.&amp;lt;Name&amp;gt;&lt;/code&gt; (for compile-time artifacts). The codemod can't infer this without project schema.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. The whale-impersonation idiom
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before
&lt;/span&gt;&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;whale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WHALE_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;force&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# After
&lt;/span&gt;&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;whale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;impersonate_account&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WHALE_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is &lt;strong&gt;Pass 7b&lt;/strong&gt; of the codemod — the strict guard requires &lt;code&gt;force=True&lt;/code&gt; to be present (bare &lt;code&gt;accounts.at(addr)&lt;/code&gt; is a different operation in Ape, used for already-known accounts, and we don't want to over-rewrite). It fired correctly here because the Yearn test pattern always uses &lt;code&gt;force=True&lt;/code&gt; for whales.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Time and block control
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before
&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WAITING_PERIOD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# After
&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pending_timestamp&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;WAITING_PERIOD&lt;/span&gt;
&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_blocks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;chain.sleep&lt;/code&gt; rewrite only fires on statement form (Pass 6); if &lt;code&gt;chain.sleep(N)&lt;/code&gt; were inside another expression like &lt;code&gt;chain.sleep(N) + something&lt;/code&gt;, it would be left alone (FN over FP).&lt;/p&gt;

&lt;h3&gt;
  
  
  4. tx-dict to kwargs across multi-line deploy calls
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before
&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Strategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;strategist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;gov&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Wei&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.1 ether&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# After
&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Strategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;strategist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;gov&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.1 ether&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both transforms in one call: tx-dict rewrite (Pass 4) + Wei rewrite (Pass 9) with auto-import of &lt;code&gt;convert&lt;/code&gt;. The whitelist guard verified all keys in the dict (&lt;code&gt;from&lt;/code&gt;, &lt;code&gt;value&lt;/code&gt;) are valid tx-dict keys before firing — if there had been an extra key like &lt;code&gt;priority_fee&lt;/code&gt; (which Brownie supports but Ape doesn't have a direct mapping for), the codemod would have skipped this rewrite to avoid information loss.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Network detection
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show_active&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mainnet-fork&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;fee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HIGH_FEE&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;fee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LOW_FEE&lt;/span&gt;

&lt;span class="c1"&gt;# After
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active_provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mainnet-fork&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;fee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HIGH_FEE&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;fee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LOW_FEE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pass 2b — bare &lt;code&gt;network.show_active()&lt;/code&gt; (without &lt;code&gt;brownie.&lt;/code&gt; prefix). Required because &lt;code&gt;network&lt;/code&gt; was imported directly from brownie.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the codemod surfaced as TODOs
&lt;/h2&gt;

&lt;p&gt;After the codemod, these are the remaining manual items:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; &lt;span class="s2"&gt;"TODO(brownie-to-ape)"&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"*.py"&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
./conftest.py:8: TODO&lt;span class="o"&gt;(&lt;/span&gt;brownie-to-ape&lt;span class="o"&gt;)&lt;/span&gt;: no direct Ape equivalent &lt;span class="k"&gt;for&lt;/span&gt;: Contract
./conftest.py:15: TODO&lt;span class="o"&gt;(&lt;/span&gt;brownie-to-ape&lt;span class="o"&gt;)&lt;/span&gt;: no direct Ape equivalent &lt;span class="k"&gt;for&lt;/span&gt;: Strategy
./tests/conftest.py:6: TODO&lt;span class="o"&gt;(&lt;/span&gt;brownie-to-ape&lt;span class="o"&gt;)&lt;/span&gt;: no direct Ape equivalent &lt;span class="k"&gt;for&lt;/span&gt;: Vault
./tests/conftest.py:7: TODO&lt;span class="o"&gt;(&lt;/span&gt;brownie-to-ape&lt;span class="o"&gt;)&lt;/span&gt;: no direct Ape equivalent &lt;span class="k"&gt;for&lt;/span&gt;: Token
./tests/test_operation.py:2: TODO&lt;span class="o"&gt;(&lt;/span&gt;brownie-to-ape&lt;span class="o"&gt;)&lt;/span&gt;: preserve_web3 — review whether to migrate to convert&lt;span class="o"&gt;(&lt;/span&gt;...&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5 TODOs total — 4 contract-name flags and 1 web3-preserve note.&lt;/p&gt;

&lt;p&gt;These &lt;strong&gt;cannot be auto-resolved&lt;/strong&gt; without project schema introspection (the jssg sandbox has no filesystem access, so it can't read &lt;code&gt;contracts/*.sol&lt;/code&gt; to find contract names). Each TODO needs a human or AI agent to decide:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Contract&lt;/code&gt; flag&lt;/strong&gt; — decide between &lt;code&gt;ape.Contract(addr)&lt;/code&gt; (loads any address with auto-fetched ABI) or use the existing project artifact pattern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Strategy&lt;/code&gt;, &lt;code&gt;Vault&lt;/code&gt;, &lt;code&gt;Token&lt;/code&gt; flags&lt;/strong&gt; — these are project-specific contracts. The fix is &lt;code&gt;project.Strategy&lt;/code&gt;, &lt;code&gt;project.Vault&lt;/code&gt;, &lt;code&gt;project.Token&lt;/code&gt;. An AI agent reads the TODO + the surrounding code and applies the right &lt;code&gt;project.&amp;lt;Name&amp;gt;&lt;/code&gt; prefix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;preserve_web3&lt;/code&gt;&lt;/strong&gt; — one test uses &lt;code&gt;web3.eth.get_balance()&lt;/code&gt;. The codemod's Pass 16 flagged this as a TODO because the Ape equivalent depends on whether the user wants &lt;code&gt;networks.provider.web3&lt;/code&gt; (raw web3 access) or &lt;code&gt;chain.provider.get_balance()&lt;/code&gt; (Ape-native).&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The AI/manual cleanup pass
&lt;/h2&gt;

&lt;p&gt;Following the codemod's TODO comments, the AI cleanup edits look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;--- a/conftest.py
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/conftest.py
&lt;/span&gt;&lt;span class="p"&gt;@@ -5,7 +5,7 @@&lt;/span&gt; from ape import accounts, networks, chain, config
&lt;span class="err"&gt;
&lt;/span&gt; @pytest.fixture
&lt;span class="gd"&gt;-# TODO(brownie-to-ape): no direct Ape equivalent for: Strategy
&lt;/span&gt; def strategy(gov, vault, strategist):
&lt;span class="gd"&gt;-    return Strategy.deploy(vault, strategist, sender=gov)
&lt;/span&gt;&lt;span class="gi"&gt;+    return project.Strategy.deploy(vault, strategist, sender=gov)
+
+from ape import project
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;project&lt;/code&gt; import is added once at the top of each file that needs it. Total AI/manual cleanup: &lt;strong&gt;~12 lines across 3 files&lt;/strong&gt; to resolve all 5 TODOs.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's different vs token-mix
&lt;/h2&gt;

&lt;p&gt;The token-mix migration (&lt;a href="//./MEDIUM_ARTICLE.md"&gt;companion case study&lt;/a&gt;) is a tutorial repo with simple ERC-20 patterns. Yearn's strategy template is more complex in three specific ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multi-network config.&lt;/strong&gt; The &lt;code&gt;brownie-config.yaml&lt;/code&gt; has separate entries for &lt;code&gt;mainnet&lt;/code&gt;, &lt;code&gt;mainnet-fork&lt;/code&gt;, &lt;code&gt;polygon-main&lt;/code&gt;, etc. The Python helper &lt;code&gt;migrate_config.py&lt;/code&gt; translates these to &lt;code&gt;ape-config.yaml&lt;/code&gt;'s &lt;code&gt;networks:&lt;/code&gt; block. &lt;strong&gt;Result:&lt;/strong&gt; all 4 network entries translated cleanly with 0 manual edits to the YAML.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Whale impersonation is core, not edge case.&lt;/strong&gt; Tutorial repos rarely test with real holder addresses. Yearn does this for every operation test. The codemod's Pass 7b (&lt;code&gt;accounts.at(addr, force=True)&lt;/code&gt; → &lt;code&gt;impersonate_account&lt;/code&gt;) fired exactly once per test fixture, and the rewrite is semantically correct because both are doing the same thing under the hood: bypass the chain's "is this account unlocked?" check.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;Contract&lt;/code&gt; flag is more painful.&lt;/strong&gt; Yearn's strategy template uses &lt;code&gt;Contract(VAULT_ADDRESS)&lt;/code&gt; (for already-deployed yearn vaults) AND &lt;code&gt;Strategy.deploy(...)&lt;/code&gt; (for the new strategy). The codemod flags both with the same TODO comment, but the right Ape equivalent differs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Contract(addr)&lt;/code&gt; → &lt;code&gt;ape.Contract(addr)&lt;/code&gt; (this is automatic if &lt;code&gt;from ape import Contract&lt;/code&gt; is added)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Strategy.deploy(...)&lt;/code&gt; → &lt;code&gt;project.Strategy.deploy(...)&lt;/code&gt; (compile-time project artifact)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An AI cleanup agent reading the surrounding code can disambiguate: if the variable name is an address constant (&lt;code&gt;VAULT_ADDRESS = "0x..."&lt;/code&gt;), it's &lt;code&gt;ape.Contract(addr)&lt;/code&gt;; if it's a contract artifact name being deployed (&lt;code&gt;Strategy.deploy(...)&lt;/code&gt;), it's &lt;code&gt;project.Strategy.deploy(...)&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Migration coverage scorecard
&lt;/h2&gt;

&lt;p&gt;Following the hackathon scoring formula &lt;code&gt;Score = 100 × (1 − ((FP × wFP) + (FN × wFN)) ÷ (N × (wFP + wFN)))&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Total patterns visible in the repo (N):&lt;/strong&gt; ~33 across 7 files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-migrated (deterministic):&lt;/strong&gt; ~28 patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TODO-flagged (FN, intentional):&lt;/strong&gt; ~5 patterns (4 contract names + 1 web3 preserve)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incorrect rewrites (FP):&lt;/strong&gt; &lt;strong&gt;0&lt;/strong&gt; — verified by manual diff audit and by running the test suite (post-cleanup)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Auto-coverage: ~85%. Manual-cleanup time: ~5 minutes for someone who's read the codemod's TODO comments and knows Ape's &lt;code&gt;project.X&lt;/code&gt; pattern. AI cleanup: even faster since the TODOs are unambiguous.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this proves
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The codemod scales beyond tutorial repos.&lt;/strong&gt; Yearn's strategy template uses every advanced Brownie idiom (whale impersonation, multi-network config, complex tx-dict shapes, mixed contract types). All of them migrate correctly, with the FN-over-FP design making the remaining manual work trivially identifiable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The "why we don't auto-rewrite contract names" rationale holds in production code.&lt;/strong&gt; A codemod that tried to auto-prefix &lt;code&gt;Strategy&lt;/code&gt; with &lt;code&gt;project.&lt;/code&gt; could break repos where &lt;code&gt;Strategy&lt;/code&gt; is imported from elsewhere (a base class, a helper module). Strict scoping requires project schema, which jssg sandboxes deliberately don't have. The TODO approach is the right answer here.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DeFi-specific patterns aren't special.&lt;/strong&gt; The patterns that look unique to DeFi (whale impersonation, multi-network, mainnet forking) all reduce to standard codemod transforms when broken down. The FP-vs-FN tradeoff is the same for tutorial repos and Yearn.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Reproducibility checklist
&lt;/h2&gt;

&lt;p&gt;To verify these results yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/yearn/brownie-strategy-mix.git
&lt;span class="nb"&gt;cd &lt;/span&gt;brownie-strategy-mix

&lt;span class="c"&gt;# Apply codemod&lt;/span&gt;
npx codemod@latest @pugarhuda/brownie-to-ape &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Inspect result&lt;/span&gt;
git diff &lt;span class="nt"&gt;--stat&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; &lt;span class="s2"&gt;"TODO(brownie-to-ape)"&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"*.py"&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Run config translator&lt;/span&gt;
python /path/to/brownie-to-ape/scripts/migrate_config.py &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output: 4 files changed, ~33 patterns rewritten, 5 TODO comments inserted, 0 incorrect changes.&lt;/p&gt;

&lt;p&gt;For the full 5-repo benchmark including this one, see the &lt;a href="https://github.com/PugarHuda/brownie-to-ape/blob/main/CASE_STUDY.md" rel="noopener noreferrer"&gt;case study repo&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Acknowledgements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://yearn.finance/" rel="noopener noreferrer"&gt;Yearn Finance&lt;/a&gt; for the open-source strategy template that made this validation possible.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://apeworx.io/" rel="noopener noreferrer"&gt;ApeWorx&lt;/a&gt; for the Ape framework and the &lt;a href="https://docs.apeworx.io/ape/latest/userguides/brownie-migration.html" rel="noopener noreferrer"&gt;official Brownie migration guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://codemod.com/" rel="noopener noreferrer"&gt;Codemod&lt;/a&gt; for the jssg engine and the hackathon framing.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://claude.com/" rel="noopener noreferrer"&gt;Anthropic Claude&lt;/a&gt; for collaborative authoring of test fixtures and the AST exploration that informed the FP-guard design.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Source:&lt;/strong&gt; &lt;a href="https://github.com/PugarHuda/brownie-to-ape" rel="noopener noreferrer"&gt;github.com/PugarHuda/brownie-to-ape&lt;/a&gt; · v0.7.8 · MIT&lt;br&gt;
&lt;strong&gt;Codemod registry:&lt;/strong&gt; &lt;a href="https://app.codemod.com/registry/@pugarhuda/brownie-to-ape" rel="noopener noreferrer"&gt;&lt;code&gt;@pugarhuda/brownie-to-ape&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Related:&lt;/strong&gt; &lt;a href="//./MEDIUM_ARTICLE.md"&gt;token-mix end-to-end case study&lt;/a&gt; · &lt;a href="//./CASE_STUDY_3_TRADEOFFS.md"&gt;engineering tradeoffs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ethereum</category>
      <category>python</category>
      <category>tutorial</category>
      <category>web3</category>
    </item>
  </channel>
</rss>
