(Part 9) Ethereum Solidity Assembly - Return, Memory, Hexadecimal, Pointers And Memory Addressing(PT 9)

in utopian-io •  12 days ago

Repository

https://github.com/igormuba/EthereumSolidityClasses/tree/master/class9

What Will I Learn?

  • Returning in assembly
  • Hexadecimal
  • Memory addressing
  • Managing multiple data with multiple pointers
  • Managing multiple data with a single pointer

Requirements

  • Internet connection
  • Code editor
  • Browser

Difficulty

  • Advanced

Tutorial Contents

Before proceeding with this class I recommend to take a look at the previous tutorial, available here
https://steemit.com/utopian-io/@igormuba/part-8-ethereum-solidity-assembly-reducing-costs-and-creation-your-low-level-expression-pt-8

I will, first, show you how can you return from assembly code, as on the previous exercise we have created the uint variable outside of the assembly code and returned it automatically.

For this, you will need to have a very minimum knowledge about Ethereum memory addressing if you also have previous knowledge of computer architecture, memory management or even some assembly this may be helpful.

Do not worry, the code looks scary and at first, you might not understand, but by using drawings I think I can teach you pretty easily how the concepts of pointers, memory management, bites, bytes and management of the data works behind the curtains

I prefer to first show you the code we will use and shortly after explain what each piece of code does and in the end I will teach a bit more in depth how the memory addressing works and how to keep track of the pointer, we will start on this first code with only one pointer, then we will move to use multiple pointers to store different pieces of data in different physical places of the memory, then we will go back to using one single pointer to store multiple chunks of data!

The return in assembly

pragma solidity ^0.5.0; //version of solidity we will use

contract assemblyReturn{
    function storeAccess(uint number) public returns (uint){
        assembly{ //prepares to receive assembly
            let _pointer:=add(msize(), 1) //starts the pointer in the end
            mstore(_pointer, number) //stores the variable on the pointer
            return(_pointer, 0x20) //returns from the pointer + 0x20(HEX) bytes
        }
    }
}

msize gets the size of the memory and adds one to it, then assigns the result to a variable that is the pointer we will use to keep track of where we are storing the information
mstore(x,y) store the data y starting on the position x of the memory
return(x,y) returns the data from the point x until the point x+y on the memory
0x20 means it is a 256bits group of data in hexadecimal

The tricky part probably for someone that has never touched assembly or do not understand other numeric systems other than decimals, here is a quick intro to hexadecimal, they are used to keep track of how many bytes we are using as distances are measured in bytes, even though uint256 says in its name in bits (256bits)

Hexadecimal

The simple way to convert decimal to hex and hex to dex is to Google, literally, if you google "32 in hex" you will get exactly 0x20
image.png

The number you use on your daily life is called decimals, and they go like "1, 2, ...., 8, 9, 10, 11, ....., 19, 20, 21, ..."

Another numeric system you might have heard before is the binary system, here is an introduction to Binary numbers with a bit of hexadecimal on it
https://ryanstutorials.net/binary-tutorial/

But, in short, binary numbers go from 1 to F
"1, 2, ..., 8, 9, A, B, C, D, E, F, 10, 11, 12, ....., 18, 19, 1A, 1B, 1C, 1D, 1E, 1F, 20, 21, 22..."
The F is like the 9 from decimal, after this you add one to the decimal place on the left and start again from zero, that is exactly how decimals work and that is how hexadecimal work too

So, by telling the instruction return(_pointer, 0x20) we are telling the solidity assembly code to search, after the pointer, for the next 256bits of data (32 bytes), the 0x20 means, in hexadecimal, how many bytes, so it is 32 bytes, which turns out that it is 256bits (32*8), which is the size of uints as when you declare just uint the system automatically considers it as uint256

Full loop with the return, all in assembly

Let us use what I have just shown you now and make the for loop from the previous class return the result but using assembly.
https://steemit.com/utopian-io/@igormuba/part-8-ethereum-solidity-assembly-reducing-costs-and-creation-your-low-level-expression-pt-8

