Solidity dynamic array assembly development tutorial

We always recommend not to use assembly when developing Solidity smart contracts. But in a few cases, there may be no other choice, so you still need to learn some knowledge of Solidity assembly development. In this tutorial, we will learn how to use dynamic byte arrays in Solidity assembly development.

Use their own familiar language learning Ethernet Square DApp development :
the Java | Php | Python | .Net / C # | golang | Node.JS | Flutter / Dart

1. Use the Remix editor

First, let's paste this simple contract into the remix editor :

pragma solidity ^0.5.10;

contract AssemblyArrays {
  
  bytes testArray;
  
  function getLength() public view returns (uint256) {
      return testArray.length;
  }
  
  function getElement(uint256 index) public view returns (bytes1) {
      return testArray[index];
  }
  
  function pushElement(bytes1 value) public {
      testArray.push(value);
  }
  
  function updateElement(bytes1 value, uint256 index) public {
      testArray[index] = value;
  }
}

First get familiar with the Remix editor. We first need to select the compiler version, then compile the contract, deploy the contract, perform some functions, and then debug.

2. The first line of assembly code

Now, let us modify the getLengthfunction to write the first line of assembly code:

function getLength() public view returns (uint256) {
  bytes memory memoryTestArray = testArray;
  uint256 result;
  assembly {
    result := mload(memoryTestArray)
  }
  return result;
}

A lot of things happened in the above few lines of code. Assembler is just like that, it takes a lot of code to realize a very simple function. We will be testArraycopied from storage to memory, because this is the focus of this article. We can talk about storage slots in the future.

Before delving into assembly language blocks, please note that the assembly instructions operate on 32-byte words. Therefore, the mload instruction pushes
the 32 bytes of the memory location pointed to by memoryTestArray onto the stack. .

3. Breakpoint setting and single step execution of Solidity assembly code

Now debug it. In Remix, you can set a breakpoint by clicking the line number. Let's set a breakpoint on line 11, so it looks like this:
Insert picture description here

After updating the getLengthfunction, make sure to compile and redeploy the contract again. Now, let us call the pushElementfunction to insert the byte 0x05 into the array, and then call getLength, the function should return 1.

getLengthWe can debug it after calling . Click the "Debug" button in the last call in the bottom panel, which will open the debugger in the left sidebar. There is a button for fast forward (such as fast_forward:), which jumps to the next breakpoint. Let's click on that. If you use a different compiler or different settings, it may not be exactly the same for you, but the core will be the same. The basic idea is mloadto get the debugger before execution, in my environment it is the #0871 instruction.
Insert picture description here

4. View the impact of Solidity assembly code on the stack

Now let's look at the stack/stack content in the sidebar of the debugger:

Insert picture description here

At the top of the stack, position 0 is 0x0…80. This is the location of the memoryTestArray in memory, which will be used as the parameter of the mload instruction.

5. View the impact of Solidity assembly code on memory

Now, let's take a look at the "Memory" section of the debugger sidebar, starting at address 0x0...80:

Insert picture description here

There are 31 bytes of 0x00, followed by 1 byte of 0x01, then 1 byte of 0x05, followed by 31 bytes of 0x00. This can be a bit confusing, so let's step back and notice that 1 byte (8 bits) is represented by 2 hexadecimal digits (1 hexadecimal digit represents 4 digits). Similarly, the decimal of 0x10 hexadecimal is equal to 16. Therefore, in memory, location 0x80 holds 16 bytes, location 0x90 (0x80 + 0x10) holds the following 16 bytes, then location 0xa0 (0x90 + 0x10) holds the following 16 bytes, and location 0xb0 holds the last 16 byte. Because the instructions in assembly operate in units of 32 bytes, if we call mload(0x80), it will take 32 bytes from memory location 0x80 and put them on the stack.

6. Step through the mload instruction

Let us look at the actual implementation. Let's click on the "Single Step into" button (ie the down arrow) in the debugger to execute the mload instruction. Now look at the top of the stack:

Insert picture description here

mloadThe instruction fetches the contents of the top of the stack: 0x0…80, and then pushes the 32 bytes at positions 0x0…1 in the memory. This is the most important point to understand the byte array in the memory: the first 32 bytes store the length of the array.

Try to call a pushElementfunction to insert element 0x06 into the array. Then call getLengthand debug again. Similarly, mload32 bytes will be loaded from memory location 0x80, but this time the memory content is 0x0...2. When we append new elements, Solidity updates the size of the array for us.

Another thing that has changed in memory is that the current position 0xa0 is 0x050600...00. Therefore, in memory, a byte array variable stores its length in the first 32 bytes, and then begins to store specific members. First we press 0x05, and then press 0x06.

Insert picture description here

7. Rewrite the getLength method with Solidity assembly

Try to push some more elements, call getLengthand debug to see the new bytes in memory. If we getElementconvert to assembly, the process will become clearer:

function getElement(uint256 index) public view returns (bytes1) {
    uint256 length = getLength();
    require(index < length);
    bytes memory memoryTestArray = testArray;
    bytes1 result;
    assembly {
      let wordIndex := div(index, 32)
      let initialElement := add(memoryTestArray, 32)
      let resultWord := mload(add(initialElement, mul(wordIndex, 32)))
      let indexInWord := mod(index, 32)
      result := shl(mul(indexInWord, 8), resultWord)
    }
    return result;
}

Well, this might scare you a bit! Let's stroke it slowly.

The first super important thing is that we added a requirestatement to check that indexit is not out of scope. This is crucial when calling mload. We need to ensure that the memory location to be loaded is correct, otherwise it may leak information that the caller should not access, which may put our contract at a serious risk of attack .

Next, let us look at the assembly code block. Since mload32 bytes are read at a time, it is not easy to read only 1 byte. If we divide the index by 32 and round up, this will get the 32-byte sequence number of the member to be found. E.g:

div(0, 32) = 0
div(18, 32) = 0
div(32, 32) = 1
div(65, 32) = 2

It looks pretty good. But remember, the first word (32 bytes) of the built-in memory pointed to by the memoryTestArray is the length of the storage array. Therefore, we need to add 32 bytes to find the first array member. After considering all these factors, we can load the 1-byte word (32 bytes) that contains the one we need:

memoryTestArrayAdd 32 bytes to the memory location to skip the length of the array, and multiply the wordIndex by 32, because each word has 32 bytes.

But it is not finished yet. Now we need to extract exactly 1 byte from this word. To do this, we need to find the index of the byte in the word. This is the remainder of the word index divided by 32, which can be obtained by modinstructions. E.g:

mod(0, 32) = 0
mod(18, 32) = 18
mod(32, 32) = 0
mod(65, 32) = 1

Yes, let us complete the last step, extract the byte. To make the byte at the front, we move the required number of bits to the left. shlThe instructions move one bit at a time, so in order to move the specified number of bits, we have to multiply indexInWord by 8.

Once we assign the 32-byte word beginning with this byte to the result variable, it will delete all other bytes because
we declared its type as bytes1.


Original link: Dynamic array developed by Solidity compilation — Huizhi.com

Guess you like

Origin blog.csdn.net/shebao3333/article/details/107812063