Solidity is the most popular programming language in the domain of blockchain. It is a powerful alternative for scripting smart contracts and offers a user-friendly development experience. If you want to learn Solidity Yul Assembly, then you have to dive deeper into the functionalities of Solidity. You can tap into the true potential of the Ethereum Virtual Machine or EVM by exploring the details of Solidity. Assembly is one of the important factors in the working of Solidity.
It is the low-level language that helps developers explore the inner working mechanisms of Ethereum Virtual Machine or EVM in detail. Assembly helps in optimization of smart contracts to achieve better performance and efficiency. It serves as an additional functionality for optimizing every line of code and drawing the best from smart contracts. Let us learn more about Solidity Yul Assembly in the following discussion.
Learn the process of creating and deploying smart contracts on the Ethereum blockchain with the Solidity Fundamentals Course.
Working of EVM and Opcodes
Before you write Assembly code, you must learn about the workings of EVM. You can understand a Solidity Yul Assembly guide with more clarity by exploring the working mechanisms of EVM and opcodes. The Ethereum Virtual Machine, or EVM, is one of the core components of the Ethereum blockchain. It serves as a universal decentralized computer for the execution of smart contracts alongside ensuring reliability and consistency throughout the network. What is the procedure for working with EVM?
Upon compilation of a contract, you would receive a bytecode. The bytecode is a long collection of bytes and serves as a representation of a collection of tiny instructions. Each instruction includes 1 byte, and they are termed as opcodes. What is the relationship between Solidity inline assembly and opcodes? Opcodes help in performing different operations, such as memory manipulation, storage access, arithmetic calculations, and control flow management.
Learn the basic and advanced concepts of Ethereum? Enroll now in the Ethereum Development Fundamentals Course
Definition of Assembly in Solidity
The next important aspect you need to learn for exploring a Solidity inline assembly example is the definition of assembly. Assembly or ‘inline assembly’ is the low-level language that enables low-level access to the Ethereum Virtual Machine. You can think of it as a special pass for the underlying working mechanisms of the Ethereum Virtual Machine. Assembly can help you write code for bypassing specific safety features and important checks in Solidity. As a result, developers would have more control over their smart contracts.
You can find a better answer to “What is Solidity Yul Assembly?” by identifying the language used for writing assembly in Solidity. The importance of Yul in writing assembly in Solidity validates the use of ‘Yul’ as a must-have term for explaining inline assembly. Yul is an intermediate language and offers the flexibility for compiling code into bytecode for the Ethereum Virtual Machine. At any point in writing code in Solidity, you can use the ‘assembly { }’ keyword to begin writing inline assembly.
You can find different control levels when you insert the assembly block in Solidity code. Solidity comes first and offers a high-level approach to writing smart contracts. On the other hand, you can want more control by using Yul or assembly language. Yul helps in manipulation of the EVM at a more granular level that can offer the flexibility of fine-tuning the code and improving efficiency. If you want a bit more adventure, then you can write bytecode directly for the EVM. It would not require a compiler and is similar to gaining complete control over the EVM.
Build your identity as a certified blockchain expert with 101 Blockchains’ Blockchain Certifications designed to provide enhanced career prospects.
How Can You Write Inline Assembly in Solidity?
The next crucial highlight in learning about inline assembly in Solidity with Yul programming language is the example code. You can use a Solidity inline assembly example for a simple contract, termed a ‘Box.’ The contract would help in storing a value, modifying the value, and retrieving it. Here is the example code for the Box contract.
pragma solidity ^0.8.14; contract Box { uint256 private _value; event NewValue(uint256 newValue); function store(uint256 newValue) public { _value = newValue; emit NewValue(newValue); } function retrieve() public view returns (uint256) { return _value; } }
Now, you can check the Solidity assembly call function by converting the example Solidity code to inline assembly. You can start working on the retrieve function from the first step. When you take the example of the original Solidity code, the retrieve function works on reading the value stored in the ‘_value’ parameter from the contract storage and returns the result.
In the case of assembly, you can achieve a similar result by using the ‘sload’ opcode for reading the value. The ‘sload’ opcode would receive one input, the key of the storage slot, and the ‘_value’ variable would be stored in slot #0. In assembly language, you can write the following example.
assembly { let v := sload(0) // Read from slot #0 }
After you obtain the value, you would have to work on returning it. In the case of Solidity inline assembly, you can rely on the ‘return’ opcode to accomplish the function. The ‘return’ opcode would take two distinct inputs, such as ‘offset’ and ‘size.’ The ‘offset’ input denotes the location where the value starts in the memory, and ‘size’ refers to the number of bytes that it should return.
On the other hand, the ‘sload’ returns the value ‘v,’ which is stored in the call stack rather than the memory. Therefore, you have to move it to memory before retrieving the value. The opcode ‘mstore’ can help in storing the value in memory by taking two inputs, such as ‘offset’ and ‘value.’ The ‘offset’ parameter denotes the location in the memory array where you should store the value, and ‘value’ refers to the number of bytes or ‘v.’
The final assembly code for the Solidity inline assembly example would look like the following.
assembly { let v := sload(0) // Read from slot #0 mstore(0x80, v) // Store v at position 0x80 in memory return(0x80, 32) // Return 32 bytes (uint256) }
You must note a special highlight in the assembly code, which chooses the 0x80 position in the memory specifically for storing the value. Why? The review of a Solidity Yul Assembly guide must also help you learn about the answer. Solidity blocks the first four 32-byte slots for specific applications. Therefore, free memory would start from 0x80. The simple example outlined in this discussion enables the use of 0x80 for storing the new variable. On the other hand, complex operations would require tracking of a pointer for free memory and ensuring effective management.
function retrieve() public view returns (uint256) { assembly { let v := sload(0) mstore(0x80, v) return(0x80, 32) } }
How to Use the Store Function in the Given Example?
The comprehensive explanation of answers to ‘What is Solidity Yul Assembly?’ would also draw attention to the ‘store’ function. It helps in storing a variable with the ‘sstore’ opcode that would take two inputs. One of the inputs is ‘key’, which is a 32-byte key in storage, and ‘value’ is the value that you have to store. In assembly language, you can write the function as the following example:
assembly { sstore(0, newValue) // store value at slot 0 of storage }
After storing the value, you can begin the process of transferring an event by utilizing the ‘log1’ opcode. The ‘log1’ opcode requires three different inputs such as ‘offset,’ ‘topic,’ and ‘size.’ The ‘offset’ input denotes the byte offset in the memory where you have to store the event data. ‘Size’ input denotes the size of the data that you have to copy in bytes.
The ‘topic’ input refers to the 32-byte value, which works as an identifier or label for the event. Candidates who want to learn Solidity Yul Assembly practical knowledge should know that ‘log1’ opcode must set the three inputs to different values. You should set the ‘offset’ to 0x80 as you have stored the value by using ‘mstore’ opcode.
The ‘size’ input for the opcode can be specified as 0x20, which serves as a representative of 32 bytes. Finally, you must set the ‘topic’ parameter as a label for an event such as the name ‘NewValue.’ The argument passed for ‘topic’ is just the hash of the event signature. By using these updates, the ‘store’ function would look like the following.
function store(uint256 newValue) public { assembly { // store value at slot 0 of storage sstore(0, newValue) // emit event mstore(0x80, newValue) log1(0x80, 0x20, 0xac3e966f295f2d5312f973dc6d42f30a6dc1c1f76ab8ee91cc8ca5dad1fa60fd) } }
Finally, the ‘Box’ contract would look like the following.
pragma solidity ^0.8.14; contract Box { uint256 public value; function retrieve() public view returns(uint256) { assembly { let v := sload(0) mstore(0x80, v) return(0x80, 32) } } function store(uint256 newValue) public { assembly { sstore(0, newValue) mstore(0x80, newValue) log1(0x80, 0x20, 0xac3e966f295f2d5312f973dc6d42f30a6dc1c1f76ab8ee91cc8ca5dad1fa60fd) } } }
How Can You Use Solidity Yul Assembly to Send Ether to an Address?
The functions of Solidity assembly call and its efficiency also allow you to design another contract for sending Ether to an address. Here is an example of a contract that can help you achieve the same functionalities.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.14; contract MyContract { address public owner = payable(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4); function sendETH(uint256 _amount) public payable { require(msg.value >= _amount,"Not Enough ETH Sent"); bool success; assembly { let o := sload(0) success := call(gas(), o, _amount, 0, 0, 0, 0) } require(success, "Failed to send ETH"); } }
Here is a breakdown of the assembly code in the individual steps.
First of all, slot 0 stores the owner’s address, and the owner’s address is assigned to the local variable ‘o’. In addition, the ‘sload’ opcode helps in reading values from storage. In the next line of the Solidity inline assembly example, you can notice the execution of the ‘call’ opcode. It helps in sending Ether to a specific address, and the ‘call’ opcode in assembly language would take different arguments.
The ‘gas()’ function works on returning the remaining gas for the existing execution context. It can be passed in the form of the first argument for calling that would indicate the provision of a maximum amount of gas for the function calls.
The ‘call’ opcode also takes the ‘address’ argument, which represents the address of the contract or user that it has to call. It has the value that is located in slot 0 storage.
The ‘value’ argument represents the amount of Ether that you have to send through the function call. In the case of this example, the ‘value’ argument must be passed as the second argument for calling.
You can understand Solidity inline assembly functionalities by pointing at the next four arguments, i.e. (0, 0, 0, 0). The arguments help in passing additional data to the function that you want to call. In the example code, you can notice that they have been set to zero to ensure that they don’t pass any additional data.
The call opcode outcomes can be assigned to the ‘success’ local variable. It would be true when the function call is successful and false in the contrary situation.
Excited to build your skill in Ethereum development by leveraging the ethers.js library? Enroll now in the Ethers.Js Blockchain Developer Course
What are the Limitations of Solidity Assembly Language?
The details in a Solidity Yul Assembly guide can help you understand that low-level language would present certain limitations. First of all, it is difficult to understand, and beginners might not understand the logic and flow of the code instantly. It could be an intimidating concept for people who are not familiar with low-level programming. However, it is important to note that such limitations do not affect the value benefits of assembly in Solidity. Assembly code could help in improving competitive advantage and gas efficiency in Solidity.
Start your journey to become a smart contract developer or architect with Smart Contract Skill Path
Conclusion
The applications of Yul programming language to create inline assembly code in Solidity help in optimizing smart contracts. The primary goal of Solidity inline assembly revolves around tailoring smart contracts to achieve the desired goals of a smart contract precisely. Assembly code could look unappealing and difficult as it is scripted in low-level language.
On the other hand, it can offer a comprehensive range of benefits that can help in achieving better cost savings and gas efficiency. Developers should account for all the trade-offs and evaluate whether the complexity of assembly code is the right price to pay for potential advantages in particular use cases.