Smart Contract Development
  • Introduction
    • What is a Transaction
    • Accounts and Signing
    • What is a smart contract
  • Learning Solidity
    • Introduction
    • Module 1
      • Variable Types
      • Variable Scope: State & Local variables
      • Global variables
      • Functions
        • View and Pure
        • Shadowing in Fuctions
      • Mapping
      • Require
      • Events
    • Project #1: Simple Registry
    • Module 2
      • Constructor
      • Data Location: Value & Reference
      • Interface
      • Import
        • Importing in Foundry
      • Inheritance
      • ERC-20
      • Checks-effect-interaction pattern
    • Project #2: Basic Vault
    • Module 3
      • Payable
      • Receive
      • Fallback
      • Returns
    • Project #3: ERC20+ETH Wrapper
    • Module 4
      • Immutable and Constant
      • Fixed-point Math
      • Abstract contracts
      • ERC-4626
      • Modifier + Inheritance +Ownable
      • Type
    • Project #4: Fractional Wrapper
    • Module 5
      • If-else
      • Libraries
        • TransferHelper
      • Chainlink Oracle
    • Project #5: Collateralized Vault
  • Compendium
    • Solidity Basics
      • Variable Types
      • Value Types
        • address
        • enum
      • Reference Types
        • strings
        • mappings
        • struct
        • Arrays
        • Multi-Dimensional arrays
      • Global Objects
      • Functions
        • Function types
        • Constructor Function
        • Transaction vs Call
        • Require, Revert, Assert
      • Function signature + selectors
      • Payable
        • Payable + withdraw
        • msg.value & payable functions
      • Receive
      • Fallback function (sol v 0.8)
        • Fallback function (sol v 0.6)
      • call, staticcall, delegatecall
    • Return & Events
    • Control Variable Visibility
    • Local Variables (Storage v Memory)
    • Data Location and Assignment Behaviors
    • Modifiers & Inheritance & Import
      • import styles
    • Interface & Abstract Contracts
    • ABI & Debugging
    • Libraries
    • Conditional(ternary) operators
    • Smart Contract Life-cycle
      • Pausing Smart Contracts
      • Destroying Smart Contracts
    • Merkle Trie and MPT
    • Merkle Tree Airdrop
  • Try & catch
  • Ethereum Signatures
  • EVM, Storage, Opcodes
    • EVM
    • Wei, Ether, Gas
    • Storage
    • ByteCode and Opcodes
    • Transaction costs & Execution costs
  • Reading txn input data
  • Data Representation
  • Yul
    • Yul
      • Intro
      • Basic operations
      • Storage Slots
      • Storage of Arrays and Mappings
      • Memory Operations
      • Memory: how solidity uses memory
      • Memory: Return, Require, Tuples and Keccak256
      • Memory: Logs and Events
      • Inter-contract calls
      • calldata
      • free memory pointer
    • Yul Exercises
      • read state variable
      • read mapping
      • iterate Array, Return Sum
    • memory-safe
  • Upgradable Contracts
    • Upgradability & Proxies
    • UUPS Example
    • Minimal Proxy Example
    • TPP Example
    • 🚧Diamond
      • On Storage
  • Gas Opt
    • Block Limit
    • gasLimit & min cost
    • Solidity Optimiser
    • Memory v calldata
    • Memory caching vs direct storage vs pointers
    • < vs <=
    • reverting early
    • X && Y, ||
    • constant and immutable
    • caching sload into mload
    • Syntactic Sugar
    • using unchecked w/o require
    • Compact Strings
    • Calling a view function
    • Custom errors over require
    • usage of this.
      • multiple address(this)
  • ERCs & EIPs
    • ERC-20.sol
      • Core functions
      • transfer()
      • transferFrom()
      • TLDR transfer vs transferFrom
    • Landing
      • ERC721.sol
      • EIP-721
        • LooksRare
        • Page 1
      • ERC-1271
      • EIP-2981
      • ERC-165
      • EIP-1167: Minimal Proxy Contract
    • VRFConsumerBase
    • UniswapV2Library
  • Yield Mentorship 2022
    • Projects
      • #1 Simple Registry
      • #2 Basic Vault
      • #3 ERC20+ETH Wrapper
        • setFailTransferTrue
      • #4 Fractional Wrapper
      • #5 Collateralized Vault
        • Process
        • Vault.sol
        • Testing
        • Chainlink Oracles
        • Pricing + Decimal scaling
        • Refactor for Simplicity
      • #9 Flash Loan Vault
        • Implementing ERC3156
        • Full code for lender
        • Ex-rate calculation
    • State Inheritance Testing
    • Testing w/ Mocks
    • Yield Style Guide
    • Github Actions
    • TransferHelper.sol
    • math logic + internal fn
    • Interfaces: IERC20
  • Foundry
    • Overview
    • Importing Contracts
    • Testing
      • stdError.arithmeticError
      • assume vs bound
      • Traces
      • label & console2
      • std-storage
  • Smart Contract Security
    • Damn Vulnerable Defi
      • 1. Unstoppable
      • 2. Naive receiver
      • 3. Truster
      • 4. Side Entrance
      • 5. The Rewarder
      • 6. Selfie
      • 7. Compromised
      • 8. Puppet
      • 9. Puppet V2
      • 10 - Free Rider
    • Merkle Tree: shortened proof attack
  • Fixed-Point Math
    • AMM Math
  • Solidity Patterns
    • checks-effects-interactions pattern
    • Router // batch
    • claimDelegate: stack unique owners
    • claimDelegate: cache previous user
  • Array: dup/ascending check
  • Deployment
    • Behind the Scenes
    • Interacting with External Contracts
    • Logging, Events, Solidity, Bloom Filter
  • Misc
    • Mnemonic Phrases
    • Bidul Ideas
  • Archive
    • Brownie Framework
      • Brownie basics
        • storing wallets in .env
        • Deployment to ganache
        • Interacting with contract
        • Unit Testing
        • Testnet deployment
        • Interacting w/ deployed contract
        • Brownie console
      • Brownie Advanced
        • Dependencies: import contracts
        • helpful_scripts.py
        • verify and publish
        • Forking and Mocking
        • Mocking
        • Forking
      • Testing
      • Scripts Framework
        • deploy.py
        • get_accounts
        • deploy_mocks()
        • fund_with_<token>()
      • Brownie Networks
    • Brownie Projects
      • SharedWallet
        • Multiple Beneficiaries
        • Common Code Contract
        • Adding Events
        • Renounce Ownership
        • Separate Files
      • Supply Chain
        • ItemManager()
        • Adding Events
        • Adding unique address to each item
      • Lottery
      • Aave - Lending and Borrowing
        • Approve & Deposit
        • Borrow
      • NFT
      • Advanced Collectible
        • adv_deploy() + Testing
        • Create Metadata
        • Setting the TokenURI
    • node npm
    • Ganache
    • Truffle
    • Remix
    • Installing Env
