{"id":1242,"date":"2025-12-16T14:56:54","date_gmt":"2025-12-16T12:56:54","guid":{"rendered":"https:\/\/ackee.xyz\/blog\/?p=1242"},"modified":"2026-01-05T14:26:32","modified_gmt":"2026-01-05T12:26:32","slug":"test-proxy-contracts-safely-in-wake","status":"publish","type":"post","link":"https:\/\/ackee.xyz\/blog\/test-proxy-contracts-safely-in-wake\/","title":{"rendered":"Test Proxy Contracts Safely in Wake"},"content":{"rendered":"<h2>Introduction<\/h2>\n<p>Upgrades are where production bugs hide: missed initializers, wrong admin, or corrupted storage. Proxy patterns let you upgrade contracts, but they introduce complexity that traditional testing misses. Wake&#8217;s Python-first tests catch these issues before they reach mainnet.<\/p>\n<p>The result is clean test code. Calling implementation functions through a proxy is straightforward:<\/p>\n<pre><code class=\"language-python\">contract = ExampleERC20Upgradeable(proxy)<\/code><\/pre>\n<p>Here&#8217;s how to test proxy contracts in Wake.<\/p>\n<h2>1. Import the Proxy Contract<\/h2>\n<p>Wake needs to compile your proxy contract to generate Python type bindings (pytypes). If the proxy contract lives in your library directory, Wake won&#8217;t compile it by default.<\/p>\n<p>In <code class=\"codehl\">wake.toml<\/code>, the default configuration is <code class=\"codehl\">exclude_paths = [&quot;script&quot;, &quot;.venv&quot;, &quot;venv&quot;, &quot;node_modules&quot;, &quot;lib&quot;, &quot;test&quot;]<\/code>. This means contracts in these paths are not compiled unless imported from a non-excluded file.<\/p>\n<p>To make the contract available in your project, import it from somewhere outside <code class=\"codehl\">exclude_paths<\/code>. See the documentation for more details: <a href=\"https:\/\/ackee.xyz\/wake\/docs\/latest\/compilation\/\">https:\/\/ackee.xyz\/wake\/docs\/latest\/compilation\/<\/a><\/p>\n<p>Create <code class=\"codehl\">tests\/imports.sol<\/code> to make pytypes available:<\/p>\n<pre><code class=\"language-solidity\">import {ERC1967Proxy} from &quot;@openzeppelin\/contracts\/proxy\/ERC1967\/ERC1967Proxy.sol&quot;;<\/code><\/pre>\n<h2>2. Import the Proxy in Python<\/h2>\n<p>Run <code class=\"codehl\">wake up<\/code> again to compile. Wake generates Python bindings for both your implementation and the proxy contract. Import them into your test file:<\/p>\n<p><code class=\"codehl\">tests\/test_upgradable.py<\/code><\/p>\n<pre><code class=\"language-python\">from pytypes.contracts.ExampleERC20Upgradeable import ExampleERC20Upgradeable\nfrom pytypes.openzeppelin.contracts.proxy.ERC1967.ERC1967Proxy import ERC1967Proxy<\/code><\/pre>\n<h2>3. Deploy and Initialize<\/h2>\n<p>Deploy the implementation contract first, then create the proxy pointing to it. The proxy&#8217;s initialization data encodes a call to the implementation&#8217;s `initialize` function:<\/p>\n<pre><code class=\"language-python\">@chain.connect()\n@on_revert(revert_handler)\ndef test_default():\n\n    impl_erc20 = ExampleERC20Upgradeable.deploy()\n\n    proxy = ERC1967Proxy.deploy(\n        implementation =impl_erc20,\n        _data=abi.encode_call(ExampleERC20Upgradeable.initialize, (&quot;Upgradable Token&quot;, &quot;UPG&quot;, 10**20, chain.accounts[0])),\n        from_=chain.accounts[0]\n    )<\/code><\/pre>\n<p>The `_data` parameter encodes the initialization call that runs during proxy deployment. This replaces the constructor pattern used in non-upgradable contracts.<\/p>\n<h2>4. Access Implementation Functions Through the Proxy<\/h2>\n<p>Wrap the proxy address with the implementation contract class. This tells Wake to route all function calls through the proxy while using the implementation&#8217;s ABI:<\/p>\n<pre><code class=\"language-python\">contract = ExampleERC20Upgradeable(proxy)<\/code><\/pre>\n<p>Wake handles the delegatecall routing automatically, so you can interact with the contract as if it were a simple deployment.<\/p>\n<h2>5. Call Implementation Functions<\/h2>\n<p>All implementation functions are now available through the wrapped proxy. You can verify the contract&#8217;s behavior, inspect events, and test state changes:<\/p>\n<pre><code class=\"language-python\"># Verify initial balance\nassert contract.balanceOf(chain.accounts[1]) == 0\n\n# Execute transfer\ntx = contract.transfer(chain.accounts[1], 10**18, from_=chain.accounts[0])\n\n# Inspect emitted events\nevent = next(event for event in tx.events if isinstance(event, ExampleERC20Upgradeable.Transfer))\nassert event.from_ == chain.accounts[0].address\nassert event.to == chain.accounts[1].address\nassert event.value == 10**18\n\n# Verify updated balance\nassert contract.balanceOf(chain.accounts[1]) == 10**18<\/code><\/pre>\n<p>The test confirms the proxy correctly delegates to the implementation and maintains state as expected.<\/p>\n<h2>Conclusion<\/h2>\n<p>Wake simplifies proxy testing through its Python bindings. Wrap the proxy address with the implementation class and call functions directly. The same approach works with unit tests and Manually Guided Fuzzing (MGF), letting you test upgradable contracts with the same tools you use for standard contracts.<\/p>\n<p>This catches upgrade bugs (missed initializers, storage collisions, access control issues) before they become exploits. Test your proxy patterns the same way you test everything else.<\/p>\n<p>Learn more here, look: <a href=\"https:\/\/ackee.xyz\/blog\/a-beginners-guide-to-manually-guided-fuzzing\/\">A Beginner\u2019s Guide to Manually Guided Fuzzing<\/a><\/p>\n<h2>Appendix: Full Test Code<\/h2>\n<pre><code class=\"language-python\">import math\nfrom wake.testing import *\nfrom dataclasses import dataclass\n\nfrom pytypes.contracts.ExampleERC20Upgradeable import ExampleERC20Upgradeable\nfrom pytypes.openzeppelin.contracts.proxy.ERC1967.ERC1967Proxy import ERC1967Proxy\n\n# Print failing tx call trace\ndef revert_handler(e: RevertError):\n    if e.tx is not None:\n        print(e.tx.call_trace)\n\n@chain.connect()\n@on_revert(revert_handler)\ndef test_default():\n\n    impl_erc20 = ExampleERC20Upgradeable.deploy()\n\n    proxy = ERC1967Proxy.deploy(\n        implementation =impl_erc20,\n        _data=abi.encode_call(ExampleERC20Upgradeable.initialize, (&quot;Upgradable Token&quot;, &quot;UPG&quot;, 10**20, chain.accounts[0])),\n        from_=chain.accounts[0]\n    )\n\n    contract = ExampleERC20Upgradeable(proxy) # Just wrap the proxy with the contract Class to call functions\n\n    assert contract.balanceOf(chain.accounts[1]) == 0\n    tx = contract.transfer(chain.accounts[1], 10**18, from_=chain.accounts[0])\n\n    event = next(event for event in tx.events if isinstance(event, ExampleERC20Upgradeable.Transfer))\n    assert event.from_ == chain.accounts[0].address\n    assert event.to == chain.accounts[1].address\n    assert event.value == 10**18\n\n    assert contract.balanceOf(chain.accounts[1]) == 10**18<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Upgrades are where production bugs hide: missed initializers, wrong admin, or corrupted storage. Proxy patterns let you upgrade contracts, but they introduce complexity that traditional testing misses. Wake&#8217;s Python-first tests catch these issues before they reach mainnet. The result is clean test code. Calling implementation functions through a proxy is straightforward: contract = ExampleERC20Upgradeable(proxy) Here&#8217;s how to test proxy contracts in&hellip;<\/p>\n","protected":false},"author":24,"featured_media":755,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[61,10,80,103],"tags":[],"class_list":["post-1242","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-education","category-ethereum","category-solidity","category-wake"],"aioseo_notices":[],"featured_image_src":"https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2024\/06\/4-600x400.png","featured_image_src_square":"https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2024\/06\/4-600x600.png","author_info":{"display_name":"Naoki Yoshida","author_link":"https:\/\/ackee.xyz\/blog\/author\/naoki-yoshida\/"},"_links":{"self":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/posts\/1242","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/users\/24"}],"replies":[{"embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/comments?post=1242"}],"version-history":[{"count":0,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/posts\/1242\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/media\/755"}],"wp:attachment":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/media?parent=1242"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/categories?post=1242"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/tags?post=1242"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}