The code was this

pragma solidity ^0.5.0;

contract solidityAssembly{
    
    function assemblyLoop() public pure returns (uint _x){
        assembly {
            for {let counter:= 0} lt(counter, 10) {counter := add(counter,1)}{
                _x := add(_x,1)
            }
        }
    }
}

As you can see we create the returnable uint variable on the declaration of the function, which makes solidity automatically keep track of what happens to it and we edit it, now I want to change to code so the variable is created inside the assembly code and returned right from there

pragma solidity ^0.5.0;

contract solidityAssembly{
    
    function assemblyLoop(uint _number) public pure returns (uint){ //we are not initiating the return like on last tutorial
        assembly {
            let _pointer:=add(msize(), 1) //initiates the pointer in the end of the memory
            let _x:=_number //put the number in an internat variable, not required though
            for {let counter:= 0} lt(counter, 10) {counter := add(counter,1)}{ //for loop from last class
                _x := add(_x, 1) //at each run adds 1 to the given number (adds 10 in total)
            }
            mstore(_pointer, _x) //stores the final result on the pointer
            return(_pointer, 0x20)//returns the result from the pointer plus 256bits after it
        }
    }
}

There is not much there to explain as it is just a merge from what I taught earlier on this tutorial plus what I taught on the previous tutorial

And it works just as expected
image.png

Playing with the memory

Let us say you want to store more than one number in the memory. To do that you, very likely, would want to have pen and paper next to your hand to keep track of the pointers, welcome to the low-level programming world!

First, here is the code, after that I will show you what happens, why and how

pragma solidity ^0.5.0;

contract solidityAssembly{
    
    function assemblyLoop(uint _number, uint _numberTwo) public pure returns (uint){ //receives 2 varibles
        assembly{
            let _pointer:=add(msize(), 1) //first pointer
            mstore(_pointer, _number) //stores on first pointer
            let _pointerTwo:=add(_pointer, 0x20) //second pointer
            mstore(_pointerTwo, _numberTwo) //stores on second pointer
            let result:= add(mload(_pointer), mload(_pointerTwo)) //sums the 32byte stored on the 2 pointers
            let _pointerThree:=add(_pointerTwo, 0x20) //third pointer
            mstore(_pointerThree, result) //stores on third pointer
            return(_pointerThree, 0x20) //returns 32bytes from third pointer
        }
    }
}

What happens is that, after we write something on the 32bytes from the pointer, we need to move to the next location, I did it the "lazy way", by creating new pointers each one in one location, the hardcore way of doing that would be using only one pointer

pragma solidity ^0.5.0;

contract solidityAssembly{
    
    function assemblyLoop(uint _number, uint _numberTwo) public pure returns (uint){
        assembly{
            let _pointer:=add(msize(), 1)
            mstore(_pointer, _number)
            mstore(add(_pointer, 0x20), _numberTwo)
            mstore(add(_pointer, 0x40), add(mload(_pointer), mload(add(_pointer, 0x20))))
            return(add(_pointer, 0x40), 0x20)
        }
    }
}

We are storing the data in chunks of 32bytes, that is why the pointer moves 0x20 places at a time because the numbers are uint256.

Please, forgive me for my bad paint skills, but here is the space of memory we are using
image.png
On the first code, we were using 3 pointers, this allows us to use the pointers directly to store and retrieve data from the memory, one pointing to each space of memory, we plain simple filled each one with a 256bits of data from the uint256, which has the exact same size, 0x20 bytes

image.png
In the second example it is pretty much the same thing, but in this case we only have 1 pointer, so whenever we want to store new data we must pay very close attention to where are we writing data, we can corrupt the memory if we write anything on the wrong place, that is why I've told you that on low level we program with pens and papers beside us, to keep track of the pointer, physically!

fewer pointers less gas spent

