Yul Exercises

In-memory

contract Question1 {
    function iterateEachElementInArrayAndReturnTheSum(uint256[] calldata array) external pure returns (uint256)  {
        // TODO: Iterate each element in the array using only assembly
        
        // creates a new in-memory copy of the input array
        uint256[] memory arrayM = array;
        uint256 sum = 0;
        
        for (uint i = 0; i < arrayM.length; ++i) {
            assembly {
                // 0x20 needs to be added to an array because the first slot contains the array length.
                sum := add(sum, mload( add(add(arrayM, 0x20), mul(i, 0x20)) ))
            }
        }

        return sum;
    }
}

working with calldata

  • Since the array is passed as calldata, I looked to work will calldata as much as possible, instead of memory.

  • To iterate, we need the length of the array

    • We obtain the length of the array with calldataload(0x24).

    • calldataload(startingOffset) loads 32 bytes starting from the specified offset in the calldata onto the stack.

    • Instead of using 'let len := calldataload(0x24)', we reference calldataload(0x24) directly into the for loop to save on gas.

  • On calldataload(0x24):

    • The first 4 bytes of calldata contain the function signature.

    • The next 32 bytes (0x20) in calldata point to the location in calldata where the array begins.

    • The subsequent 32 bytes is the length space. (data following the array’s length is the actual array content).

  • Then we set up a for loop to iterate through the array elements.

    • 1st element is located 32 bytes after the length space, at add(0x24, 0x20) = 0x44

    • 2nd element is located 32 bytes after the first element, at add(add(0x24, 0x20), 32)

    • So to traverse down the calldata space, from element to element in the loop, we add mul(i, 0x20) to the 1st element's position

    • Essentially, mul(i, 0x20) allows for iteration of elements in the array by increasing the memory offset from the 1st element

Last updated