Back to Blog 

SolidityWeb3 Security
Solidity Gas Optimization Tips With Assembly You Haven't Heard Yet!
5 min
When it comes to writing efficient and cost-effective smart contracts on the Ethereum blockchain, every little bit of gas savings counts. One way to optimize gas usage is by using assembly code instead of Solidity in certain parts of your contract.
In this article, we will explore how using assembly code can help you save gas in various scenarios, from hashing and math operations to writing to storage and checking for zero addresses. We will provide examples of Solidity code and their corresponding assembly implementations, along with gas usage comparisons so you can see for yourself the potential gas savings.
1. Use assembly to hash instead of Solidity
1function solidityHash(uint256 a, uint256 b) public view {2 //unoptimized3 keccak256(abi.encodePacked(a, b));4}
Gas: 313
1function assemblyHash(uint256 a, uint256 b) public view {2 //optimized3 assembly {4 mstore(0x00, a)5 mstore(0x20, b)6 let hashedVal := keccak256(0x00, 0x40)7 }8}
Gas: 231
Let's take a look at the Yul instructions used and their explanation.

2. When possible, use assembly instead of unchecked{++i}
You can also use
unchecked{++i;} for even more gas savings but this will not check to see if i overflows. For best gas savings, use inline assembly, however, this limits the functionality you can achieve.1//loop with unchecked{++i}2function uncheckedPlusPlusI() public pure {3 uint256 j = 0;4 for (uint256 i; i < 10; ) {5 j++;6 unchecked {7 ++i;8 }9 }10}
Gas: 1329
1//loop with inline assembly2function inlineAssemblyLoop() public pure {3 assembly {4 let j := 05 for {6 let i := 07 } lt(i, 10) {8 i := add(i, 0x01)9 } {10 j := add(j, 0x01)11 }12 }13}
Gas: 709
Now, the explanation of
lt Yul instruction and similar comparison opcodes.
3. Use assembly for math (add, sub, mul, div)
Use assembly for math instead of Solidity. You can check for overflow/underflow in assembly to ensure safety.
Addition
1//addition in Solidity2function addTest(uint256 a, uint256 b) public pure {3 uint256 c = a + b;4}
Gas: 303
1//addition in assembly2function addAssemblyTest(uint256 a, uint256 b) public pure {3 assembly {4 let c := add(a, b)5 if lt(c, a) {6 mstore(0x00, "overflow")7 revert(0x00, 0x20)8 }9 }10}
Gas: 263
Subtraction
1//subtraction in Solidity2function subTest(uint256 a, uint256 b) public pure {3 uint256 c = a - b;4}
Gas: 300
1//subtraction in assembly2function subAssemblyTest(uint256 a, uint256 b) public pure {3 assembly {4 let c := sub(a, b)5 if gt(c, a) {6 mstore(0x00, "underflow")7 revert(0x00, 0x20)8 }9 }10}
Gas: 263
Multiplication
1//multiplication in Solidity2function mulTest(uint256 a, uint256 b) public pure {3 uint256 c = a * b;4}
Gas: 325
1//multiplication in assembly2function mulAssemblyTest(uint256 a, uint256 b) public pure {3 assembly {4 let c := mul(a, b)5 if lt(c, a) {6 mstore(0x00, "overflow")7 revert(0x00, 0x20)8 }9 }10}
Are you audit-ready?
Download the free Pre-Audit Readiness Checklist used by 40+ protocols preparing for their first audit.
No spam. Unsubscribe anytime.
Gas: 265
Division
1//division in Solidity2function divTest(uint256 a, uint256 b) public pure {3 uint256 c = a * b;4}
Gas: 325
1//division in assembly2function divAssemblyTest(uint256 a, uint256 b) public pure {3 assembly {4 let c := div(a, b)5 if gt(c, a) {6 mstore(0x00, "underflow")7 revert(0x00, 0x20)8 }9 }10}
Gas: 265
So, now let's see the explanation of these Yul instructions.

4. Use assembly to write storage values
1address owner = 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84;23function updateOwner(address newOwner) public {4 owner = newOwner;5}
Gas: 5302
1address owner = 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84;23function assemblyUpdateOwner(address newOwner) public {4 assembly {5 sstore(owner.slot, newOwner)6 }7}
Gas: 5236
We've seen some of these Yul instructions above but let's remind ourselves of their explanation.

5. Use assembly to check for address(0)
1function ownerNotZero(address _addr) public pure {2 require(_addr != address(0), "zero address)");3}
Gas: 258
1function assemblyOwnerNotZero(address _addr) public pure {2 assembly {3 if iszero(_addr) {4 mstore(0x00, "zero address")5 revert(0x00, 0x20)6 }7 }8}
Gas: 252
iszero and mstore we've already seen above, so let's check now what is the explanation for revert.
6. Use assembly when getting a contract's balance of ETH
You can use
selfbalance() instead of address(this).balance when getting your contract's balance of ETH to save gas.1function addressInternalBalance() public returns (uint256) {2 return address(this).balance;3}
Gas: 148
1function assemblyInternalBalance() public returns (uint256) {2 assembly {3 let c := selfbalance()4 mstore(0x00, c)5 return(0x00, 0x20)6 }7}
Gas: 133
return we have just seen above, so let's check now selfbalance.
Get in touch
At Zealynx, we specialize in smart contract security audits — including gas optimization reviews that go beyond surface-level checks. Whether you need a full protocol audit or a targeted gas efficiency review, our team is ready to help you ship secure, cost-effective code. Reach out to start the conversation.
Want to stay ahead with more in-depth analyses like this? Subscribe to our newsletter and ensure you don't miss out on future insights.
Are you audit-ready?
Download the free Pre-Audit Readiness Checklist used by 40+ protocols preparing for their first audit.
No spam. Unsubscribe anytime.