After the previous explanation, you might think to yourself that having one pointer to each piece of data might be a good idea, but it is not! The advantage of storing raw data directly on the memory is to save gas cost, if we use pointers it won't change anything as we will still be using variables! So the less amount of pointers you can use the better, so the code with only one pointer is the cheapest one and I will show you.

I have created 2 functions on Remix, one with the use of 1 pointer and the other using 3 pointers

pragma solidity ^0.5.0;

contract solidityAssembly{
    
    function assemblyLoop(uint _number, uint _numberTwo) public pure returns (uint){
        assembly{
            let _pointer:=add(msize(), 1)
            mstore(_pointer, _number)
            mstore(add(_pointer, 0x20), _numberTwo)
            mstore(add(_pointer, 0x40), add(mload(_pointer), mload(add(_pointer, 0x20))))
            return(add(_pointer, 0x40), 0x20)
        }
    }
    
     function assemblyLoopTwo(uint _number, uint _numberTwo) public pure returns (uint){ //receives 2 varibles
        assembly{
            let _pointer:=add(msize(), 1) //first pointer
            mstore(_pointer, _number) //stores on first pointer
            let _pointerTwo:=add(_pointer, 0x20) //second pointer
            mstore(_pointerTwo, _numberTwo) //stores on second pointer
            let result:= add(mload(_pointer), mload(_pointerTwo)) //sums the 32byte stored on the 2 pointers
            let _pointerThree:=add(_pointerTwo, 0x20) //third pointer
            mstore(_pointerThree, result) //stores on third pointer
            return(_pointerThree, 0x20) //returns 32bytes from third pointer
        }
    }
}

As you can see, they give the same results for the same inputs, quite obviously

image.png

But if you take a look at the execution cost

This first screenshot is for the code that uses only one pointer
image.png

This second screenshot is for the code that uses two pointers
image.png

As you can clearly see, by using only one pointer we save almost 10% of the gas cost, and that is the very reason why we use assembly

In comparison, if I build a function using higher level programming, no assembly, a pretty simple, fast and cheap function

function noAssembly(uint _number, uint _numberTwo) public pure returns (uint){
        return _number+_numberTwo;
    }

It still costs more than using 3 pointers

image.png

Sure, it costs almost the same as using 3 pointers, so we can take from this 2 things:

  • Using fewer pointers is a very good thing.
  • Using assembly, even ineffectively, is cheaper, even if only a little bit

But of course, not always the gas savings pays off for the extra work

Curriculum

Beneficiaries

This post has as beneficiaries
@utopian.pay with 5%
using the SteemPeak beneficiary tool
image.png

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

Thank you for your contribution @igormuba.
After analyzing your tutorial we suggest the following:

  • The image about the memory space you are using is a little bad. Try to put more professional images according to the quality of your tutorial.

Thank you for continuing this excellent work.

We are waiting for your next tutorial.

Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post, click here.


Need help? Chat with us on Discord.

[utopian-moderator]

·

Thank you for your review, @portugalcoin! Keep up the good work!

I would love you to host my Dtube videos. Please tell me if you are still providing this service. 😎
https://d.tube/v/acousticsteveo/wri9mha0
https://d.tube/#!/c/acousticsteveo

·

Yes, I am seeding DTube videos for free from my server, do you have discord or telegram?

Posted using Partiko Android

·
·

Yes, I am acousticsteveo on discord, and I have an acousticsteveo channel on telegram. You rock.

Posted using Partiko Android

Boa Igor sucesso com os tutoriais e os projetos. Estou acompanhando

Posted using Steeve, an AI-powered Steem interface

Hi @igormuba!

Your post was upvoted by @steem-ua, new Steem dApp, using UserAuthority for algorithmic post curation!
Your post is eligible for our upvote, thanks to our collaboration with @utopian-io!
Feel free to join our @steem-ua Discord server

Hey, @igormuba!

Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Get higher incentives and support Utopian.io!
Simply set @utopian.pay as a 5% (or higher) payout beneficiary on your contribution post (via SteemPlus or Steeditor).

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!