
{"id":195298,"date":"2026-07-09T15:31:42","date_gmt":"2026-07-09T15:31:42","guid":{"rendered":"https:\/\/mycryptomania.com\/?p=195298"},"modified":"2026-07-09T15:31:42","modified_gmt":"2026-07-09T15:31:42","slug":"gas-optimization-auditing-tips","status":"publish","type":"post","link":"https:\/\/mycryptomania.com\/?p=195298","title":{"rendered":"Gas Optimization &amp; Auditing Tips"},"content":{"rendered":"<h4>How Does a Compiler Actually\u00a0Work?<\/h4>\n<p>Image: <a href=\"https:\/\/grok.com\/\">Grok\u00a0AI<\/a><\/p>\n<p>Solidity is the dominant high-level programming language for writing smart contracts on Ethereum and compatible EVM blockchains. However, the Ethereum Virtual Machine (EVM) cannot execute Solidity code directly. It requires low-level bytecode consisting of opcodes and\u00a0data.<\/p>\n<p>The Solidity compiler (solc) bridges this gap by translating human-readable Solidity into EVM-executable bytecode. Understanding <em>how<\/em> and <em>why<\/em> the compiler works is essential for writing secure, gas-efficient, and maintainable smart contracts. It helps developers avoid common pitfalls, leverage optimizations effectively, debug issues, and ensure contracts can be verified on explorers like Etherscan.<\/p>\n<p>The primary implementation is solc, a C++ compiler maintained by the Solidity team (with solc-js as a JavaScript port via Emscripten). It takes Solidity source code (.sol files) as input and produces multiple outputs, including:<\/p>\n<p><em>EVM bytecode (creation and\u00a0runtime)<\/em><em>Application Binary Interface (ABI)<\/em><em>Contract metadata<\/em><em>Gas estimates<\/em><em>Abstract Syntax Tree\u00a0(AST)<\/em><em>Assembly representations<\/em><em>And more (via the Standard JSON interface)<\/em><\/p>\n<p>In modern versions (Solidity 0.8.x series, with documentation referencing 0.8.36-develop as of mid-2026), the compiler supports two main compilation pipelines:<\/p>\n<p><strong><em>Legacy pipeline (default):<\/em><\/strong><em> Direct translation from analyzed Solidity to bytecode.<\/em><strong><em>IR-based pipeline (viaIR: true):<\/em><\/strong><em> Solidity \u2192 Yul intermediate representation \u2192 optimization \u2192 bytecode. This path generally enables superior optimizations.<\/em><\/p>\n<p>Fun fact, inside the solidity compiler, there are two Solidity languages. 3 if you count Core. And 4 if you count Fe! <a href=\"https:\/\/x.com\/danielvf\/status\/2070189026271781185\">Thx Daniel for spotting<\/a> this funny\u00a0fact.<\/p>\n<h4>What Does It Actually\u00a0Do?<\/h4>\n<p>First of all, we must note that <a href=\"https:\/\/medium.com\/@thelukaswils\/understanding-compilers-for-humans-ba970e045877\">compilers essentially take text<\/a>, parse and process it, then turn it into binary for your computer to read. This keeps you from having to manually write binary for your computer, and furthermore, allows you to write complex programs\u00a0easier.<\/p>\n<p>In other words, the compiler converts high-level source code to low-level code. Then, the target machine executes low-level code. The compilation process consists of <a href=\"https:\/\/www.baeldung.com\/cs\/how-compilers-work\">several<\/a>\u00a0phases:<\/p>\n<p><em>1. Lexical\u00a0analysis<\/em><em>2. Syntax\u00a0analysis<\/em><em>3. Semantic\u00a0analysis<\/em><em>4. Intermediate code generation<\/em><em>5. Optimization<\/em><em>6. Machine Code generation<\/em><\/p>\n<p>To proceed to the next topic, we must first <a href=\"https:\/\/blog.trailofbits.com\/2020\/06\/05\/breaking-the-solidity-compiler-with-a-fuzzer\/\">understand<\/a> how <a href=\"https:\/\/www.alchemy.com\/overviews\/solidity-compiler\">this occurs in Solidity<\/a> and, as a result, how we can use it to audit and write code safely. It\u2019s <a href=\"https:\/\/medium.com\/zeppelin-blog\/solidity-compiler-audit-8cfc0316a420\">critical<\/a> to understand the specifics of the process we\u2019ll go over\u00a0below:<\/p>\n<p><strong>Lexing and Parsing<\/strong><br \/>The source code is broken into tokens (lexer) and then structured into an Abstract Syntax Tree (AST) (parser). This captures the syntactic structure of contracts, functions, variables, inheritance, etc.<strong>Semantic Analysis<\/strong><br \/>The compiler performs type checking, resolves names, handles inheritance (using C3 linearization), checks for errors (e.g., visibility, mutability), and analyzes control flow. It also processes pragmas (e.g., pragma solidity ^0.8.0;) and library\u00a0linking.<strong>Intermediate Representation (IR) Generation (especially in viaIR mode)<\/strong><br \/>The analyzed code is translated into Yul, a low-level but human-readable intermediate language. Yul uses constructs like functions, if\/switch\/for loops, and variables while abstracting away raw EVM stack manipulation (DUP, SWAP, JUMP).<br \/>Yul serves as a clean target for optimizations and can also be written directly (via inline assembly or standalone Yul contracts).<strong>Optimization<\/strong><br \/>This is one of the most important stages. The optimizer runs at multiple\u00a0levels:<strong>Yul optimizer (powerful in the IR pipeline):<\/strong> <br \/>Applies passes such as common subexpression elimination (CSE), function inlining, dead code elimination, loop-invariant code motion, constant folding, and\u00a0more.<strong>Opcode\/peephole level:<\/strong> Simplifies instruction sequences.Configurable via\u200a\u2014\u200aoptimize (or optimizer.enabled: true) and\u200a\u2014\u200aoptimize-runs (default often 200).<br \/>Low runs values prioritize smaller deployment bytecode (cheaper to deploy). High values prioritize runtime gas efficiency.<\/p>\n<p><strong>5. Code Generation<\/strong><br \/>The optimized representation (Yul or direct) is converted into EVM bytecode. This includes generating function selectors (via keccak256 of signatures), handling storage\/memory\/calldata layouts, events, and constructors. The compiler also appends CBOR-encoded metadata (containing compiler version, settings, and a hash) to the end of the bytecode (unless disabled).<\/p>\n<p><strong>6. Output Generation<\/strong><br \/>The compiler produces all requested artifacts based on the output selection.<\/p>\n<h3>What Does the Compiler Actually Do to Solidity\u00a0Code?<\/h3>\n<p>We can roughly break down the entire process into the following three\u00a0phases:<\/p>\n<p>a) Splits code into\u00a0tokens<\/p>\n<p><em>b) Analyzes\u00a0syntax<\/em><\/p>\n<p><em>c) Constructs an AST (abstract syntax\u00a0tree)<\/em><\/p>\n<h4>What Does the Compiler Do With\u00a0AST?<\/h4>\n<p>Here we can also divide the entire process into three phases, which are listed\u00a0below:<\/p>\n<p><em>a) Analyzes semantics (at this point compiler errors are exposed\/derived!)<\/em><\/p>\n<p><em>b) Optimizes AST (that\u2019s what runs value in the project\u2019s <\/em><a href=\"https:\/\/hardhat.org\/hardhat-runner\/docs\/config#:~:text=%3A%20true%2C-,runs%3A%20200,-%7D%0A%20%20%20%20%7D%0A%20%20%7D\"><em>config<\/em><\/a><em> is specified for\u200a\u2014\u200a<\/em><a href=\"https:\/\/hardhat.org\/hardhat-runner\/docs\/config#:~:text=%3A%20true%2C-,runs%3A%20200,-%7D%0A%20%20%20%20%7D%0A%20%20%7D\"><em>check out this example<\/em><\/a><em>!)<\/em><\/p>\n<p><em>c) Generates bytecode!<\/em><\/p>\n<p>Here is a simple example of some of the compiler\u2019s phases:<\/p>\n<p>Source: My Own Screenshot<\/p>\n<p><strong>The design is driven by fundamental constraints of the\u00a0EVM:<\/strong><\/p>\n<p>EVM is a stack machine with a hard limit of 1024 stack items and expensive operations (especially storage\u00a0writes).High-level abstractions in Solidity (mappings use keccak256 hashing for storage slots, inheritance packs variables according to C3 linearization, structs\/arrays have specific packing rules) must be translated into raw storage slots (0, 1, 2,\u00a0\u2026), memory, and calldata.Gas is money: Every opcode has a cost. The compiler and optimizer exist to minimize both deployment size and execution gas without changing semantics.Security and determinism: The compiler enforces rules (e.g., overflow checks by default since 0.8.0) and produces verifiable output.Yul as IR: It provides a sweet spot\u200a\u2014\u200amore optimizable than raw Solidity but more readable and portable than pure EVM assembly. This enables whole-program optimizations that would be difficult at the opcode\u00a0level.<\/p>\n<h3>Why It Is Important to Understand the\u00a0Compiler<\/h3>\n<p>Many developers treat the compiler as a black box. This leads to suboptimal or insecure contracts. Here\u2019s why deep understanding pays\u00a0off:<\/p>\n<p><strong>1. Gas Optimization (The Biggest Practical Benefit)<\/strong><br \/>The optimizer does a lot automatically, but it cannot fix fundamentally expensive patterns. Understanding compilation helps\u00a0you:<\/p>\n<p>Write code the optimizer loves (e.g., simple expressions, reusable functions).Manually optimize where needed (storage packing, using calldata vs memory, avoiding unnecessary state changes, custom errors instead of require strings).Choose the right optimizer-runs and consider enabling viaIR for better results in many\u00a0cases.Inspect Yul or assembly output to see what the compiler actually generates.<\/p>\n<p><strong>2. Security and Correctness<\/strong><\/p>\n<p>Storage layout collisions are a major risk in upgradeable contracts and inheritance. The compiler\u2019s deterministic layout rules (starting at slot 0, packing rules, keccak for mappings) must be respected.Compiler bugs, while rare, have existed historically. Using recent, audited versions and pinning exact versions (pragma solidity 0.8.XX;) reduces\u00a0risk.Understanding how features compile (e.g., modifiers, inheritance) helps spot subtle\u00a0issues.<\/p>\n<p><strong>3. Debugging and Maintenance<\/strong><br \/>Source maps and assembly output allow stepping through code at the EVM level. When things go wrong on-chain, inspecting bytecode or Yul is often necessary.<\/p>\n<p><strong>4. Contract Verification and Reproducibility<\/strong><br \/>Explorers verify source by recompiling it with the exact compiler version and settings. Mismatched settings produce different bytecode, breaking verification.<\/p>\n<p><strong>5. Advanced Development and\u00a0Tooling<\/strong><\/p>\n<p>Inline assembly\/Yul gives fine-grained control when Solidity abstractions are insufficient.Frameworks (Hardhat, Foundry, etc.) expose compiler settings. Knowing them lets you tune builds effectively.Future-proofing: As EVM evolves (new versions, EOF), the compiler adapts. Understanding pipelines helps adopt improvements.<\/p>\n<p><strong>6. Best Practices<\/strong><br \/>Always:<\/p>\n<p>Pin exact compiler versions.Enable the optimizer (and consider\u00a0viaIR).Review warnings as\u00a0errors.Use the latest stable 0.8.x version for safety features.Inspect storageLayout for complex contracts.<\/p>\n<h3>Gas Optimization &amp; Auditing\u00a0Tips<\/h3>\n<p>Modifiers: each inclusion of <em>`_`<\/em> in a modifier inserts the function body into bytecode. If a function has several modifiers, it may significantly increase the size of the contract and the cost of deployment. If you need to save money, you can combine modifiers or put the checks into a separate function;In older versions of solidity, reading the <em>storage<\/em> length of the array (<em>array.length<\/em>) in the loop condition means reading from storage at each iteration;Sometimes when auditing code, you may notice unnecessary copying (<em>calldata-&gt;storage; calldata-&gt;memory; memory &lt;-&gt; storage<\/em>) when assigning or passing arguments to a function. For example, you should always mark <em>reference-type<\/em> arguments of external functions as <em>calldata<\/em>, not <em>memory<\/em>; sometimes you may even use <em>storage<\/em>references in <em>internal<\/em>\u00a0calls;You can change the order of <em>storage<\/em> variables or fields in a structure somewhere to use <em>storage packing<\/em>, and it will be useful. However, sometimes reading and writing with <em>storage packing<\/em> is not always <a href=\"https:\/\/docs.soliditylang.org\/en\/latest\/internals\/layout_in_storage.html\">cheaper<\/a> than without it. You can save a lot of gas when writing a <em>storage<\/em> array of structures with packed\u00a0fields;In Solidity, some data types have a higher gas cost than others. And that is what is often required of a smart contract developer and that\u2019s why you should understand the <a href=\"http:\/\/trustchain.medium.com\/all-solidity-extreme-gas-optimization-methods-x30-64a3ea113cca\">gas utilization<\/a> of the available data\u00a0types;Custom errors is cheaper to use than revert(\u201cerror text\u201d). There is more information in this article: <a href=\"https:\/\/soliditylang.org\/blog\/2021\/04\/21\/custom-errors\/\">soliditylang.org\/blog\/2021\/04\/21\/custom-errors<\/a>.<\/p>\n<p><strong><em>The integration of your project will be substantially more secure if you implement the below recommendations:<\/em><\/strong><\/p>\n<p>Functions: Internal calls preserve the context (<em>msg.sender, msg.value, etc<\/em>). For example, an internal call to `transfer(\u2026)` inside a token transfers tokens from the address of the caller, not from the balance of the token contract\u00a0itself;Functions: <em>external &gt;\u00a0public<\/em>Variables: Visibility of private and internal does not hide data. Variable values are <a href=\"https:\/\/quillaudits.medium.com\/accessing-private-data-in-smart-contracts-quillaudits-fe847581ce6d\">readable from off-chain<\/a>;Variables: <em>Public<\/em> visibility for variables creates getters and the code of getters takes up space in the contract. When optimizing gas, you can remove <em>Public<\/em> visibility for some variables (unused or already read in some other functions) and thus reduce gas consumption during contract execution;Constants: Magic constants are dangerous, so make full-fledged constants with a normal name. Immutable is also\u00a0ok;Constants: Function signatures should be checked using\u00a0<a href=\"https:\/\/www.4byte.directory\/\">4bytes<\/a>;Ether: <em>payable<\/em>\u200a\u2014\u200acan be redundant (the function can receive ether but does not process\u00a0it);Ether: <em>fallback vs receive;<\/em> <em>receive<\/em> is sufficient for receiving money. It often includes a check that the broadcast comes from one of the addresses (e.g.\u00a0<em>WETH<\/em>);Ether: It is impossible to limit the consumption of ether (<em>selfdestruct, mining<\/em>), so you cannot rely on the exact value of the balance. This is also true for\u00a0tokens;Ether: Three Ether sending options: send, call, transfer:<\/p>\n<p><em>a) Specifics of use <\/em><a href=\"http:\/\/solidity.readthedocs.io\/en\/develop\/units-and-global-variables.html?highlight=transfer#address-related\"><em>address.transfer()<\/em><\/a><em>\u200a\u2014\u200athrows on failure, forwards 2,300 gas stipend (not adjustable), safe against reentrancy, should be used in most cases as it\u2019s the safest way to send\u00a0ether;<\/em><\/p>\n<p><em>b) Specifics of use <\/em><a href=\"http:\/\/solidity.readthedocs.io\/en\/develop\/units-and-global-variables.html?highlight=transfer#address-related\"><em>address.send()<\/em><\/a><em>\u200a\u2014\u200areturns false on failure, returns false on failure, should be used in rare cases when you want to handle failure in the contract;<\/em><\/p>\n<p><em>c) Specifics of use <\/em><a href=\"http:\/\/solidity.readthedocs.io\/en\/develop\/units-and-global-variables.html?highlight=transfer#address-related\"><em>address.call.value().gas()()<\/em><\/a><em>\u200a\u2014\u200areturns false on failure, forwards all available gas (adjustable), not safe against reentrancy, should be used when you need to control how much gas to forward when sending ether or to call a function of another contract;<\/em><\/p>\n<p>It\u2019s bad form to work directly with gas: When working with the <em>tx.gasprice<\/em> variable, it is necessary to remember that its value is set by the user at the moment of transaction execution;It\u2019s bad form to work directly with gas: release of a specific amount of gas through\u00a0<em>.gas(X) \/ {gas:\u00a0X}<\/em><em>.transfer VS\u00a0.send VS\u00a0.call\u200a\u2014\u200acall<\/em> is a good standard, but it brings its own set of problems (reentrancy etc);It is easy to make collisions, for example with arrays: <em>abi.encodePacked([1,2],[3]) == abi.encodePacked([1],[2,3]))<\/em>so check them out carefully. Technically, similar can be done with <em>abi.encode<\/em>, e.g. via <em>structures<\/em>.Use <a href=\"https:\/\/docs.openzeppelin.com\/contracts\/2.x\/api\/math\">SafeMath<\/a> and analogs for Solidity &lt;0.8 (and do not use for more recent ones), keep in mind that <em>a.add(b).mul(c) == (a+b)*c<\/em>\u00a0;Very carefully check all unchecked sections for Solidity &gt;=0.8\u00a0;It is better to do all calculations in the <em>uint256 type<\/em>, because, for example, in the case of <em>`uint256(a) = uint16(b) * uint32(c) \/ uint16(d)`<\/em> the right part may <em>overflow<\/em>, because the intermediate value may not fit into <em>uint32<\/em> (for each operation the maximum of operand types is taken, i.e. the result of multiplication of <em>uint16<\/em> and <em>uint32<\/em> will be\u00a0<em>uint32<\/em>);Type conversion\u200a\u2014\u200aalways check that the number is converted normally. Recommendation: use libraries like <a href=\"https:\/\/docs.openzeppelin.com\/contracts\/3.x\/api\/utils\">SafeCast<\/a>\u00a0;It\u2019s almost always multiplication first, then division;When calculating fractions, don\u2019t forget that you can accidentally get <em>zero<\/em>. e.g. <em>`balanceOf(user) \/ totalSupply() == 0`<\/em>. You need to multiply by a suitable multiplier (<em>often\u00a01e18<\/em>).It is better to put the additional multiplier in a constant like <em>`uint<\/em> <em>constant private HUNDRED_PERCENT=1e18;`<\/em>\u00a0;There are times when you need to calculate the sum of fractions (i.e., the common denominator of all fractions). It is correct to add first, then\u00a0divide;Instead of <em>`a\/b &gt; c\/d`<\/em> it is often better to use <em>`a*d &gt;\u00a0c*b`<\/em>.<\/p>\n<h3>Short Types in Solidity: Rare Tricks Uncovered<\/h3>\n<p>In Solidity, some data types have a higher gas cost than others. And that is what is often required of a smart contract developer and that\u2019s why you should understand the <a href=\"http:\/\/trustchain.medium.com\/all-solidity-extreme-gas-optimization-methods-x30-64a3ea113cca\">gas utilization<\/a> of the available data types;\u200a\u2014\u200ain order to choose the most efficient one according to your\u00a0needs.<\/p>\n<p>For the purposes of this article, we refer to uint8-uint248 and int8-int248 types as \u201cshort\u00a0types\u201d.<\/p>\n<p>Solidity compiler tightly packs short types (if possible) to reduce storage footprint of storage variables, struct fields, array elements;Short types require more opcodes and gas in all other cases (calldata, memory,\u00a0stack).Overflows happen more\u00a0often;Solidity 0.8 and SafeMath do not check for overflows during type\u00a0casts;Short types behave differently for abi.encodePacked and other abi.encode* calls.Solidity ABI encoder puts every value into a separate slot, i.e. the size of calldata or returndata remains the\u00a0same.The compiler often inserts additional opcodes\u200a\u2014\u200ait may inflate the size of the contract.<\/p>\n<h4>I recommend:<\/h4>\n<p>Using short types only to reduce storage footprint;Local variables, return values, function and event parameters should be uint256 or\u00a0int256Converting uint256 values to shorter types right before you write them to storage. Utilize libs like a <a href=\"https:\/\/github.com\/OpenZeppelin\/openzeppelin-contracts\/blob\/master\/contracts\/utils\/math\/SafeCast.sol\">SafeCast<\/a>;\u200a\u2014\u200athey have a pretty good\u00a0syntax;When you read short type value from storage, convert it to uint256 immediatelyUtilizing storage location when possible: function foo(MyStruct storage ms, uint32[] storage array) internal\u00a0{\u2026}Read individual fields or elements if you don\u2019t need the whole structure or array: uint256 value = uint256(ms.field) + uint256(array[i]);<\/p>\n<h3>Conclusion<\/h3>\n<p>The Solidity compiler is far more than a simple translator. It is a sophisticated tool that handles the immense complexity of mapping high-level smart contract logic onto a constrained, gas-metered virtual machine. It performs parsing, semantic analysis, powerful optimizations (especially via Yul in the IR pipeline), and generates critical artifacts like ABI and metadata.<\/p>\n<p>Developers who understand its inner workings like how code becomes bytecode, how storage is laid out, how the optimizer functions, and what outputs it produces will write better contracts. They save on gas, reduce security risks, debug faster, and build more reliable decentralized applications.<\/p>\n<p>In the evolving world of blockchain development (with L2s, account abstraction, and potential EVM upgrades), compiler literacy is no longer optional, it is a core skill for professional Solidity engineers.<\/p>\n<p><strong><em>Master the compiler, and you master the bridge between your ideas and the blockchain.<\/em><\/strong><\/p>\n<p><a href=\"https:\/\/medium.com\/coinmonks\/gas-optimization-auditing-tips-2ab58e0ba67f\">Gas Optimization &amp; Auditing Tips<\/a> was originally published in <a href=\"https:\/\/medium.com\/coinmonks\">Coinmonks<\/a> on Medium, where people are continuing the conversation by highlighting and responding to this story.<\/p>","protected":false},"excerpt":{"rendered":"<p>How Does a Compiler Actually\u00a0Work? Image: Grok\u00a0AI Solidity is the dominant high-level programming language for writing smart contracts on Ethereum and compatible EVM blockchains. However, the Ethereum Virtual Machine (EVM) cannot execute Solidity code directly. It requires low-level bytecode consisting of opcodes and\u00a0data. The Solidity compiler (solc) bridges this gap by translating human-readable Solidity into [&hellip;]<\/p>\n","protected":false},"author":0,"featured_media":195299,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-195298","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-interesting"],"_links":{"self":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/posts\/195298"}],"collection":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=195298"}],"version-history":[{"count":0,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/posts\/195298\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/media\/195299"}],"wp:attachment":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=195298"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=195298"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=195298"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}