paint-brush
An Update to Solidity’s ‘Assert()’ Statement You Might’ve Missedby@zartaj
189 reads

An Update to Solidity’s ‘Assert()’ Statement You Might’ve Missed

by Md Zartaj AfserJuly 23rd, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

A change in Solidity’s `assert()` statement that most developers are perhaps still unaware of. In previous versions of Solidity, the ‘assert()’ function used the INVALID opcode, which consumed all remaining gas in a transaction upon execution. Because of solidity 0.8.0, this difference is no longer true.
featured image - An Update to Solidity’s ‘Assert()’ Statement You Might’ve Missed
Md Zartaj Afser HackerNoon profile picture



As a developer, no matter what language you use, the most imperative task is to keep track of changes that are introduced in the newer versions of the language and tools. As I am a solidity dev, I missed this change and found out recently through a LinkedIn post. I am talking about an imperative change in Solidity’s assert() statement that most Smart Contract developers are perhaps still unaware of. Additionally, it seems quite a few Solidity articles around this topic are outdated.


Let’s see what has changed and how it affects devs and users.


What do we know about Solidity’s assert() Statement so far?

One of the major differences that you might have noticed between assert() and require() statements in solidity is that:


▶️ If assert() statements fail, they consume all of the Gas provided in a transaction

▶️ If require() statements fail, they refund the remaining gas back to the caller


Here is a simple example of the above statements, when we use a version less than 0.8.0.

// SPDX-License-Identifier: MIT

pragma solidity <0.8.0;

contract AssertTest {address public owner = msg.sender;

uint256 public requirecounter;  
uint256 public assertcounter;  

function assertFun() external {  
    assert(msg.sender == owner);  

    assertcounter = 10;  

     //gas 3000000 gas  
     //transaction cost 3000000 gas   
     //execution cost 2978936 gas  
}  

function requireFun() external {  
    require(msg.sender == owner);  

    requirecounter = 20;  
       //gas 3000000 gas  
       //transaction cost 23415 gas   
       //execution cost 2351 gas  
}  

}


As you can see assert() doesn’t refund the remaining gas but require() does.


Let’s dive into the details and understand why

To begin with, you first need to understand the difference between two specific opcodes in the EVM, i.e., 𝙄𝙉𝙑𝘼𝙇𝙄𝘿(0xfe) & 𝙍𝙀𝙑𝙀𝙍𝙏(0xfd).


𝐈𝐍𝐕𝐀𝐋𝐈𝐃: As per this opcode, the EVM must revert all state changes and consume all the gas sent in a transaction. No gas refunds for the sender of transactions.𝐑𝐄𝐕𝐄𝐑𝐓: As per this opcode, the EVM must revert all state changes but should also return the remaining gas to the sender.


Here is a reference from the yellow paper about this difference


Ethereum Yellow Paper page no.38


Now, in previous versions of Solidity, the assert() function used the INVALID opcode, which consumed all remaining gas in a transaction upon execution.


This behavior lead to issues like limiting the gas refunds for users and complicating debugging due to the lack of information provided by the INVALID opcode.


Well, since solidity version 0.8.0, this difference is no more true.

Because after the launch of solidity 0.8.0, the behavior of assert() statements has changed.



What Changed?

After solidity 0.8.0, the 𝐚𝐬𝐬𝐞𝐫𝐭() 𝐬𝐭𝐚𝐭𝐞𝐦𝐞𝐧𝐭 𝐝𝐨𝐞𝐬𝐧’𝐭 𝐜𝐨𝐧𝐬𝐮𝐦𝐞 𝐚𝐥𝐥 𝐠𝐚𝐬 𝐢𝐧 𝐚 𝐭𝐫𝐚𝐧𝐬𝐚𝐜𝐭𝐢𝐨𝐧. It now 𝐫𝐞𝐭𝐮𝐫𝐧𝐬 𝐭𝐡𝐞 𝐫𝐞𝐦𝐚𝐢𝐧𝐢𝐧𝐠 𝐠𝐚𝐬 𝐛𝐚𝐜𝐤 𝐭𝐨 𝐭𝐡𝐞 𝐬𝐞𝐧𝐝𝐞𝐫.


However, since Solidity version 0.8.0, the assert() function now uses the REVERT opcode, which does not consume all gas in a transaction but actually returns any remaining gas to the transaction’s sender.


With this change, the REVERT opcode now allows for better gas management, providing users with greater control over their gas usage.


This change has made it possible for developers to design more complex and gas-efficient smart contracts, which can lead to cost savings for both developers and users.


Below is the same code we saw before, but this time we used a solidity version greater than 0.8.0.


Notice, this time the function using assert() is using only partial gas and refunding the rest of it.


// SPDX-License-Identifier: MIT

pragma solidity <0.8.0;

contract AssertTest {address public owner = msg.sender;

uint256 public requirecounter;  
uint256 public assertcounter;  

function assertFun() external {  
    assert(msg.sender == owner);  

    assertcounter = 10;  

       //gas 3000000 gas  
       //transaction cost 23404 gas   
       //execution cost 2340 gas  
}  

function requireFun() external {  
    require(msg.sender == owner);  

    requirecounter = 20;  
       //gas 3000000 gas  
       //transaction cost 23415 gas   
       //execution cost 2351 gas  
}  

}


Now you might recall one of the optimization tricks is to throw the error as soon as possible inside a function. Because the more function will execute the more gas will be used, and at the point it hits the error, very less gas will be remaining and be refunded.


Note for Solidity Devs 📝It’s imperative for you as a smart contract developer to keep up with the latest changes in the language, as solidity evolves quite rapidly.


Always keep an eye on Solidity’s official documentation, blogs, etc, and validate the information that you read online.


Stay curious, keep learning, and keep building!


Also published here.