<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Convergence Boy</title>
        <link>https://paragraph.com/@vicnaum</link>
        <description>undefined</description>
        <lastBuildDate>Mon, 20 Apr 2026 14:45:05 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>Convergence Boy</title>
            <url>https://storage.googleapis.com/papyrus_images/40fc6e213df9c8e54f204979a670c30261572d714a42a8c551fab0901c8c3c86.jpg</url>
            <link>https://paragraph.com/@vicnaum</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Making of HappyNewYear CTF Puzzle ]]></title>
            <link>https://paragraph.com/@vicnaum/making-of-happynewyear-ctf-puzzle</link>
            <guid>UkP8AUfRtChn79QjNzrI</guid>
            <pubDate>Wed, 04 Jan 2023 12:33:01 GMT</pubDate>
            <description><![CDATA[Imagine there is a contract that can execute any code you send to it. And imagine it also holds some money. How easy would it be to hack it? It would be quite easy, especially if all the opcodes are available. That’s why I decided to create a puzzle like this, but with a few extra tricks to make it a bit harder to solve (even if the tricks are mostly “smoke and mirrors”). I announced the puzzle on Twitter two days before 2023, and it has already been solved: https://twitter.com/0x796/status/1...]]></description>
            <content:encoded><![CDATA[<p>Imagine there is a contract that can execute any code you send to it. And imagine it also holds some money. How easy would it be to hack it?</p><p>It would be quite easy, especially if all the opcodes are available. That’s why I decided to create a puzzle like this, but with a few extra tricks to make it a bit harder to solve (even if the tricks are mostly “smoke and mirrors”).</p><p>I announced the puzzle on Twitter two days before 2023, and it has already been solved:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/0x796/status/1608553575969611777">https://twitter.com/0x796/status/1608553575969611777</a></p><p>But if you still want to try and solve it yourself before reading the rest of this article, here is the saved bytecode (the contract self-destructed after it was solved):</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://library.dedaub.com/contracts/Ethereum/0xA0Eb20483Cb60213bF944c2C3833bebc9fbc4706/bytecode?line=1">https://library.dedaub.com/contracts/Ethereum/0xA0Eb20483Cb60213bF944c2C3833bebc9fbc4706/bytecode?line=1</a></p><p><strong>Note: the rest of this article contains a full code review of the puzzle, so it will ruin the fun if you don&apos;t want any help solving it!</strong></p><h2 id="h-spoilers-ahead" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">SPOILERS AHEAD!</h2><p>Now that all the warnings have been given, let’s dive into how the puzzle was constructed and see how I wrote the contract…</p><h2 id="h-ascii-salute" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">ASCII salute 🫡</h2><p>To start off, why not begin the contract bytecode with an ASCII encoded string? And let’s make it thematic. Just because we can :)</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/aa0df50502478a9fb77483c6d52d22ef1d0b18fedf7c7145f5b93c4371c563d3.png" alt="&quot;Happy New Year Everybody!!!!!!!&quot; encoded at the beginning of the bytecode" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">&quot;Happy New Year Everybody!!!!!!!&quot; encoded at the beginning of the bytecode</figcaption></figure><p>I opened my fork of evm.codes, which instantly decompiles bytecode, and played around with different ASCII text to see which opcodes it decompiled to. I ended up with a “Happy New Year” greeting – and I was pretty lucky:</p><ul><li><p>“H” is ASCII 0x48, which is just a BASEFEE opcode – it can stay on the stack.</p></li><li><p>“a” is 0x61, which is PUSH2, pushing the next two chars “pp” to the stack.</p></li><li><p>“y” is 0x79, which is PUSH26, pushing the rest of the string with some exclamation marks at the end.</p></li></ul><p>This way, at the start of the contract we have these three things on the stack – but do we really need them? We’ll see.</p><p>Unfortunately, Etherscan doesn’t have an option to show contract code as UTF-8 or ASCII (as it does with calldata). Otherwise, everyone would be able to see the greetings :-/</p><p>I realized this only after deploying the contract. But it’s a nice Easter egg (Santa egg?) for anyone who is curious.</p><h2 id="h-now-lets-do-the-can-run-anything-part" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Now let’s do the “can run anything” part</h2><p>Contracts can’t run arbitrary code from input – there is no opcode for that. The only way to make them do that is by deploying the code into a new contract and then doing a DELEGATECALL – which means that the code from the external contract will be executed by the current contract in the current context. And that’s exactly what we need!</p><p>How do we deploy an external contract from bytecode? Using the CREATE or CREATE2 bytecodes. Let’s go with a simple CREATE – it just needs a few parameters, such as the value of the money to pass (0), the offset (location of the code) in memory, and the size of the code in memory.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/13c0f4b5d7d4786758b979dd62bf9d41ed3497cb75d65e8a45832afba720e43d.png" alt="CREATE op-code" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">CREATE op-code</figcaption></figure><p>That’s all easy, but how do we deploy a contract whose code only contains the “send money to me” instruction? How does a contract deployment happen anyway?</p><p>When a contract is being deployed on an address, it’s constructor code is executed on that address (even before the address has any code). And for the contract to be considered “deployed” and to have some code remain forever on the address, the constructor code must RETURN the deployed code at the end of its execution.</p><p>This may sound strange, so let’s see what it means in practice.</p><p>Here is a constructor code that deploys a contract with “32FF” in its bytecode:</p><pre data-type="codeBlock" text="PUSH2 0x32FF // Push 32FF into stack
PUSH1 0x00   // Push 0 offset where to save it
MSTORE       // Save it to 0 position in memory (0x00.....0032FF)
PUSH1 0x02   // Push 2 into stack (size of our bytecode)
PUSH1 0x1e   // Push 0x1E (18) - which skips all the 18 zeroes
RETURN       // Returns just 32FF from memory - this is our bytecode
"><code>PUSH2 <span class="hljs-number">0x32FF</span> <span class="hljs-comment">// Push 32FF into stack</span>
PUSH1 <span class="hljs-number">0x00</span>   <span class="hljs-comment">// Push 0 offset where to save it</span>
MSTORE       <span class="hljs-comment">// Save it to 0 position in memory (0x00.....0032FF)</span>
PUSH1 <span class="hljs-number">0x02</span>   <span class="hljs-comment">// Push 2 into stack (size of our bytecode)</span>
PUSH1 <span class="hljs-number">0x1e</span>   <span class="hljs-comment">// Push 0x1E (18) - which skips all the 18 zeroes</span>
RETURN       <span class="hljs-comment">// Returns just 32FF from memory - this is our bytecode</span>
</code></pre><p>After executing this constructor code, the 32FF will remain on the contract forever. Well, not forever in this case - it will self-destruct on any call :-D</p><p>There is an easier way of returning the bytecode than copying it to memory from the stack and that is using CODECOPY:</p><pre data-type="codeBlock" text="PUSH1 0x02   // Push 2 into stack (size of our code)
PUSH1 0x0c   // 0x0C is where our code begins in this code
PUSH1 0x00   // 0x00 where to save code in memory
CODECOPY     // Copy 2 bytes of code from 0x0C and save to memory
PUSH1 0x02   // 2 is size of the code in memory
PUSH1 0x00   // 0 is location in memory
RETURN       // Returns 2 bytes from 0 location in memory
ORIGIN       // This is offset 0x0C - where our code begins
SELFDESTRUCT // Origin is who called the TX, selfdestruct sends money to that address
"><code>PUSH1 <span class="hljs-number">0x02</span>   <span class="hljs-comment">// Push 2 into stack (size of our code)</span>
PUSH1 <span class="hljs-number">0x0c</span>   <span class="hljs-comment">// 0x0C is where our code begins in this code</span>
PUSH1 <span class="hljs-number">0x00</span>   <span class="hljs-comment">// 0x00 where to save code in memory</span>
CODECOPY     <span class="hljs-comment">// Copy 2 bytes of code from 0x0C and save to memory</span>
PUSH1 <span class="hljs-number">0x02</span>   <span class="hljs-comment">// 2 is size of the code in memory</span>
PUSH1 <span class="hljs-number">0x00</span>   <span class="hljs-comment">// 0 is location in memory</span>
RETURN       <span class="hljs-comment">// Returns 2 bytes from 0 location in memory</span>
ORIGIN       <span class="hljs-comment">// This is offset 0x0C - where our code begins</span>
SELFDESTRUCT <span class="hljs-comment">// Origin is who called the TX, selfdestruct sends money to that address</span>
</code></pre><p>As you can see, this is a bit different: we put our contract code &quot;as is&quot; - just after the RETURN, and then we just say &quot;hey, CODECOPY from this offset please&quot; - and we have this code in memory and return it - thus deploying it on-chain.</p><p>That&apos;s the general template for deploying some bytecode, and in our case - we need to take the bytecode from calldata and also determine its size. Luckily, we can use PUSH2 for the size, and it will also work the same as PUSH1, which will give us enough slack for deploying even 65535 bytes of bytecode (and contracts can&apos;t be that large - so that&apos;s a universal solution).</p><p>This code looks like this in bytecode:</p><pre data-type="codeBlock" text="0x61000080600a3d393df3
"><code></code></pre><p>Where instead of four zeros &quot;0000&quot; we should paste the size of the bytecode we are about to execute (which is equal to calldata size).</p><p>To make it a little bit more obscure - let&apos;s XOR it with our HAPPY NEW YEAR intro and deXOR it at runtime - it&apos;s on the stack already, so why not:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/63b6d21654466cb4668c4fc1f58dc08cbabeae1683b1bed840f69fb92520645d.png" alt="XOR&apos;ing the constructor" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">XOR&apos;ing the constructor</figcaption></figure><p>This will make it a little bit harder for anyone decompiling to understand why this is there. But just a little bit - any experienced developer will just be delayed a couple of minutes more.</p><h2 id="h-huffin" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Huffin</h2><p>After some experimentation with bytecode, I&apos;ve decided it&apos;s time to organize and start a Huff project - it&apos;s also a good excuse to finally learn Huff.</p><p>So far, we have this:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/8e570b9ede698e56bad95fa2f423e12bfb9b5ac2427e2c2aded295ece3b2de2e.png" alt="Basics of the CTF" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Basics of the CTF</figcaption></figure><p>I&apos;ve added a DELEGATECALL after the contract is deployed - it will execute the freshly deployed code.</p><p>So right now, it&apos;s all about sending 32FF to the contract and it will self-destruct while sending any money it holds back to the transaction origin (which is us).</p><p>That&apos;s still too primitive, so let&apos;s add more complexity to it: SOCIAL!</p><h2 id="h-social-mechanics" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Social mechanics</h2><p>Initially, I wanted to just put 0.1 ETH as a prize on the contract and that&apos;s it, but it&apos;s not as interesting as some kind of &quot;social game&quot; - where you need to put some of your money at stake if you feel you can solve the puzzle fast enough to win it back, along with the whole prize pool. This creates some interesting social mechanics:</p><ul><li><p>Initially, nobody would want to put their ETH on an unknown contract</p></li><li><p>But if you&apos;ve already solved the puzzle and you&apos;re 100% sure about what&apos;s happening in the bytecode, then you can safely deposit and take the prize</p></li><li><p>But what if someone else also solved the puzzle and just waits to frontrun you, taking both your money and the prize?</p></li></ul><p>That&apos;s why I&apos;ve also introduced a 1-hour delay between depositing and being able to submit a solution - so no direct frontrunning is possible without also having the frontrunner&apos;s funds at stake. And also:</p><ul><li><p>You have an incentive to deposit earlier, even if you&apos;re not 100% sure about your solution - but you already know the general principles and you&apos;re going to make it within 1 hour</p></li><li><p>If someone else has already deposited, you don&apos;t know if they already have the solution or if they&apos;re also just booking a place and still solving it</p></li></ul><p>I&apos;ve also introduced diminishing deposit - it goes to zero at 23:59 on December 31, 2022 - so booking a place at the beginning costs more, but diminishing still allows those who aren&apos;t sure to wait until it&apos;s cheaper, although with the cost of the prize being taken by someone else (which actually happened, but spoilers later).</p><p>Now, lets hope that these mechanics would make the game more fun and less risky to play!</p><p>Also I understood, that, as a creator, I know the solution and can frontrun anyone, so I&apos;ve decided that my initial 0.1 ETH prize should be sent during contract deployment so it doesn&apos;t count as a deposit (i.e. I cannot submit solutions). This still doesn&apos;t provide any proofs or guarantees that I will not mimic an anonymous user and submit another deposit from a separate unknown wallet, but anyways, that&apos;s impossible to prove, so that&apos;s the most I could do to make the game fair.</p><h2 id="h-xoring-the-calldata" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">XORing the calldata</h2><p>To make it a little bit harder from a technical standpoint, I&apos;ve also decided to XOR the provided calldata with the <strong>msg.sender</strong> address - this way we&apos;ll have an additional front-running protection: all solutions will be different for everyone. And this will act as an additional delay for hackers - it&apos;s not as easy as submitting &quot;32FF&quot; to the contract address anymore.</p><p>Writing the XOR loop in Huff was fun :) and required some effort. But in the end, here&apos;s how it looks:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/16add453a975c3ab492d38e39aebf120e9a9a4ea3cf888b3dac4288165def513.png" alt="XORing the calldata with msg.sender" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">XORing the calldata with msg.sender</figcaption></figure><p>How it works:</p><ol><li><p>It takes msg.sender and duplicates it to fit the whole 32-byte slot:</p></li></ol><pre data-type="codeBlock" text="0xd48B1EBb6828D1a1760b5c70aaE1aAE3CdcD18c1
                  ^^^^^^^^^^^^^^^^^^^^^^^^
  vvvvvvvvvvvvvvvvvvvvvvvv
0x760b5c70aaE1aAE3CdcD18c1_d48B1EBb6828D1a1760b5c70aaE1aAE3CdcD18c1
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
"><code><span class="hljs-number">0xd48B1EBb6828D1a1760b5c70aaE1aAE3CdcD18c1</span>
                  <span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span>
  vvvvvvvvvvvvvvvvvvvvvvvv
<span class="hljs-number">0x760b5c70aaE1aAE3CdcD18c1_d48B1EBb6828D1a1760b5c70aaE1aAE3CdcD18c1</span>
                           <span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span>
</code></pre><ol start="2"><li><p>It calculates the size of calldata, determining how many 32-byte slots it takes - thus how many times we need to process it.</p></li><li><p>It processes the calldata (copied to memory already) slot-by-slot, XORing it with this duplicated msg.sender 32bit value.</p></li></ol><p>After this, 32FF becomes something else - so the hacker needs to figure this out and XOR it beforehand, so the contract de-XORs it.</p><h2 id="h-how-all-these-xors-work" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">How all these XORs work?</h2><p>A little side note: talking about all those XORs, what is happening here?</p><p>For those who don&apos;t know, XOR is a bit-operation that can be applied to two numbers.</p><p>Technically, what it does is show the difference between the bit representations of two numbers:</p><pre data-type="codeBlock" text="A = 0001110001010000111
B = 0001110001111000000
XOR 0000000000101000111
              ^ ^   ^^^
"><code>A <span class="hljs-operator">=</span> 0001110001010000111
B <span class="hljs-operator">=</span> 0001110001111000000
XOR 0000000000101000111
              <span class="hljs-operator">^</span> <span class="hljs-operator">^</span>   <span class="hljs-operator">^</span><span class="hljs-operator">^</span><span class="hljs-operator">^</span>
</code></pre><p>As you can see, XOR shows which bits differ between two numbers and puts 1 there.</p><p>But another interesting property of XOR is that if you XOR a number with some other number, it becomes &quot;encrypted.&quot; Why? Because if you XOR this encrypted number with the same number again, you&apos;ll get back the original number!</p><p>Here’s an example:</p><pre data-type="codeBlock" text="A = 000111000111
B = 010101010101
XOR 010010010010

and then you XOR the result with B again:
RES 010010010010
B = 010101010101
XOR 000111000111

And you see that XOR result here is the same as A!
"><code>A = <span class="hljs-number">000111000111</span>
B = <span class="hljs-number">010101010101</span>
<span class="hljs-built_in">XOR</span> <span class="hljs-number">010010010010</span>

<span class="hljs-built_in">and</span> <span class="hljs-keyword">then</span> you <span class="hljs-built_in">XOR</span> the result <span class="hljs-keyword">with</span> B again:
RES <span class="hljs-number">010010010010</span>
B = <span class="hljs-number">010101010101</span>
<span class="hljs-built_in">XOR</span> <span class="hljs-number">000111000111</span>

<span class="hljs-built_in">And</span> you see that <span class="hljs-built_in">XOR</span> result here <span class="hljs-built_in">is</span> the same <span class="hljs-keyword">as</span> A!
</code></pre><p>That&apos;s why we XOR things back and forth - to &quot;encode&quot; and &quot;decode&quot; them - that&apos;s the simplest encoding human kind has known since punch cards became a trend. Any experienced hacker knows about it, but it&apos;s still always fun!</p><h2 id="h-registering-players" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Registering players</h2><p>How do we register the players who made a deposit? It&apos;s simple - just put something in storage at the msg.sender address slot. And this something will be a timestamp - because we&apos;re introducing this 1-hour delay between depositing and the solution.</p><p>Here&apos;s how it looks in Huff:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/25900a511929aa59eba39c0b353adc9c82dea34289eba205e821be09aaf3d891.png" alt="Registering player and check callvalue macro" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Registering player and check callvalue macro</figcaption></figure><p>I guess everything is clear from the comments - first, we check that a proper ETH amount was sent with the transaction (which depends on the time until NEW_YEAR), and then we register the player by saving the current timestamp + SOLVING_DELAY to storage under the msg.sender address slot.</p><h2 id="h-verifying-if-player-was-registered" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Verifying if player was registered</h2><p>And when submitting a solution, we verify if the player was registered:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/50bdba9ceb5208141e810227c5380837851a8ba39d47b8bb72f4290f0bbbf8b2.png" alt="Verify if payer was registered and if they can already submit a solution" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Verify if payer was registered and if they can already submit a solution</figcaption></figure><p>It&apos;s also pretty simple - just reading from storage and comparing it to 0 (if the player was registered at all) and to the current timestamp (to check if they can already submit a solution).</p><h2 id="h-piling-everything-up-together" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Piling everything up together</h2><p>Now that we have all the functions, let&apos;s put everything together into one big Huff program :)</p><p>I won&apos;t copy the screenshots of the above functions, but I&apos;ll just show you the MAIN() macro instead:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b91d64441ac98722eecb37cf9e4171305b0e1581276d32386b765f84bab4020f.png" alt="MAIN() macro" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">MAIN() macro</figcaption></figure><p>I&apos;ve added some more tricks like using RETURNDATASIZE instead of PUSH1 0x00 - because in this case it does the same thing - pushes zero onto the stack. But it also saves bytecode (because it&apos;s just 1 byte) and hopefully confuses hackers a little bit more.</p><p>I&apos;ve also added the &quot;CONGRATZ&quot; text along with &quot;chad]&quot; as push instructions. Why? Because the delegate_call JUMPDEST is 0x5B, which is &quot;[&quot;, so I wanted to use that and make it look like &quot;CONGRATZ [chad]&quot; when you look at the bytecode :)</p><p>Honestly, I also wanted to make it a string and jump inside of it, but in the end, I found out that you can&apos;t jump inside PUSH instructions data :-( (more on that in my <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/vicnaum.eth/zJX21EV6bjrPcL_8fnI-0zoChvBw-ZscbL7S7inroro">previous article</a>).</p><h2 id="h-results" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Results!</h2><p>So my prediction was that an experienced hacker should be able to solve this within 1-2 hours (thus the 1-hour delay). And that&apos;s basically what happened - yannickcrypto.eth deposited his stake after about an hour or two after I deployed and announced the game, and then submitted a solution in another hour or two - grabbing the prize and making the whole contract lifetime just 4 hours. Congrats to Yannick!</p><p>The solution was exactly that: &quot;32FF&quot; encoded, so the contract self-destructed and sent all the money to the hacker.</p><p>The code is still available on dedaub:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://library.dedaub.com/contracts/Ethereum/0xA0Eb20483Cb60213bF944c2C3833bebc9fbc4706/decompiled?line=1">https://library.dedaub.com/contracts/Ethereum/0xA0Eb20483Cb60213bF944c2C3833bebc9fbc4706/decompiled?line=1</a></p><p>And look how amazed I was with the output of their decompiler!</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c691c82199777637e86a55661fcc38a3d9d657892fcb71b5d70189119c9c1e68.png" alt="Decompiled bytecode by Dedaub" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Decompiled bytecode by Dedaub</figcaption></figure><p>It decoded everything into very readable Solidity code! And it did the best job out of all the decompilers I&apos;ve tested this bytecode with (some didn&apos;t even output anything because they were probably confused by the &quot;HAPPY NEW YEAR&quot; intro :-D)</p><p>It&apos;s too bad I didn&apos;t know about its existence when I was testing the game, because with such code, it shouldn&apos;t take longer than 15-30 minutes for anyone experienced enough to understand what&apos;s going on, making the game too easy.</p><p>But still, it was a good experience. I hope the next puzzles and games that I make will be better and more fun to participate in!</p><p>As always, stay tuned, and if you like the content, subscribe, collect, and follow me on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/0x796/">Twitter</a> to not miss the fun next time!</p><p>As always - stay tuned, and if you like the content: subscribe, collect, and follow me on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/0x796/">Twitter</a> to not miss the fun next time!</p><div data-type="subscribeButton" class="center-contents"><a class="email-subscribe-button" href="null">Subscribe</a></div>]]></content:encoded>
            <author>vicnaum@newsletter.paragraph.com (Convergence Boy)</author>
        </item>
        <item>
            <title><![CDATA[PUSH1: or "Parsing EVM Bytecode"]]></title>
            <link>https://paragraph.com/@vicnaum/push1-or-parsing-evm-bytecode</link>
            <guid>8wzGtd6oSN3l11s97Xyp</guid>
            <pubDate>Wed, 28 Dec 2022 12:22:54 GMT</pubDate>
            <description><![CDATA[Have you ever encountered a JUMPDEST opcode in Ethereum bytecode and wondered how the execution can jump there? Look at this delicious 5Bs, so many places the code can run from!A lot of 5B&apos;s - the JUMPDEST opcodesIf there is a 5B you can always jump there, right?… Wrong! And the answer has to do with something called instruction boundaries.What are instruction boundaries and why do they matter?When the Ethereum Virtual Machine (EVM) processes bytecode, it loads each octet and defines whe...]]></description>
            <content:encoded><![CDATA[<p>Have you ever encountered a JUMPDEST opcode in Ethereum bytecode and wondered how the execution can jump there? Look at this delicious 5Bs, so many places the code can run from!</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c3da05efb7df2e9c611002c0ed7b7f84f81cba8ae422d9abcd71239d11579eb1.png" alt="A lot of 5B&apos;s - the JUMPDEST opcodes" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">A lot of 5B&apos;s - the JUMPDEST opcodes</figcaption></figure><p>If there is a 5B you can always jump there, right?…</p><p>Wrong!</p><p>And the answer has to do with something called instruction boundaries.</p><h3 id="h-what-are-instruction-boundaries-and-why-do-they-matter" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">What are instruction boundaries and why do they matter?</h3><p>When the Ethereum Virtual Machine (EVM) processes bytecode, it loads each octet and defines where one instruction starts and ends. These are known as instruction boundaries. Most opcodes are one-byte, with the exception of the PUSH opcodes. There are 32 PUSH opcodes, ranging from PUSH1 to PUSH32, and they all take more than one byte in bytecode for a single instruction at the program counter. When the EVM encounters anything between 60-7F, it needs to determine which PUSH it is and the size of the accompanying payload, then load everything at once as a single instruction and skip to the next bytecode byte.</p><p>But what does this has to do with JUMPDESTs? 5B is a 5B in the end! You see it in code - you jump!</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/6655f590b9810b95a61412ef6e94838840a5f07bc1c5f637a747f4d835ab10e3.png" alt="See a 5B? Jump!" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">See a 5B? Jump!</figcaption></figure><p>Not really. If you’ve seen the Ethereum Yellow Paper, there’s a separate section there about the Validity of Jump Destinations (9.4.3):</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/a6cddceaf4305a5a0a8240b15a915baedada0b7fd5ca1083bbeedca2ec6a1087.png" alt="It is really yellow!" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">It is really yellow!</figcaption></figure><p>And it explicitly states that &quot;all [JUMP] positions must be on valid instruction boundaries, rather than sitting in the data portion of PUSH operations.&quot;</p><h3 id="h-in-other-words-you-cannot-jump-to-a-byte-in-the-middle-of-a-push-instruction" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">In other words, you cannot jump to a byte in the middle of a PUSH instruction.</h3><p>For example, consider the following bytecode:</p><blockquote><pre data-type="codeBlock" text="5B600055
"><code></code></pre></blockquote><p>At first glance, it may seem like this bytecode consists of a JUMPDEST opcode followed by a PUSH1 00 opcode and an SSTORE opcode, which could potentially write something to storage. However, if we take a broader look in the context of it, the JUMPDEST opcode is actually in the middle of a PUSH instruction:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/21369b8de109aa5dffc8f3291e7aea5cedae504a8aab192eba6a624fa440a632.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>So it’s not really a JUMPDEST, but a “5b600055” number pushed into stack. Therefore, the JUMPDEST opcode is not on an instruction boundary and cannot be jumped to.</p><h3 id="h-and-what-about-all-those-sweet-5bs-i-saw-in-the-beginning" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">“And what about all those sweet 5Bs I saw in the beginning?”</h3><p>That code was from SeaPort and actually it’s a string that contains 5B, that is pushed into the stack with PUSH32:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/025c41f0999c2e1744a5e0540498b7acacf4101149d002c704a6c0a6b46b0cb6.png" alt="SeaPort code containing 5B" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">SeaPort code containing 5B</figcaption></figure><p>And the string is “ConsiderationItem[]” or whatever that means:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/73799fe51badf81f0b6f6ae52778bddb3da33a7af09f72d9fbc8e0fa5ec03bfc.png" alt="Consider a ConsiderationItem[] consideratio" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Consider a ConsiderationItem[] consideratio</figcaption></figure><p>So the next time you see a JUMPDEST opcode in the middle of a PUSH instruction, you&apos;ll know you can&apos;t jump there.</p><p>And if you want to read and translate Ethereum bytecode into something more readable, just remember to process it sequentially and take PUSH instructions and their data into account.</p><p>Actually, it’s very easy, here’s a sample TypeScript code that does exactly that:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/811b428733df0f980e65d685203f01d68b1d82fbd03a1a8157ab7ac8ce5fca71.png" alt="https://github.com/comitylabs/evm.codes/blob/main/context/ethereumContext.tsx" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">https://github.com/comitylabs/evm.codes/blob/main/context/ethereumContext.tsx</figcaption></figure><p>And here’s a bonus cherry on the top - a fully RegExp disassembler for EVM bytecode:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/0x796/status/1608039943582142464">https://twitter.com/0x796/status/1608039943582142464</a></p><p>The regex strings are linked in the tweet reply - take a look! It only works in the PCRE2 flavor of regex (Perl family), so you may need to apply some tricks if your language doesn&apos;t support it.</p><p><strong>So, in the end, we are safe and no jumps to the middle of revert strings are possible. You can breath out and make yourself a favorite hot drink.</strong></p><blockquote><p><em>If you like the stuff I write - subscribe, collect, follow me on </em><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/0x796"><em>Twitter</em></a><em> and spread the word!</em></p></blockquote><div data-type="subscribeButton" class="center-contents"><a class="email-subscribe-button" href="null">Subscribe</a></div>]]></content:encoded>
            <author>vicnaum@newsletter.paragraph.com (Convergence Boy)</author>
        </item>
        <item>
            <title><![CDATA[Forget about "optimizing"]]></title>
            <link>https://paragraph.com/@vicnaum/forget-about-optimizing</link>
            <guid>1mc6Isu3gb9AY3lBQelu</guid>
            <pubDate>Sat, 10 Dec 2022 10:54:31 GMT</pubDate>
            <description><![CDATA[Is Solidity optimization a waste of time?(or: surprising facts about the compiler&apos;s optimizer)Imagine you have a packed struct like this in storage:Storage Packed StructCan you guess which code will be cheaper to run:the one that’s reading every value from storage several times? (A)the one reading from a storage reference? (B)or maybe the one loading the whole struct into memory first? (C)Which code will be cheaper?Wrong. Whatever you guessed - you’re not even close!As Solidity developer...]]></description>
            <content:encoded><![CDATA[<h2 id="h-is-solidity-optimization-a-waste-of-time" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Is Solidity optimization a waste of time?</h2><h3 id="h-or-surprising-facts-about-the-compilers-optimizer" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">(or: surprising facts about the compiler&apos;s optimizer)</h3><p>Imagine you have a packed struct like this in storage:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/43e13ae4df98e24dcaaff4c02a2a7d74244b9b62e954daa895e5163812478a5f.png" alt="Storage Packed Struct" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Storage Packed Struct</figcaption></figure><p><strong>Can you guess which code will be cheaper to run:</strong></p><ul><li><p>the one that’s reading every value from storage several times? (A)</p></li><li><p>the one reading from a storage reference? (B)</p></li><li><p>or maybe the one loading the whole struct into memory first? (C)</p></li></ul><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ba83a2c26d1be8ee64afd35ed29486b73ce869ade739a6a0c636b527a0f83296.png" alt="Which code will be cheaper?" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Which code will be cheaper?</figcaption></figure><h3 id="h-wrong-whatever-you-guessed-youre-not-even-close" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Wrong. Whatever you guessed - you’re not even close!</h3><p>As Solidity developers, we&apos;re constantly on the lookout for ways to optimize our code. We read articles, follow advice from experts, and employ various techniques to ensure that our smart contracts are as efficient as possible. But, as it turns out, many of these optimization techniques may be completely ineffective.</p><p>The Solidity compiler&apos;s optimizer is incredibly powerful, and often does a better job of optimizing code than we could do ourselves. In fact, our attempts at optimization may actually make things worse, as the optimizer will often rewrite our code in ways that we can&apos;t predict (because it’s also dumb lol :)).</p><p>So the real answer to the question is - another question: What are the project’s optimizer settings?</p><p>Because the gas usage of these three functions will be completely different if:</p><ul><li><p>Optimizer is off (rare - can skip)</p></li><li><p>Optimizer is On with default settings</p></li><li><p>We use the new trendy VIA-IR way</p></li></ul><p>The easiest and the most predictable case is without the optimizer: the resulting bytecode will be quite large and bloated and contain many <code>SLOAD</code> operations (like loads), but surprisingly - even for the memory mode.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ac4794f107a932267e32fa3820019c53b090a93cb281b725fe84265abf43abd3.png" alt="Optimizer off" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Optimizer off</figcaption></figure><p>Here the results are at least predictable to the common knowledge: yes, the reading the struct values one-by-one directly from storage is more expensive (+111 gas) than loading the whole struct to memory and reading from there. And Storage Refs are even more (+13 gas) expensive than direct reads.</p><p><strong>SLOAD usage without optimizer:</strong></p><ul><li><p>Read Directly: 4 SLOADs</p></li><li><p>Read From Storage Ref: 4 SLOADs</p></li><li><p>Read From Memory Copy: 3 SLOADs</p></li></ul><h2 id="h-you-see" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">You see?</h2><p>Already everything you’ve been told is a lie - even without the optimizer - the loading of a packed struct into memory takes 3 SLOADs! And direct read (in this case) is 4 - cause we read things 4 times. So what will be the case when it’s 3 SLOAD for storage vs 1 SLOADs for memory?</p><h3 id="h-lets-enable-the-optimizer" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Let’s enable the optimizer!</h3><p>With default settings:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/a70e8d826e38312543fc2fd6745cf959215535e47577101b2ce5c30883a4e56d.png" alt="Optimizer enabled... WTF?" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Optimizer enabled... WTF?</figcaption></figure><p>Wait… But I thought… I was told…</p><p>What the hell just happened here?</p><p>Why is Direct read from storage cheaper than caching the struct into memory?</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/74eab6cf7c02c6e8e4170bdb06a111dfc52908493192b77caaf4e73c1f9f52e5.png" alt="Cannot trust anyone anymore..." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Cannot trust anyone anymore...</figcaption></figure><p>So what happens here is: with the optimizer turned on, you will only have two <code>SLOAD</code> operations for the Direct and StorageRef methods, and one <code>SLOAD</code> for the memory method.</p><p>But, surprisingly, the memory method still is the most expensive. Why? Because while one additional <code>SLOAD</code> on a hot slot costs 100 gas, the operations required to read the struct into and from memory will cost more than 100 gas - and the Direct and StorageRef stuff will just use stack (which is cheaper than memory). So that’s why the additional costs.</p><p>Overall, enabling the optimizer here saved us 1000 gas overall, but had such a weird and unexpected behaviour that I don’t know what I’m doing with my code anymore…</p><p>So where is the promised 3 SLOADs-storage vs 1 SLOAD-memory and memory being cheaper? Maybe VIA-IR will give us this?</p><h2 id="h-via-ir" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">VIA-IR</h2><p>Let’s try the new unexplored tech and enable via_ir:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f6322a4bf2f556fe30e75e5d5819d05cc7e3832ba989507e519971ba299d95da.png" alt="I don&apos;t wanna live on this planet anymore..." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">I don&apos;t wanna live on this planet anymore...</figcaption></figure><p>How did memory become almost 200 more gas expensive than reading from storage??</p><p><strong>Let’s look at the IR code:</strong></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3ba787fed3ce37a596b6bb4648a3e824457476fdc46b4da424803a75393235ab.png" alt="Direct on the left VS Memory on the right" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Direct on the left VS Memory on the right</figcaption></figure><p>The DirectRead and ReadFromStorageRef compiled to the exact same Yul code, so I just include one to save the screenshot space.</p><p>So if you use compilation via-IR, all three methods will use only one <code>SLOAD</code>! And yet, the memory method will still be the most expensive (in fact, a whole lot more expensive, by 200 gas) due to 3 more <code>MSTORE</code> and 4 more <code>MLOAD</code> opcodes used to initialize the struct in memory, rather than performing everything on the stack like the smart DirectRead method does.</p><h2 id="h-so-where-is-the-holy-grail-of-3-sloads-vs-1-sload" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">So where is the holy grail of 3 SLOADs vs 1 SLOAD?</h2><h2 id="h-there-is-none" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">There is none.</h2><p>No compilation options give the “expected” optimizooor behavior.</p><p>That’s just a lie.</p><p>It will not happen in real life.</p><p>And now you’ll have to live with this knowledge.</p><h2 id="h-conclusion" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Conclusion</h2><p>So, what can we conclude from this? It&apos;s time to reconsider our approach to optimizing Solidity code. We can&apos;t rely on our own techniques and tricks, as the optimizer will often undo our efforts. Instead, sometimes we just need to trust the compiler and focus on testing our code’s gas usage to see the real-world impact of our optimizations.</p><h2 id="h-tldr" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">TLDR?</h2><h3 id="h-test-for-gas-usage-after-every-optimization-you-introduce" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Test for gas usage after every “optimization” you introduce.</h3><p>And don’t introduce optimizations too early - first get your code to work and make it beautiful - and then strike for lower gas.</p><h2 id="h-ps" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">P.S.</h2><p>But, you may be wondering, is it even worth trying to optimize Solidity code at all?</p><p>The answer is yes – but only if we approach it in the right way. Instead of trying to outsmart the compiler, we need to work with it. This means using the via-IR compiler whenever possible, and being willing to let go of our preconceived notions about what makes for efficient code. We’re clearly moving towards C world, where the C compiler can already outperform any human effort in optimizing the routines (unless you don’t write really dumb code).</p><p>So, the next time you&apos;re tempted to use one of those clever optimization techniques you read about online, stop and think.</p><p>Will it actually make a difference, or will the optimizer just undo your efforts?</p><p><strong>The only way to know for sure is to test your code and see the results for yourself. And, who knows, you may be surprised at what you find.</strong></p><blockquote><p><em>If you like the stuff I write - subscribe, collect and spread the word!</em></p></blockquote><div data-type="subscribeButton" class="center-contents"><a class="email-subscribe-button" href="null">Subscribe</a></div>]]></content:encoded>
            <author>vicnaum@newsletter.paragraph.com (Convergence Boy)</author>
        </item>
        <item>
            <title><![CDATA[A thing about gas-golfing and test-fitting]]></title>
            <link>https://paragraph.com/@vicnaum/a-thing-about-gas-golfing-and-test-fitting</link>
            <guid>3YM5UFAogaKdX8DdqNjd</guid>
            <pubDate>Sat, 22 Oct 2022 11:29:28 GMT</pubDate>
            <description><![CDATA[Crypto-twitter likes puzzles. And gas-golfing. Any CTF puzzle gains huge success, although in the end it all comes down to “test-fitting”. Let me review a recent example from RareSkills that blew up on CT a couple of days ago:Distribution gas-puzzle by RareSkillsThe idea is to optimize gas in the contract and run provided tests for it that have a threshold. By default, the gas is way over the target:Provided tests results when ran on the original contractSo the “rules” are: no messing with op...]]></description>
            <content:encoded><![CDATA[<p>Crypto-twitter likes puzzles. And gas-golfing. Any CTF puzzle gains huge success, although in the end it all comes down to “test-fitting”.</p><p>Let me review a recent <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/RareSkills_io/status/1582492216559370242">example from RareSkills</a> that blew up on CT a couple of days ago:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e18b4ade4540cb358f767e52759875ed35b46420bb3406933e0c603669f48fdb.png" alt="Distribution gas-puzzle by RareSkills" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Distribution gas-puzzle by RareSkills</figcaption></figure><p>The idea is to optimize gas in the contract and run provided tests for it that have a threshold. By default, the gas is way over the target:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e3d175e1c0614f23114f9bbf25200bfa9bb2aafb322febd3c37f564f8f75c9bd.png" alt="Provided tests results when ran on the original contract" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Provided tests results when ran on the original contract</figcaption></figure><p>So the “rules” are: no messing with optimizer or solidity version, no messing with test files, use solidity (so no huff/yul/bytecode contracts), and don’t make functions <code>payable</code> (which is a weird rule, but ok).</p><h2 id="h-warning-spoilers-ahead" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">🚨🚨🚨WARNING! SPOILERS AHEAD!🚨🚨🚨</h2><p><em>Make sure you try to solve this puzzle yourself before reading further - it’s really fun and makes you learn stuff! But if you already solved it and want to read other ways of doing it, or just struggling and want to know what’s the trick - go ahead!</em></p><h2 id="h-lets-optimiiiiize" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Let’s optimiiiiize!</h2><p>Let’s do the obvious things first:</p><ol><li><p>Make vars immutable instead of storage (<strong>saves 10710 gas</strong> - because immutable vars are embedded in the contract bytecode as constants instead of reading from storage)</p></li><li><p>Replace <code>transfer</code> with <code>send</code> (<strong>saves 108 gas</strong> - cause send has no check if the transfer succeeded)</p></li><li><p>Replace <code>currentTime</code> with <code>endTime</code> (<strong>saves 69 gas</strong> - and do the time calculations in constructor)</p></li></ol><p>That gives us <strong>61066</strong> gas which is already 10887 better than original, but still 4k above the target:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/7826c944361f252d1ba9bc6f0ca67da8297ecca9e849aa27b6b0bbd349f23875.png" alt="After simple optimizations - still 4k short" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">After simple optimizations - still 4k short</figcaption></figure><h2 id="h-so-whats-the-trick" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">So what’s the trick?</h2><p>There’s a particular trick this puzzle should teach you. And it’s an old way of sending ETH to addresses by <code>SELFDESTRUCT</code>:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/23d78bdb79c926d79d6d500c0de91c9c9768667f69c614f2c2c3e705c22b189a.png" alt="Self-destructive way of solving puzzles" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Self-destructive way of solving puzzles</figcaption></figure><p>This trick alone saves you <strong>4066</strong> <strong>gas</strong> and meets the target!</p><h2 id="h-but-what-if-we-go-deeper" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">But what if we go… deeper?</h2><p>Every sane optimization always have its insane evil-brother… Let’s see how far we can push this!</p><p>Making the contract self-destructable already feels like cheating - why would you have a contract like this that ceases to exist after the first Distribution call? But tests are green so I guess it’s… OKAY?</p><p>Let’s see what else can leave the tests green, but optimize for more gas:</p><ul><li><p>Making contributors constant instead of immutable (saves <strong>24 gas</strong> - cause Hardhat addresses are always the same, so why not?</p></li><li><p>Make amount constant 0.25 ETH instead of calculating from balance (saves <strong>106 gas</strong> - cause again, in tests the amount is always the same, so why not?)</p></li><li><p>Use Assembly to do <code>call</code> instead of usual solidity <code>address.send</code> (saves <strong>90 gas</strong>)</p></li></ul><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/629db6d9ac46be95c33890d7d6fa97a475c02f95d79a7f3ef779a558fac4c625.png" alt="56780 so far" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">56780 so far</figcaption></figure><h2 id="h-even-deeper" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Even deeper?…</h2><p>Putting addresses as bytes32 number directly in calls can save us <strong>9 more gas units</strong>!</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/72019cb7a0bd05704c72af279fab577263409394a51ba981b5d34fa5fb1f12c4.png" alt="Zero-padded address in call: 56771" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Zero-padded address in call: 56771</figcaption></figure><h2 id="h-allright-how-bout-we-go-all-the-way-down" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Allright, how bout we go all the way down?</h2><p>So as far as we’re gaming tests, let’s game them even further.</p><p>Why don’t we just return if the test is “Gas Target”?</p><p>Knowing that Hardhat always runs tests in the same blocks, we can just put this in the beginning of our <code>distribute()</code> function:</p><p><code>if (block.number == 5) return;</code></p><p>And it will always return on the “Gas Target” test without consuming much gas, but will still do the rest of the function on “Business Logic” test :)</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e88abfd6a3befa6911760aa0192d7cf6c440d5e007a35944a2ac1e56de38b040.png" alt="Test-fitting like there&apos;s no tomorrow!" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Test-fitting like there&apos;s no tomorrow!</figcaption></figure><p>This gives us an amazing <strong>21207 gas</strong>! Which is unbelievable until you understand what’s going on here…</p><h2 id="h-so-what-thats-cheating" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">So what? That’s cheating!</h2><p>Yeah, and this space is often about cheating - MEV, exploits, code-is-law, and all other stuff: if you can do it - you do it. Remember the recent gas-CTF contests with best optimizooor getting an NFT? There nobody is solving the original problem anymore - everything’s about “cheating” the smart-contract to accept the required values and pass the required tests with the lowest bytecode and the lowest gas - and you get the #1 NFT.</p><h2 id="h-and-what-does-this-mean-we-all-doomed" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">And what does this mean? We all doomed?</h2><p>No :) We just need better testing and better conditions. Even this gas-puzzle could still verify the business logic in the Gas Target test, and use randomized values and addresses - so there’s no incentive to write code that just makes the tests green.</p><p>If the tests are good and fuzzy - there’s no possibility of writing hacky code to just meet the minimal requirements.</p><p>Same with the CTF contests. If they tested their problem solutions many times (like 1024 times in a single transaction - all with different numbers) - first, there will be no simple block-hacking (waiting for the right randomized number to submit your solution in a particular block to pass), and also the gas-estimation would be better when using different input values.</p><p><strong>I hope the next CTF’s and Puzzles would consider this, so the game becomes more about efficient algorithms, and not just clever ways to hack the system.</strong></p><p>If not - I would have to write one myself :)</p><p><strong>P.S.</strong> <em>The solutions provided here are not optimal - there are still a lot of ways to optimize even further: I’ve seen lower numbers, different tricks (including bytecode replacement), but here I’m describing my own experience with the puzzle. And trying to raise an issue about test-fitting and how to make things better.</em></p><p><em>Cheers, and let’s discuss the tricks you discovered in CT!</em></p><div data-type="subscribeButton" class="center-contents"><a class="email-subscribe-button" href="null">Subscribe</a></div>]]></content:encoded>
            <author>vicnaum@newsletter.paragraph.com (Convergence Boy)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/dea29d809bb90014596f81ad569472d42e3941015f51a5a469d2e7d90e086d17.png" length="0" type="image/png"/>
        </item>
    </channel>
</rss>