Powered by GitBook
On this page
  • 1. Working with calldata
  • Inspiration code
  • 2. creates in-memory copy of the input calldata
  • References
  1. Yul
  2. Yul Exercises

iterate Array, Return Sum

We will examine two ways to iterate over a dynamic array passed as parameter into function, for which we must find the sum of elements.

1. Working with calldata

contract Question1 {
    function iterateEachElementInArrayAndReturnTheSum(uint256[] calldata array) external pure returns (uint256)  {
        // TODO: Iterate each element in the array using only assembly
        uint256 sum = 0;

        assembly {

            // locates the length of the array in the calldata 
            // copies it onto the stack
            let len := calldataload(0x24) 
            
            for {let i := 0} lt(i, len) {i := add(i,1)}
            {
                // update free memory pointer 
                let ptr := add(mload(0x40), mul(i, 0x20)) 
                
                // creates a new in-memory copy of the input array
                // calldatacopy(copyToMemoryLocation, copyFromCallDataLocation, copySize)
                //copyFromCallDataLocation: add(0x24, 0x20): location of len+32 bytes, to get first char
                calldatacopy(ptr, add(add(0x24, 0x20), mul(i, 0x20)), 32)
                sum := add(sum, mload(ptr))

            }
        }

        return sum;
    }
}
/** Explanation:

    First we obtain the length of the array passed: let len := calldataload(0x24)
    
     calldataload(startingOffset) loads 32 bytes starting from the specified offset in the calldata onto the stack.
     
     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).
     
     To get length of array, we want an startingOffset of 0x24 (32+4 = 36 bytes).
     Therefore, calldataload(0x24) loads the length of the array.

    Then we set up a for loop to iterate through the array elements. 
    
    Free memory pointer: let ptr := add(mload(0x40), mul(i, 0x20)) 
     mload(0x40) gives us the free memory pointer. 
     Since we need to iterate and load multiple elements in a loop, on each interation we increment the pointer by 32 bytes (0x20).
     1st element will be loaded to memory location: 0x40
     2nd element will be loaded to memory location: 0x40 + 0x20 = 0x60

    calldatacopy(copyToMemoryLocation, copyFromCallDataLocation, copySize) <=> calldatacopy(ptr, add(add(0x24, 0x20), mul(i, 0x20)), 32)
    
     calldatacopy copies the calldata starting from 'copyFromCallDataLocation', for 'copySize', into memory storing it at 'copyToMemoryLocation'
     The 1st element located 32 bytes after the length space, at add(0x24, 0x20) = 0x44
     Each element is of size 32 bytes, hence copySize = 32.
     
     mul(i, 0x20) allows the for loop to traverse down the calldata space, from element to element. 
     
 */

