{"id":22399916,"url":"https://github.com/laugharne/optimal_function_names_en","last_synced_at":"2025-10-15T17:30:43.642Z","repository":{"id":213921591,"uuid":"734779385","full_name":"Laugharne/Optimal_Function_Names_en","owner":"Laugharne","description":"Optimizing gas costs is a key challenge in the development of smart contracts on the Ethereum blockchain, as every operation carried out on Ethereum incurs a gas cost.","archived":false,"fork":false,"pushed_at":"2024-01-24T15:18:13.000Z","size":495,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-12-05T08:11:44.502Z","etag":null,"topics":["abi","assembly","bytecode","function-dispatcher","gas","gas-optimization","keccak-256","keccak256","optimization","solidity","yul","yul-assembly"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Laugharne.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-12-22T15:40:34.000Z","updated_at":"2024-04-14T16:14:06.000Z","dependencies_parsed_at":"2023-12-24T10:25:48.678Z","dependency_job_id":"b472bdb2-b8ad-47f7-8579-aec7dd64f920","html_url":"https://github.com/Laugharne/Optimal_Function_Names_en","commit_stats":null,"previous_names":["laugharne/optimal_function_names_en"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Laugharne%2FOptimal_Function_Names_en","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Laugharne%2FOptimal_Function_Names_en/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Laugharne%2FOptimal_Function_Names_en/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Laugharne%2FOptimal_Function_Names_en/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Laugharne","download_url":"https://codeload.github.com/Laugharne/Optimal_Function_Names_en/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":236624389,"owners_count":19178981,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["abi","assembly","bytecode","function-dispatcher","gas","gas-optimization","keccak-256","keccak256","optimization","solidity","yul","yul-assembly"],"created_at":"2024-12-05T08:10:31.246Z","updated_at":"2025-10-15T17:30:38.169Z","avatar_url":"https://github.com/Laugharne.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Optimization on Ethereum: Make a Difference with Function Names \n\n\u003c!-- TOC --\u003e\n\n- [Optimization on Ethereum: Make a Difference with Function Names](#optimization-on-ethereum-make-a-difference-with-function-names)\n\t- [TL;DR](#tldr)\n\t- [Introduction](#introduction)\n\t- [Selectors and signatures](#selectors-and-signatures)\n\t- [Introduction to the \"Function Dispatcher\"](#introduction-to-the-function-dispatcher)\n\t\t- [Operation](#operation)\n\t\t- [In Solidity](#in-solidity)\n\t\t\t- [Reminder on Solidity Function Visibilities](#reminder-on-solidity-function-visibilities)\n\t\t\t- [During Compilation](#during-compilation)\n\t\t\t\t- [Generated Code](#generated-code)\n\t\t\t\t- [Diagram](#diagram)\n\t\t\t\t- [Evaluation Order](#evaluation-order)\n\t\t\t\t- [Automatic Getter](#automatic-getter)\n\t\t- [In Yul](#in-yul)\n\t- [An increasing Complexity!](#an-increasing-complexity)\n\t\t- [Influence of the Runs Level](#influence-of-the-runs-level)\n\t\t- [Eleven Functions and a Thousand Runs](#eleven-functions-and-a-thousand-runs)\n\t\t- [Pseudo-code](#pseudo-code)\n\t\t- [Gas Cost Calculation](#gas-cost-calculation)\n\t\t- [Consumption Statistics](#consumption-statistics)\n\t- [Algorithms and Processing Order](#algorithms-and-processing-order)\n\t\t- [Linear Search runs = 200](#linear-search-runs--200)\n\t\t- [Fractional Search runs = 1000](#fractional-search-runs--1000)\n\t- [Optimizations](#optimizations)\n\t\t- [Execution Cost Optimization](#execution-cost-optimization)\n\t\t- [Intrinsic Cost Optimization](#intrinsic-cost-optimization)\n\t\t\t- [Examples of Gains on Intrinsic Costs:](#examples-of-gains-on-intrinsic-costs)\n\t- [Select0r](#select0r)\n\t- [Conclusions](#conclusions)\n\t- [Additional resources](#additional-resources)\n\n\u003c!-- /TOC --\u003e\n\n\n## TL;DR\n\n1. *Cost optimization in gas is crucial for smart contracts on Ethereum.*\n2. *The \"function dispatcher\" manages the execution of functions in smart contracts for EVMs.*\n3. *Solidity compiler generates the \"function dispatcher\" for publicly exposed functions, whereas in Yul, it needs to be coded.*\n4. *Signatures, hashes, and footprints of functions are determined by their names and parameter types.*\n5. *The compiler's optimization setting and the number of functions impact the function selection algorithm.*\n6. *Strategic renaming of functions optimizes gas costs and the selection order, influenced by footprint values.*\n\n\n\n## Introduction\n\nCost optimization in gas is a key challenge in the development of smart contracts on the Ethereum blockchain, as each operation on Ethereum incurs a gas cost. This article is the translation of [Optimisation sur Ethereum : Faites la différence avec les noms de fonctions](https://medium.com/@franck.maussand/optimisation-sur-ethereum-faites-la-diff%C3%A9rence-avec-les-noms-de-fonctions-ba4692c9e39f) (🇫🇷).\n\n**Reminder :**\n\n- The **bytecode** represents a smart contract on the blockchain as a sequence of hexadecimal values.\n- The Ethereum Virtual Machine (**EVM**) executes instructions by reading this bytecode during interactions with the contract.\n- Each elementary instruction, encoded in one byte, is called an **opcode** and has a gas cost reflecting the resources required for its execution.\n- A compiler translates this source code into bytecode executable by the EVM and provides elements such as the Application Binary Interface (ABI).\n- An **ABI** defines how a contract's functions should be called and data exchanged, specifying the data types of arguments and the functions' signatures.\n\nIn this article, we will explore how simply naming your functions can influence the gas costs associated with your contract.\n\nWe will also discuss various optimization strategies, from the order of signature hashes to function renaming tricks, to reduce costs associated with interactions with your contracts.\n\n**Details :**\n\nThis article is based on:\n1. **Solidity** code (0.8.13, 0.8.17, 0.8.20, 0.8.22)\n2. Compiled using the `solc` compiler\n3. For **EVMs** on **Ethereum**\n\nThe following concepts will be covered:\n- The signature: the numerical identifier of a function within the EVM.\n- The \"*function dispatcher*\": the mechanism for selecting a function within a contract.\n- And the function name as an argument (on the caller side).\n\n\n## Selectors and signatures\n\nThe **signature** of a function as used with the Ethereum Virtual Machines (**EVMs**) (Solidity) consists of the concatenation of its name and parameter types (excluding return type and spaces).\n\nThe **function selector** is the unique identifier for the function. In Solidity, this involves the 4 most significant bytes (32 bits) of the result of hashing the function's signature with the [**Keccak-256 algorithm**](https://www.geeksforgeeks.org/difference-between-sha-256-and-keccak-256/).\n\nThis is based on the [**Solidity ABI specifications**](https://docs.soliditylang.org/en/develop/abi-spec.html#function-selector).\n\nI would like to emphasize again that I am referring to the function selector for the **solc compiler for Solidity**, and this might not be the case for other languages like **Rust**, which operates on a completely different paradigm.\n\nConsidering parameter types is essential to differentiate functions with the same name but different parameters, as seen in the `safeTransferFrom` method of [**ERC721 tokens**](https://eips.ethereum.org/EIPS/eip-721).\n\nHowever, the fact that only **four bytes** are retained for the function selector implies potential **hash collision risks** between two functions—a rare but existing risk despite over 4 billion possibilities (2^32).\n\nAs evidenced by the [**Ethereum Signature Database**](https://www.4byte.directory/signatures/?bytes4_signature=0xcae9ca51) with the following example:\n\n\n| Function selectors | Signatures                                                   |\n| ------------------ | ------------------------------------------------------------ |\n| `0xcae9ca51`       | `onHintFinanceFlashloan(address,address,uint256,bool,bytes)` |\n| `0xcae9ca51`       | `approveAndCall(address,uint256,bytes)`                      |\n\nFortunately, a simple Solidity contract with these two functions does not compile.\n\n```\nTypeError: Function signature hash collision for approveAndCall(address,uint256,bytes)\n  --\u003e contracts/HashCollision.sol:10:1:\n   |\n10 | contract HashCollision {\n   | ^ (Relevant source part starts here and spans across multiple lines).\n```\n\nHowever, this remains problematic: Check out the challenge [**Hint-finance**](https://github.com/paradigmxyz/paradigm-ctf-2022/tree/main/hint-finance), in the [**Web3 Hacking: Paradigm CTF 2022**](https://medium.com/amber-group/web3-hacking-paradigm-ctf-2022-writeup-3102944fd6f5).\n\n\n## Introduction to the \"Function Dispatcher\"\n\nThe \"Function Dispatcher\" (or function manager) in smart contracts written for the **EVMs** is a component of the contract that determines which function should be executed when someone interacts with the contract through an ABI.\n\nIn essence, the \"Function Dispatcher\" is like a conductor during calls to the functions of a smart contract. It ensures that the right functions are called when you perform specific actions on the contract.\n\n\n### Operation\n\nWhen interacting with a smart contract through a transaction, you specify which function you want to execute. The \"*function dispatcher*\" thus links the command to the specific function that will be called.\n\nThe function's signature is retrieved from the `calldata` during contract execution, and a `revert` occurs if the call cannot be matched with a function of the contract.\n\nThe selection mechanism is similar to that of a `switch/case` structure or a set of `if/else` statements.\n\n\n### In Solidity\n\nApplying what has been discussed above, we obtain, for the following function:\n\n```solidity\nfunction square(uint32 num) public pure returns (uint32) {\n    return num * num;\n}\n```\n\nThe following signatures, hash, and selectors :\n\n| Fonction  | square(uint32 num) public pure returns (uint32)                    |\n| --------- | ------------------------------------------------------------------ |\n| Signature | `square(uint32)` (*1*)                                             |\n| Hash      | `d27b38416d4826614087db58e4ea90ac7199f7f89cb752950d00e21eb615e049` |\n| Selector  | `d27b3841`                                                         |\n\n(*1*) : *Keccak-256 online calculator : [`square(uint32)`](https://emn178.github.io/online-tools/keccak_256.html?input_type=utf-8\u0026input=square(uint32))*\n\nIn Solidity, the \"*function dispatcher*\" is generated by the compiler, so there's no need to handle the coding of this complex task.\n\nIt only applies to functions in a contract that are accessible from outside the contract, thus having an access attribute of external and public.\n\n\n#### Reminder on Solidity Function Visibilities\n\n1. **External**: External functions are designed to be called from **outside the contract**, typically by other contracts or external accounts. It is the visibility to expose a public interface to your contract.\n\n2. **Public**: Public functions are accessible from **both outside and inside the contract**.\n\n3. **Internal** and **Private**: Internal and private functions can only be called from **inside the contract** (and contracts inheriting from it in the case of internal).\n\n**Example #1**:\n\n```solidity\npragma solidity 0.8.13;\n\ncontract MyContract {\n    uint256 public value;\n    uint256 internalValue;\n\n    function setValue(uint256 _newValue) external {\n        value = _newValue;\n    }\n\n    function getValue() public view returns (uint256) {\n        return value;\n    }\n\n    function setInternalValue(uint256 _newValue) internal {\n        internalValue = _newValue;\n    }\n\n    function getInternalValue() public view returns (uint256) {\n        return internalValue;\n    }\n}\n```\n\n#### During Compilation\n\nIf we revisit the previous code used as an example, we obtain the following signatures and footprints:\n\n| Fonctions                                              | Signatures                  | Keccak            | Selectors      |\n| ------------------------------------------------------ | --------------------------- | ----------------- | -------------- |\n| **`setValue(uint256 _newValue) external`**             | `setValue(uint256)`         | `55241077...ecbd` | **`55241077`** |\n| **`getValue() public view returns (uint256)`**         | `getValue()`                | `20965255...ad96` | **`20965255`** |\n| **`setInternalValue(uint256 _newValue) internal`**     | `setInternalValue(uint256)` | `6115694f...7ce1` | **`6115694f`** |\n| **`getInternalValue() public view returns (uint256)`** | `getInternalValue()`        | `e778ddc1...c094` | **`e778ddc1`** |\n\n(*The hashes from Keccak have been intentionally truncated*)\n\nIf we examine the ABI generated during compilation, the function `setInternalValue()` does not appear, which is expected as its visibility is `internal` (see above).\n\nIt is noteworthy in the ABI data, the reference to the `value` storage data, which is `public` (we will come back to this later).\n\n\n##### Generated Code\n\nHere is an excerpt of the \"*function dispatcher*\" code generated by the `solc` compiler (Solidity version: 0.8.13). It can be observed that the numerical value of the fingerprint is retrieved from the `calldata`, and this value is then compared to the different function signatures, allowing for a \"jump\" to the code of the desired function.\n\n```assembly\ntag 1\n  JUMPDEST \n  POP \n  PUSH 4\n  CALLDATASIZE \n  LT \n  PUSH [tag] 2\n  JUMPI \n  PUSH 0\n  CALLDATALOAD \n  PUSH E0\n  SHR \n  DUP1 \n  PUSH 20965255  // ◄ signature : getValue()\n  EQ \n  PUSH [tag] getValue_0\n  JUMPI \n  DUP1 \n  PUSH 3FA4F245  // ◄ signature : value (automatic storage getters)\n  EQ \n  PUSH [tag] 4\n  JUMPI \n  DUP1 \n  PUSH 55241077  // ◄ signature : setValue(uint256)\n  EQ \n  PUSH [tag] setValue_uint256_0\n  JUMPI \n  DUP1 \n  PUSH E778DDC1  // ◄ signature : getInternalValue()\n  EQ \n  PUSH [tag] getInternalValue_0\n  JUMPI \ntag 2\n  JUMPDEST \n  PUSH 0\n  DUP1 \n  REVERT\n```\n\n##### Diagram\n\nIn diagram form, one can better understand the selection mechanism, similar to that of a `switch/case` structure or a set of `if/else` statements.\n\n![](assets/functions_dispatcher_diagram.png)\n\u003c!-- ![](assets/functions_dispatcher_diagram.svg) --\u003e\n\n\n##### Evaluation Order\n\n**Important**: The evaluation order of functions is not the same as their declaration order in the code!\n\n| Evaluation Order | Order in the code  | Selectors   | Signatures                   |\n| ---------------- | ------------------ | ----------- | ---------------------------- |\n| 1                | **3**              | `20965255`  | `getValue()`                 |\n| 2                | **1**              | `3FA4F245`  | `value` (*automatic getter*) |\n| 3                | **2**              | `55241077`  | `setValue(uint256)`          |\n| 4                | **4**              | `E778DDC1`  | `getInternalValue()`         |\n\nIndeed, function signature evaluations are ordered by an ascending sort on their values.\n\n`20965255` \u003c `3FA4F245` \u003c `55241077` \u003c `E778DDC1`\n\n\n##### Automatic Getter\n\nThe function with the selector `3FA4F245` is actually an automatic **getter** for the public data `value`, and it is generated by the compiler. In Solidity, the compiler automatically provides a public getter for any public storage variable.\n\n```solidity\n  uint256 public value;\n```\n\nWe can find the selection footprint (`3FA4F245`) and the function (at `tag 4` address) of the automatic getter for this variable in the opcodes.\n\n**Selector** :\n```assembly\n  DUP1 \n  PUSH 3FA4F245  \n  EQ \n  PUSH [tag] 4\n  JUMPI \n```\n\n**Fonction** :\n```assembly\ntag 4\n  JUMPDEST \n  PUSH [tag] 11\n  PUSH [tag] 12\n  JUMP [in]\ntag 11\n  JUMPDEST \n  PUSH 40\n  MLOAD \n  PUSH [tag] 13\n  SWAP2 \n  SWAP1 \n  PUSH [tag] abi_encode_tuple_t_uint256__to_t_uint256__fromStack_reversed_0\n  JUMP [in]\ntag 13\n  JUMPDEST \n  PUSH 40\n  MLOAD \n  DUP1 \n  SWAP2 \n  SUB \n  SWAP1 \n  RETURN\n```\n\n\"`getter` actually has the same code as the `getValue()` function.\"\n\n```assembly\ntag getValue_0\n  JUMPDEST \n  PUSH [tag] getValue_1\n  PUSH [tag] getValue_3\n  JUMP [in]\ntag getValue_1\n  JUMPDEST \n  PUSH 40\n  MLOAD \n  PUSH [tag] getValue_2\n  SWAP2 \n  SWAP1 \n  PUSH [tag] abi_encode_tuple_t_uint256__to_t_uint256__fromStack_reversed_0\n  JUMP [in]\ntag getValue_2\n  JUMPDEST \n  PUSH 40\n  MLOAD \n  DUP1 \n  SWAP2 \n  SUB \n  SWAP1 \n  RETURN \n```\n\nDemonstrating the futility of having the variable `value` with the `public` attribute in conjunction with the `getValue()` function, and also highlighting a weakness in the Solidity compiler `solc` that cannot merge the code of the two functions.\n\nFor those interested in delving deeper, here is a link to [**a detailed article**](https://medium.com/coinmonks/soliditys-cheap-public-face-b4e972e3924d) on `automatic storage getters` in Solidity.\n\n\n### In Yul\n\nHere is an excerpt from an example of a [**ERC20 contract**](https://docs.soliditylang.org/en/develop/yul.html#complete-erc20-example) entirely written in **Yul**.\n\nWhile **Solidity** provides abstraction and readability, **Yul**, a lower-level language close to assembly, allows for much finer control over execution.\n\n```yul\nobject \"runtime\" {\n    code {\n        // Protection against sending Ether\n        require(iszero(callvalue()))\n\n        // Dispatcher\n        switch selector()\n        case 0x70a08231 /* \"balanceOf(address)\" */ {\n            returnUint(balanceOf(decodeAsAddress(0)))\n        }\n        case 0x18160ddd /* \"totalSupply()\" */ {\n            returnUint(totalSupply())\n        }\n        case 0xa9059cbb /* \"transfer(address,uint256)\" */ {\n            transfer(decodeAsAddress(0), decodeAsUint(1))\n            returnTrue()\n        }\n        case 0x23b872dd /* \"transferFrom(address,address,uint256)\" */ {\n            transferFrom(decodeAsAddress(0), decodeAsAddress(1), decodeAsUint(2))\n            returnTrue()\n        }\n        case 0x095ea7b3 /* \"approve(address,uint256)\" */ {\n            approve(decodeAsAddress(0), decodeAsUint(1))\n            returnTrue()\n        }\n        case 0xdd62ed3e /* \"allowance(address,address)\" */ {\n            returnUint(allowance(decodeAsAddress(0), decodeAsAddress(1)))\n        }\n        case 0x40c10f19 /* \"mint(address,uint256)\" */ {\n            mint(decodeAsAddress(0), decodeAsUint(1))\n            returnTrue()\n        }\n        default {\n            revert(0, 0)\n        }\n\n        /* ---------- calldata decoding functions ----------- */\n        function selector() -\u003e s {\n            s := div(calldataload(0), 0x100000000000000000000000000000000000000000000000000000000)\n        }\n\n  ...\n\n```\n\nIt features the same cascading `if/else` structure as in the previous diagram.\n\nCreating a contract **entirely in Yul** requires coding the \"*function dispatcher*\" manually, allowing one to choose the order of processing imprints and utilize algorithms beyond a simple cascading test suite.\n\n\n## An increasing Complexity!\n\nNow, here's a completely different example to illustrate that things are actually more complex!\n\nBecause depending on the **number of functions** and the **optimization level** (see: `--optimize-runs`), the Solidity compiler behaves differently!\n\n**Example #2:**\n\n```solidity\n// SPDX-License-Identifier: GPL-3.0\n\npragma solidity 0.8.17;\n\ncontract Storage {\n\n    uint256 numberA;\n    uint256 numberB;\n    uint256 numberC;\n    uint256 numberD;\n    uint256 numberE;\n\n\n    // selector : C534BE7A\n    function storeA(uint256 num) public {\n        numberA = num;\n    }\n\n    // selector : 9AE4B7D0\n    function storeB(uint256 num) public {\n        numberB = num;\n    }\n\n    // selector : 4CF56E0C\n    function storeC(uint256 num) public {\n        numberC = num;\n    }\n\n    // selector : B87C712B\n    function storeD(uint256 num) public {\n        numberD = num;\n    }\n\n    // selector : E45F4CF5\n    function storeE(uint256 num) public {\n        numberE = num;\n    }\n\n    // selector : 2E64CEC1\n    function retrieve() public view returns (uint256) {\n        return Multiply( numberA, numberB, numberC, numberD);\n    }\n\n}\n```\n\nHere, the `storage` variables are `internal` (default attribute in Solidity), so no automatic getter will be added by the compiler.\n\nAnd we indeed have 6 functions listed in the ABI JSON. The **6 following `public` functions** with their dedicated signatures:\n\n| Fonctions                                      | Signatures        | Selectors      |\n| ---------------------------------------------- | ----------------- | -------------- |\n| **`storeA(uint256 num) public`**               | `storeA(uint256)` | **`C534BE7A`** |\n| **`storeB(uint256 num) public`**               | `storeB(uint256)` | **`9AE4B7D0`** |\n| **`storeC(uint256 num) public`**               | `storeC(uint256)` | **`4CF56E0C`** |\n| **`storeD(uint256 num) public`**               | `storeD(uint256)` | **`B87C712B`** |\n| **`storeE(uint256 num) public`**               | `storeE(uint256)` | **`E45F4CF5`** |\n| **`retrieve() public view returns (uint256)`** | `retrieve()`      | **`2E64CEC1`** |\n\nBased on the [**optimization level**](https://docs.soliditylang.org/en/develop/internals/optimizer.html) of the compiler, we get a different code for the \"*function dispatcher*\".\n\nWith a level of **200** (`--optimize-runs 200`), we obtain the type of code generated previously, with its cascading `if/else` statements.\n\n```assembly\ntag 1\n  JUMPDEST \n  POP \n  PUSH 4\n  CALLDATASIZE \n  LT \n  PUSH [tag] 2\n  JUMPI \n  PUSH 0\n  CALLDATALOAD \n  PUSH E0\n  SHR \n  DUP1 \n  PUSH 2E64CEC1\n  EQ \n  PUSH [tag] retrieve_0\n  JUMPI \n  DUP1 \n  PUSH 4CF56E0C\n  EQ \n  PUSH [tag] storeC_uint256_0\n  JUMPI \n  DUP1 \n  PUSH 9AE4B7D0\n  EQ \n  PUSH [tag] storeB_uint256_0\n  JUMPI \n  DUP1 \n  PUSH B87C712B\n  EQ \n  PUSH [tag] storeD_uint256_0\n  JUMPI \n  DUP1 \n  PUSH C534BE7A\n  EQ \n  PUSH [tag] storeA_uint256_0\n  JUMPI \n  DUP1 \n  PUSH E45F4CF5\n  EQ \n  PUSH [tag] storeE_uint256_0\n  JUMPI \n  PUSH 0\n  DUP1\n  REVERT\n```\n\nHowever, with a higher level of `runs` (`--optimize-runs 300`)\n\n```assembly\ntag 1\n  JUMPDEST\n  POP\n  PUSH 4\n  CALLDATASIZE\n  LT\n  PUSH [tag] 2\n  JUMPI\n  PUSH 0\n  CALLDATALOAD\n  PUSH E0\n  SHR\n  DUP1\n  PUSH B87C712B\n  GT\n  PUSH [tag] 9\n  JUMPI\n  DUP1\n  PUSH B87C712B\n  EQ\n  PUSH [tag] storeD_uint256_0\n  JUMPI\n  DUP1\n  PUSH C534BE7A\n  EQ\n  PUSH [tag] storeA_uint256_0\n  JUMPI\n  DUP1\n  PUSH E45F4CF5\n  EQ\n  PUSH [tag] storeE_uint256_0\n  JUMPI\n  PUSH 0\n  DUP1\n  REVERT\ntag 9\n  JUMPDEST\n  DUP1\n  PUSH 2E64CEC1\n  EQ\n  PUSH [tag] retrieve_0\n  JUMPI\n  DUP1\n  PUSH 4CF56E0C\n  EQ\n  PUSH [tag] storeC_uint256_0\n  JUMPI\n  DUP1\n  PUSH 9AE4B7D0\n  EQ\n  PUSH [tag] storeB_uint256_0\n  JUMPI\ntag 2\n  JUMPDEST\n  PUSH 0\n  DUP1\n  REVERT\n```\n\nThe opcodes and the execution flow with `--optimize-runs 300` are no longer the same, as shown in the following diagram.\n\n![](assets/functions_split_dispatcher_diagram.png)\n\nIt can be observed that the tests are \"split\" into two linear searches around a pivot value `B87C712B`, thereby reducing consumption for the less favorable cases of `storeB(uint256)` and `storeE(uint256)`.\n\n\n### Influence of the Runs Level\n\nOnly **4 tests** for the functions `storeB(uint256)` and `storeE(uint256)`, instead of, respectively, **3 tests** and **6 tests** with the previous algorithm.\n\nDetermining the trigger for this type of optimization is more delicate; for example, the threshold for the number of functions happens to be 6 to trigger it with `--optimize-runs 284`, providing **two sets** of 3 linear test series.\n\nWhen the number of functions is less than 4, the selection process is done through linear search. However, with five or more functions, the compiler splits the processing based on its optimization parameter.\n\n[Tests on basic contracts](https://github.com/Laugharne/solc_runs_dispatcher) with 4 to 15 functions, using optimizations from 200 to 1000 executions, have demonstrated these thresholds.\n\nThe following table (resulting from these tests) shows the number of splits, indicating the number of linear searches.\n\n**Record of the number of linear sequences based on runs level and the number of functions**\n\n![](assets/func_runs.png)\n\n( *F : Number of functions / R : Runs level* )\n\nAre these thresholds (associated with `runs` values) likely to evolve with subsequent versions of the `solc` compiler?\n\n\n### Eleven Functions and a Thousand Runs\n\nLet's delve into an example for a contract with 11 functions to visualize the impact on gas consumption.\n\nWith **11 eligible functions** and a higher `runs` level of `--optimize-runs 1000`, we transition from **two ranges** (one of 6 + one of 5) to **four ranges** (three of 3 + one of 2).\n\n### Pseudo-code\n\nThis time, I won't reproduce the opcodes and the associated diagram. To clarify the explanation, here is the execution flow in the form of *pseudo-code*, resembling code in the **C** language.\n\n```c\n// [tag 1]\n// 1 gas (JUMPDEST)\nif( selector \u003e= 0x799EBD70) {  // 22 = (3+3+3+3+10) gas\n  if( selector \u003e= 0xB9E9C35C) {  // 22 = (3+3+3+3+10) gas\n    if( selector == 0xB9E9C35C) { goto storeF }  // 22 = (3+3+3+3+10) gas\n    if( selector == 0xC534BE7A) { goto storeA }  // 22 = (3+3+3+3+10) gas\n    if( selector == 0xE45F4CF5) { goto storeE }  // 22 = (3+3+3+3+10) gas\n    revert()\n  }\n  // [tag 15]\n  // 1 gas (JUMPDEST)\n  if( selector == 0x799EBD70) { goto storeG }  // 22 = (3+3+3+3+10) gas\n  if( selector == 0x9AE4B7D0) { goto storeB }  // 22 = (3+3+3+3+10) gas\n  if( selector == 0xB87C712B) { goto storeD }  // 22 = (3+3+3+3+10) gas\n  revert()\n} else {\n  // [tag 14]\n  // 1 gas (JUMPDEST)\n  if( selector \u003e= 0x4CF56E0C) { // 22 = (3+3+3+3+10) gas\n    if( selector == 0x4CF56E0C) { goto storeC }  // 22 = (3+3+3+3+10) gas\n    if( selector == 0x6EC51CF6) { goto storeJ }  // 22 = (3+3+3+3+10) gas\n    if( selector == 0x75A64B6D) { goto storeH }  // 22 = (3+3+3+3+10) gas\n    revert()\n  }\n  // [tag 16]\n  // 1 gas (JUMPDEST)\n  if( selector == 0x183301E7) { goto storeI }    // 22 = (3+3+3+3+10) gas\n  if( selector == 0x2E64CEC1) { goto retrieve }  // 22 = (3+3+3+3+10) gas\n  revert()\n}\n```\n\nThe joints around the different \"pivot\" values are more clearly distinguished:\n- With `799EBD70` as the **first pivot value**.\n- Then `0x4CF56E0C` \u0026 `0xB9E9C35C` as **secondary pivot values**.\n\n\n### Gas Cost Calculation\n\nI used the code of a Solidity contract with **11 eligible functions** for the \"*function dispatcher*\" as a reference to estimate the gas cost of the selection, depending on whether it's a linear or fractioned search.\n\nIt's only the **cost of selection** in the \"*function dispatcher*\" and not the execution of functions that is estimated. We don't concern ourselves with what the function does or how much gas it consumes, nor with the code that extracts the function's signature by fetching data from the `calldata` area.\n\nThe estimation of gas costs for the used opcodes was done with the assistance of the following sites:\n- [**Ethereum Yellow Paper**](https://ethereum.github.io/yellowpaper/paper.pdf) (Berlin version)\n- [**EVM Codes - An Ethereum Virtual Machine Opcodes Interactive Reference**](https://www.evm.codes/?fork=shanghai)\n\nThe relevant **opcodes** in play for our purposes are as follows:\n\n| Mnemonic           | Gas | Description                             |\n| ------------------ | --- | --------------------------------------- |\n| `JUMPDEST`         | 1   | Mark valid jump destination.            |\n| `DUP1`             | 3   | Clone 1st value on stack                |\n| `PUSH4 0xXXXXXXXX` | 3   | Push 4-byte value onto stack.           |\n| `GT`               | 3   | Greater-than comparison.                |\n| `EQ`               | 3   | Equality comparison.                    |\n| `PUSH [tag]`       | 3   | Push 2-byte value onto stack.           |\n| `JUMPI`            | 10  | Conditionally alter the program counter |\n\n\nThis allowed me to estimate the gas search costs for each function, for the [threshold values](#influence-of-the-runs-level) of `200` and `1000` runs, thus leading to different processing, sequential for `200 runs` and \"fractionated\" for `1000 runs`.  \n\n| Signatures        | Selectors        | Gas (linear)    | Gas (splited)   |\n| ----------------- | ---------------- | --------------- | --------------- |\n| `storeI(uint256)` | `183301E7`       | **22 (*min*)**  | 69              |\n| `retrieve()`      | `2E64CEC1`       | 44              | 91              |\n| `storeC(uint256)` | `4CF56E0C` (*2*) | 66              | 69              |\n| `storeJ(uint256)` | `6EC51CF6`       | 88              | 90              |\n| `storeH(uint256)` | `75A64B6D`       | 110             | **112 (*max*)** |\n| `storeG(uint256)` | `799EBD70` (*1*) | 132             | 68              |\n| `storeB(uint256)` | `9AE4B7D0`       | 154             | 90              |\n| `storeD(uint256)` | `B87C712B`       | 176             | **112 (*max*)** |\n| `storeF(uint256)` | `B9E9C35C` (*2*) | 198             | **67 (*min*)**  |\n| `storeA(uint256)` | `C534BE7A`       | 220             | 89              |\n| `storeE(uint256)` | `E45F4CF5`       | **242 (*max*)** | 111             |\n\n- (*1*): *First pivot value for 1000 runs*\n- (*2*): *Secondary pivot values for 1000 runs*\n\n\n### Consumption Statistics\n\nIf we take a closer look at the results of certain **statistics** on both types of search.\n\n| \\          | Linear | Fractional |\n| ---------- | ------ | ---------- |\n| Min        | **22** | 67         |\n| Max        | 242    | **112**    |\n| Average    | 132    | **88**     |\n| Deviation  | 72,97  | **18,06**  |\n\nWe observe significant differences. Specifically, a lower **average** (-33%) with a considerably lower [**standard deviation**](https://en.wikipedia.org/wiki/Standard_deviation) of consumptions (4 times less) in favor of the fractional processing.\n\n\n## Algorithms and Processing Order\n\nDepending on the algorithm used by the Solidity compiler to generate the \"*function dispatcher*,\" the processing order of functions will differ, both from the order of declaration in the source code and from the alphabetical order.\n\n\n### Linear Search (runs = 200)\n\n| #      | Signatures        | Selectors  |\n| ------ | ----------------- | ---------- |\n| **1**  | `storeI(uint256)` | `183301E7` |\n| **2**  | `retrieve()`      | `2E64CEC1` |\n| **3**  | `storeC(uint256)` | `4CF56E0C` |\n| **4**  | `storeJ(uint256)` | `6EC51CF6` |\n| **5**  | `storeH(uint256)` | `75A64B6D` |\n| **6**  | `storeG(uint256)` | `799EBD70` |\n| **7**  | `storeB(uint256)` | `9AE4B7D0` |\n| **8**  | `storeD(uint256)` | `B87C712B` |\n| **9**  | `storeF(uint256)` | `B9E9C35C` |\n| **10** | `storeA(uint256)` | `C534BE7A` |\n| **11** | `storeE(uint256)` | `E45F4CF5` |\n\n\nThe number of tests and the complexity of the process are proportional to the number of functions, in [**O(n)**](https://en.wikipedia.org/wiki/Time_complexity#List_of_time_complexities).\n\n\n### Fractional Search (runs = 1000)\n\n| #      | Signatures        | Selectors  |\n| ------ | ----------------- | ---------- |\n| **1**  | `storeF(uint256)` | `B9E9C35C` |\n| **2**  | `storeG(uint256)` | `799EBD70` |\n| **3**  | `storeI(uint256)` | `183301E7` |\n| **4**  | `storeC(uint256)` | `4CF56E0C` |\n| **5**  | `storeA(uint256)` | `C534BE7A` |\n| **6**  | `storeJ(uint256)` | `6EC51CF6` |\n| **7**  | `storeB(uint256)` | `9AE4B7D0` |\n| **8**  | `retrieve()`      | `2E64CEC1` |\n| **9**  | `storeE(uint256)` | `E45F4CF5` |\n| **10** | `storeH(uint256)` | `75A64B6D` |\n| **11** | `storeD(uint256)` | `B87C712B` |\n\n\nThis is not a [binary search](https://en.wikipedia.org/wiki/Binary_search_algorithm) in the strict sense of the term but rather a segmentation into groups of sequential tests around pivot values. However, in the end, the complexity is the same, in [O(log n)](https://en.wikipedia.org/wiki/Time_complexity#Table_of_common_time_complexities).\n\n## Optimizations\n\nIf we assume that functions are called fairly (at the same frequency of use), their calls will not cost the same based on their signatures (and therefore their names). It's clear that the cost of selecting a call to these functions, regardless of the algorithm, is highly heterogeneous, and while it can be estimated, it cannot be imposed.\n\nHowever, by strategically renaming functions, adding suffixes, for example, you can influence the results of function signatures and, consequently, the gas costs associated with these functions. This practice can optimize gas consumption in your smart contract, not only during function selection in the EVM but also, as we will see later, during transactions.\n\nThe cost of a transaction consists of two parts: the **intrinsic cost** (including those related to the useful data of transactions) and the **execution cost**. Our optimizations focus on these two costs.\n\nYou can find more information on the breakdown of transaction costs on [this page](https://www.lucassaldanha.com/transaction-execution-ethereum-yellow-paper-walkthrough-4-7/).\n\nThe combination of these two optimization approaches makes a **significant difference** by reducing gas consumption in smart contracts. This is particularly crucial in certain areas such as MEV (arbitrage) where optimization is vital.\n\n\n### Execution Cost Optimization\n\nTo illustrate, modifying the function signature `square(uint32)` to `square_low(uint32)` changes the fingerprint to `bde6cad1` instead of `d27b3841`.\n\nThe lower value of the new fingerprint will prioritize the processing of calls to this function. This optimization can be crucial for highly complex smart contracts, reducing the time needed to search and select the correct function to call, resulting in gas savings and improved performance on the Ethereum blockchain.\n\nThe fact that the search is fractionated rather than linear complicates matters a bit. Depending on the number of functions and the compiler's optimization level, threshold values are more challenging to determine to choose new signatures based on the desired order.\n\n### Intrinsic Cost Optimization\n\nWhen you send a transaction on the Ethereum blockchain, you include data specifying which function of the smart contract you want to call and what the arguments of that function are. The gas cost of a transaction partly depends on the number of zero bytes in the transaction data.\n\nAs specified in the [**Ethereum Yellow Paper**](https://ethereum.github.io/yellowpaper/paper.pdf) (Berlin version),\n\n![](assets/g_tx_data.png)\n\n- `Gtxdatazero` costs **4 gas** for each zero byte in the transaction.\n- `Gtxdatanonzero` costs **16 gas** for each non-zero byte, which is **4 times more expensive**.\n\nThus, whenever a zero byte (`00`) is used in `msg.data` instead of a non-zero byte, it saves **12 gas**.\n\nThis EVM characteristic also impacts the consumption of other opcodes like `Gsset` and `Gsreset`. To illustrate, modifying the function signature `square(uint32)` to `square_Y7i(uint32)` changes the fingerprint to `00001878` instead of `d27b3841`.\n\nThe two most significant bytes of the fingerprint (`0000`) not only prioritize the **processing of the call** to this function, as seen earlier, but also consume **less gas** during data retrieval (**40** instead of **64**).\n\nHere are some additional examples:\n\n| Signatures (optimal)   | Selectors (optimal) | Signatures         | Selectors |\n| ---------------------- | ------------------- | ------------------ | --------- |\n| `deposit_ps2(uint256)` | 0000fee6            | `deposit(uint256)` | b6b55f25  |\n| `mint_540(uint256)`    | 00009d1c            | `mint(uint256)`    | a0712d68  |\n| `b_1Y()`               | 00008e0c            | `b()`              | 4df7e3d0  |\n\nSimilarly, being able to use signatures with **three zero-weight bytes** allows for consuming only **28 gas**.\n\nFor instance, [**`deposit278591A(uint)`**](https://emn178.github.io/online-tools/keccak_256.html?input_type=utf-8\u0026input=deposit278591A(uint)) and [**`deposit_3VXa0(uint256)`**](https://emn178.github.io/online-tools/keccak_256.html?input_type=utf-8\u0026input=deposit_3VXa0(uint256)), with respective signatures **`00000070`** and **`0000007e`**, achieve this optimization.\n\nHowever, given that there can be only a unique selection value (signature), there can be only **one function in a contract** with a signature that has four zero bytes, even though multiple signatures may lead to this optimized signature **`00000000`**, allowing for consuming only **16 gas** (example with the following signature: [**`execute_44g58pv()`**](https://emn178.github.io/online-tools/keccak_256.html?input_type=utf-8\u0026input=execute_44g58pv())).\n\n\n#### Examples of Gains on Intrinsic Costs:\n\n| Signatures          | Selectors  | # of zeros | Gas | Gain (gas) |\n| ------------------- | ---------- | ---------- | --- | ---------- |\n| `execute()`         | `61461954` | 0          | 64  | **0**      |\n| `execute_5Hw()`     | `00af0043` | 1          | 52  | **8**      |\n| `execute_mAX()`     | `0000eb63` | 2          | 40  | **24**     |\n| `execute_6d4S()`    | `000000ae` | 3          | 28  | **36**     |\n| `execute_44g58pv()` | `00000000` | 4          | 16  | **48**     |\n\n\n## Select0r\n\nI have developed **Select0r**, a tool written in **Rust** that allows you to rename your functions to optimize their calls. The program, given a function signature, will provide a list of alternative signatures with lower gas costs, enabling better ordering for the \"*function dispatcher*.\"\n\n[**GitHub - Laugharne/select0r**](https://github.com/Laugharne/select0r/tree/main)\n\n\n\n## Conclusions\n\n- Optimizing gas costs is a crucial aspect of designing efficient smart contracts on Ethereum.\n  \n- By paying attention to details such as the order of function signatures, the number of leading zeros in the hash, the order of function processing, and function renaming, you can significantly reduce the costs associated with your contract.\n\n- **However,** be aware that this may reduce the user-friendliness and readability of your code.\n\n- Optimization for execution may not be necessary for so-called administrative functions or those infrequently called.\n\n- On the other hand, it should be prioritized for functions assumed to be the most frequently called (to be determined manually or statistically during practical tests).\n\n- A single optimization may seem insignificant, especially compared to the overall cost of a transaction. However, a set of optimizations performed on a series of transactions makes all the difference, and it's not limited to optimizations on the \"*function dispatcher*.\"\n\nIn the end, these optimizations can make the difference between a cost-effective contract and one that is gas-expensive.\n\n\n--------\n\nCredits: **[Franck Maussand](mailto:franck@maussand.net)**\n\n*Special thanks to [**Igor Bournazel**](https://github.com/ibourn) for his suggestions and proofreading of this article.*\n\n--------\n\n\n\n## Additional resources\n\n- Hash function :\n  - [Hash function - Wikipedia](https://en.wikipedia.org/wiki/Hash_function)\n\n- Keccak :\n  - [SHA-3 - Wikipedia](https://en.wikipedia.org/wiki/SHA-3)\n  - [Difference Between SHA-256 and Keccak-256 - GeeksforGeeks](https://www.geeksforgeeks.org/difference-between-sha-256-and-keccak-256/)\n\n- Binary search :\n  - [Binary search algorithm - Wikipedia](https://en.wikipedia.org/wiki/Binary_search_algorithm)\n  - [Big O notation - Wikipedia](https://en.wikipedia.org/wiki/Big_O_notation)\n\n- References :\n  - [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf)\n  - [Opcodes for the EVM](https://ethereum.org/en/developers/docs/evm/opcodes/)\n  - [EVM Codes - An Ethereum Virtual Machine Opcodes Interactive Reference](https://www.evm.codes/?fork=shanghai)\n  - [Operations with dynamic Gas costs](https://github.com/wolflo/evm-opcodes/blob/main/gas.md)\n  - [Contract ABI Specification — Solidity 0.8.22 documentation](https://docs.soliditylang.org/en/develop/abi-spec.html#function-selector)\n  - [Yul — Solidity 0.8.22 documentation](https://docs.soliditylang.org/en/latest/yul.html)\n  - [Yul — Complete ERC20 Example](https://docs.soliditylang.org/en/develop/yul.html#complete-erc20-example)\n  - [Using the Compiler — Solidity 0.8.22 documentation](https://docs.soliditylang.org/en/latest/using-the-compiler.html)\n  - [The Optimizer — Solidity 0.8.22 documentation](https://docs.soliditylang.org/en/develop/internals/optimizer.html)\n\n- Tools :\n  - [GitHub - Laugharne/select0r](https://github.com/Laugharne/select0r/tree/main) ✨\n  - [Keccak-256 Online](http://emn178.github.io/online-tools/keccak_256.html)\n  - [Compiler Explorer](https://godbolt.org/)\n  - [Solidity Optimize Name](https://emn178.github.io/solidity-optimize-name/)\n  - [Ethereum Signature Database](https://www.4byte.directory/)\n  - [GitHub - shazow/whatsabi: Extract the ABI (and other metadata) from Ethereum bytecode, even without source code.](https://github.com/shazow/whatsabi)\n\n- Misc :\n  - [Function Dispatching | Huff Language](https://docs.huff.sh/tutorial/function-dispatching/#linear-dispatching)\n  - [Solidity’s Cheap Public Face](https://medium.com/coinmonks/soliditys-cheap-public-face-b4e972e3924d)\n  - [Web3 Hacking: Paradigm CTF 2022 Writeup](https://medium.com/amber-group/web3-hacking-paradigm-ctf-2022-writeup-3102944fd6f5)\n  - [paradigm-ctf-2022/hint-finance at main · paradigmxyz/paradigm-ctf-2022 · GitHub](https://github.com/paradigmxyz/paradigm-ctf-2022/tree/main/hint-finance)\n  - [GitHub - Laugharne/solc_runs_dispatcher](https://github.com/Laugharne/solc_runs_dispatcher)\n  - [WhatsABI? with Shazow - YouTube](https://www.youtube.com/watch?v=sfgassm8SKw)\n  - [Ethereum Yellow Paper Walkthrough (4/7) - Transaction Execution](https://www.lucassaldanha.com/transaction-execution-ethereum-yellow-paper-walkthrough-4-7/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flaugharne%2Foptimal_function_names_en","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flaugharne%2Foptimal_function_names_en","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flaugharne%2Foptimal_function_names_en/lists"}