Inspiration code


assembly {

    //locates the length of the string in the calldata and copies it to stack
    // first 4 bytes are function selector - ignore: 0x20+4 = 36 bytes
    // first 32 bytes point to the location in calldata where the byte array begins - 0x20 = 32 bytes
    let len := calldataload(0x24) // stores size of array on init: 5
    
    // free memory pointer 
    let ptr := mload(0x40)
    
    // creates a new in-memory copy of the input array
    // calldatacopy(copyToMemoryLocation, copyFromCallDataLocation, copySize)
    //copyFromCallDataLocation: add(0x24, 0x20): location of len+32 bytes, to get first char
    calldatacopy(ptr, add(0x24, 0x20), 32)
    sum := mload(ptr)
}
  • this will return the first element in the array

2. creates in-memory copy of the input calldata

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;
    }
}
Explanation
/** Explanation:

    for (uint i = 0; i < data.length; ++i) 
     Starts a loop that iterates over each element of the array using the loop variable i. 
     The loop runs as long as i is less than the length of array.

    sum := add(sum, mload( add(add(data, 0x20), mul(i, 0x20)) )) 
      Reads the value of the i-th element of array and adds it to the sum variable
      The outermost add function is used to perform the addition, and the mload function is used to load the i-th element from memory.
    
    How do we get the memory address of the i-th element, for mload?
    add( add(data, 0x20), mul(i, 0x20) ) 
     The add and mul functions are used to calculate memory offsets.
     
     The 0x20 value is added to data to skip over the first 32 bytes (the length field), 
     and then i is multiplied by 0x20 (32 bytes) to calculate the offset of the i-th element (since each element is 32 bytes in size).

*/

create a copy of calldata in memory and worh with that.
since the Solidity for loop does not allow direct access to the elements of a calldata array.


for (uint i = 0; i < arrayM.length; ++i) 
 starts a loop that iterates over each element of the arrayM copy using the loop variable i. 
 The loop runs as long as i is less than the length of arrayM.

assembly { sum := add(sum, mload(add(add(arrayM, 0x20), mul(i, 0x20)))) } 
  reads the value of the i-th element of arrayM and adds it to the sum variable

  The outermost add function is used to perform the addition, and the mload function is used to read a 256-bit word from memory.

add(add(arrayM, 0x20), mul(i, 0x20)) 
 The add and mul functions are used to calculate memory offsets.
 The add and mul functions are used to calculate the memory address of the i-th element of the array. 
 The 0x20 value is added to arrayM to skip over the first 32 bytes (the length field), 
 and then i is multiplied by 0x20 to calculate the offset of the i-th element (since each element is 32 bytes in size).

References

Incorrect inspiration

```solidity
        for (uint i = 0; i < array.length; ++i) {
            assembly {
                let a := add(mload(0x40), mul(i, 0x20))     //memory pointer for 1st var
                calldatacopy(a, add(4, mul(i, 0x20)), 32)  //to store the first parameter in memeory location, a
                sum := add(sum, mload(a))

            }
        }
``` 
Previousread mappingNextmemory-safe

Last updated 1 year ago

https://betterprogramming.pub/solidity-tutorial-all-about-calldata-aebbe998a5fc
https://medium.com/swlh/getting-deep-into-evm-how-ethereum-works-backstage-ab6ad9c0d0bf
https://medium.com/@kalexotsu/understanding-solidity-assembly-hashing-a-string-from-calldata-fbd2ece82263
https://docs.soliditylang.org/en/v0.8.17/assembly.html
https://blog.openzeppelin.com/ethereum-in-depth-part-2-6339cf6bddb9/