Assignment - Storage Design

Hey @mars1

I’m not sure if it meets the criteria by using a mapping of a mapping to include multiple addresses for the same msg.sender…

This was not requested but the goal is not to replicate exactly what Filip does but rather to try and experiment and understand the concepts.

Mappings are the Solidity best way to store values, and should be used as much as you can.

Good job,
Dani

1 Like
  1. The array consumes more. I think it’s because it have to keep the counter too.

  2. The array does. It iterates through the array, to check for matching entities, which costs more than a direct lookup.

pragma solidity 0.8.0;

pragma experimental ABIEncoderV2;

contract C {
    
    struct Entity {
        uint data;
        address entityAddress;
    }
    
    mapping(address => Entity) mappingData;
    
    
    function addEntity(uint _data) public {// execution cost 41454
        mappingData[msg.sender].data = _data;
        mappingData[msg.sender].entityAddress = msg.sender;
    }
    
    function updateEntity(uint _data) public { // execution cost 5515 
        mappingData[msg.sender].data = _data;
    }
}


contract B {
    
    struct Entity {
        uint data;
        address entityAddress;
    }
    
    Entity[] entities;
    
    function addEntity(uint _data) public { // execution cost 62286
        entities.push(Entity(_data, msg.sender)); // 1. 62286 2. 47286  3. 47286  4. 47286  5. 47286 
    }
    
    function updateEntity(uint _data) public returns(uint) {  
        for(uint i = 0; i < entities.length; i++) { // execution 19965 update fith data.
            if(entities[i].entityAddress == msg.sender) {entities[i].data = _data; break;}
            
        }
        return entities.length;
    }
    
    function getList() public view returns(Entity[] memory) {
        return entities;
    }
}
1 Like

That is correct indeed.

Well done!

My solutions…
GasChallenge.sol (Array Version)

pragma solidity 0.8.0;

contract GasChallenge{
    
    struct Entity{
        uint data;
        address _address;
    }
    
    Entity[] public entityStructs;
    
    function newEntity(address entityAddress, uint entityData) public returns (Entity memory){
        Entity memory newEntity;
        newEntity.data = entityData;
        newEntity._address = entityAddress;
        entityStructs.push(newEntity);
        return entityStructs[entityStructs.length -1];
    }
    
    function updateEntity(address entityAddress, uint entityData) public returns (Entity memory){
        for (uint index=0;index<entityStructs.length;index++){
            if (entityStructs[index]._address == entityAddress){
                entityStructs[index].data = entityData;
                return entityStructs[index];
            }
        }
        return Entity(0,address(0x0));
    }
}

GasChallengeMapping.sol

pragma solidity 0.8.0;

contract GasChallengeMapping{
    
    struct Entity{
        uint data;
        address _address;
        bool isEntity;
    }
    
    mapping (address => Entity) entityStructs;
    //Check if entity exists already
    function isEntity(address entityAddress) private returns (bool isIndeed){
        return entityStructs[entityAddress].isEntity;
    }
    //addEntity(). Creates a new entity for msg.sender and adds it to the mapping/array.
    function addNewEntity(address entityAddress, uint entityData) public returns (bool success){
        if (isEntity(entityAddress)) revert(); //Entity already exists in mapping
        entityStructs[entityAddress].data = entityData;
        entityStructs[entityAddress].isEntity = true;
        return true;
    }
    
    //updateEntity() Updates the data in saved entity for msg.sender
    function updateEntity(address entityAddress, uint entityData) public returns (bool success){
        if (!isEntity(entityAddress)) revert();
        entityStructs[entityAddress].data = entityData;
        return true;
    }
    
}```

1. Executing NewEntity function cost approx 25000 wei more than the mapping equivalent.  I think this must be because of the need to construct the new entity in memory before pushing into the array, as well as having to read the array length at the end of the function.

2.  Executing Update Entity after adding 5 records into each Contract cost approx 20000 wei extra for array version.  This is because the code must loop through the array to identify the record to update, whereas the mapping can do it a lot quicker.
1 Like

My solution is:

pragma solidity 0.8.2;
pragma abicoder v2;
// Storage design


// first contract stores struct only in a mapping
contract StoreStruct01{
    
    // struct that needs to be stored
    struct Entity{
        uint data;
        address adrs;
    }
    
    mapping (address => Entity) public EntMap;
    
    function addEntity(address address_, uint data_) public {
        Entity memory newEntity = Entity(data_, address_);
        EntMap[address_] = newEntity;
    }
    
}

//second contract stores struct only in an array
contract StoreStruct02{
    
    // struct to be stored
    struct Entity{
        uint data;
        address adrs;
    }
    
    Entity[] public EntArr;
    
    function addEntity(address address_, uint data_) public {
        Entity memory newEntity = Entity(data_, address_);
        EntArr.push(newEntity);
    }
    
}


// Check the gas costs in the logs

To answer the questions:
1. When executing the addEntity function, which design consumes the most gas (execution cost)?

-> gas cost:
(mapping=3000000) == (array=3000000)

-> transaction gas cost:

(mapping=64645) < (array=85480)

-> execution gas cost:

(mapping=41709) < (array=62544)

2. Is it a significant difference? Why/why not?
Yes, there is a significant difference of about 30% gas cost between storing a struct into a mapping vs array. Being the latter, more costly.

3. Add 5 Entities into storage using the addEntity function and 5 different addresses. Then update the data of the fifth address you used. Do this for both contracts and take note of the gas consumption (execution cost). Which solution consumes more gas and why?

1 Like

Contract with mapping

pragma solidity 0.7.5;

contract mappingAssign {
    
    struct Entity {
    uint data;
    address _address;
    }
    
    mapping(address => Entity) Entities;
    
    // addEntity(). Creates a new entity for msg.sender and adds it to the mapping.
    function addEntity(uint _data) public returns(bool success){
        Entities[msg.sender].data = _data;
        Entities[msg.sender]._address = msg.sender;
        return true;
    }
    
    
    // updateEntity(). Updates the data in a saved entity for msg.sender
    function updateEntity(uint _data) public returns(bool success) {
        Entities[msg.sender].data = _data;
        return true;
    }
    
}

Contract with array

pragma solidity 0.7.5;
pragma abicoder v2;

contract arrayAssign {
    
    struct Entity {
    uint data;
    address _address;
    }
    
    Entity[] EntityArray;
    
    function addEntity(uint _data) public returns(bool success){
        Entity memory newEntity;
        newEntity.data = _data;
        newEntity._address = msg.sender;
        EntityArray.push(newEntity);
        return true;
    }
    
    function updateEntity(uint _index, uint _data) public returns(bool success) {
        require(EntityArray[_index]._address == msg.sender, "Only entity owner can update values.");
        EntityArray[_index].data = _data;
        return true;
    }
}
  1. The array consumes a lot more gas than mapping.
// ARRAY EXAMPLE
// Contract deployment cost 
// 264366 gas

// Cost on adding entity
// transaction cost - 83747 gas 
// execution cost - 62283 gas

// Cost on updating entity 
// transaction cost - 84049 gas 
// execution cost - 62585 gas

//---------------------------------//

// MAPPING EXAMPLE
// Contract deployment cost 
// 197669 gas

// Cost on adding entity
// transaction cost - 62818 gas
// execution cost - 41354 gas

// Cost on updating entity 
// transaction cost - 26879 gas
// execution cost - 5415 gas
  1. Updating an entity also costs more in array than in mapping, examples are in question 1.
1 Like

Here is the code I used to analyze gas costs for both addEntity and updateEntity. I mistakingly didn’t see that I needed to build 2 contracts. But anyways, the function execution cost should still reflect the difference.


pragma solidity 0.7.5;
contract CompareCost {
    
    struct Entity {
        uint data;
        address _address;
    }

    mapping(address => Entity) map;
    Entity[] public listOfEntities;

    function addEntityMap(address entityAddress, uint data) public {
        map[entityAddress].data = data;
        map[entityAddress]._address = entityAddress;
        
    }
    
    function addEntityList(address entityAddress, uint data) public {
        Entity memory newEntity;
        newEntity._address = entityAddress;
        newEntity.data = data;
        listOfEntities.push(newEntity);
    }

    function updateMap(address entityAddress, uint data) public {
         map[entityAddress].data = data;
    }
    
    function updateList(address entityAddress, uint data) public {
         for (uint i = 0; i < listOfEntities.length; i ++) {
             if(listOfEntities[i]._address == entityAddress) {
                 listOfEntities[i].data = data;
             }
         }
    }

}

For addEntity over 5 tries the average execution cost for Array is 46448 while Mapping costs 37496, which is a 19.2% difference. This is quite significant.

For Update, the worst case for Array costs 20233 while Mapping only costs 5439. I would say this is highly significant. The array will cost more because of the iteration which definitely is an O(n) operation, while maps take O(1)

1 Like

Contract with mapping

pragma solidity 0.8.0;

contract EntityMapping {
    
    
    struct entityStruct{
        uint data;
        address _address;
    }
    
    mapping(address => entityStruct) Entities;
    
    function addData(uint _data) public returns (bool){
        Entities[msg.sender].data = _data;
        Entities[msg.sender]._address = msg.sender;
        return true;
    }
    
    function updateData(uint _data) public returns(bool){
        Entities[msg.sender].data = _data;
        return true;
    }
}

Contract with array

pragma solidity 0.8.0;

contract EntityArray {
    
    
    struct entityStruct{
        uint data;
        address _address;
    }
    
    entityStruct[] entityList;
    
    function addData(uint _data) public returns (bool success){
        entityStruct memory newEntity;
        newEntity.data = _data;
        newEntity._address = msg.sender;
        entityList.push(newEntity);
        return true;
    }
    
    function updateData(uint _data) public {
        for(uint i=0; i<entityList.length; i++){
            if(entityList[i]._address == msg.sender) {
            entityList[i].data = _data; 
            break;
            }
            
        }
    }
    
}
  1. When executing the addData function the gas consumption was following:
    Contract with an array: 62607 gas
    Contract with mapping: 41678 gas

Therefore, there is a significant difference in the gas cost between storing with a mapping vs. an array.

  1. When updating the data of the 5th address, the gas consumption was following:
    Contract with an array: 19943 gas
    Contract with mapping: 5695 gas

Once again a significant difference in the gas cost, where the mapping updateData function is much more cheaper to execute.

1 Like
pragma solidity 0.8.0;


contract ArrayDesign  {
    
    struct Entity {
        uint data;
        address _address;
    }
    
    Entity[] entityArray;
    
    function addEntity(uint _data) public {
        entityArray.push(Entity(_data, msg.sender));
    }
    
    function updateEntity(uint index, uint newData) public {
        entityArray[index].data = newData;
    }
}


contract MappingDesign  {
    
    struct Entity  {
        uint data;
        address _address;
    }
    
    mapping(address => Entity) mappingDesign;
    
    function addEntity(uint _data) public {
        mappingDesign[msg.sender] = Entity(_data, msg.sender);
    }
    
    function updateEntitiy(uint newData) public {
        mappingDesign[msg.sender].data = newData;
    }
}

Here is my code. I created both contracts in the same file. When I ran the addEntity function using the array, the execution cost was 62286. When I ran it for the mapping, the execution cost was 41450. Therefore, the gas cost for the mapping was about 1/3 less.

When I ran the update function in the array, the execution cost was 6490. When I ran it for the mapping, it was 5515. The difference was much smaller. However, if I didn’t know which position in the array I had to change the data, the cost would be MUCH GREATER as I would have to go through each position and hope that I eventually found the correct index/position to make the change. This just reinforces the principle of using a mapping for less gas consumption.

1 Like

The transaction cost and the Execution cost does not change while we handle the structure as a mapping.

Storage Mapping
I am getting the costs as below for the Mapping Scenario

Add Entity - Transaction Cost - 64827 Gas
Add Entity - Execution Cost - 41955 Gas

Update Entity – Transaction Cost - 28824 Gas
Update Entity – Execution Cost – 5952 Gas
Storage_Mapping_Cost_Chart

Storage Array
While handling the struct as an array, I see a variation of the costs, as explained by the chart below.
Storage_Array_Cost_Chart

My codes are here:

Storage Mapping

//SPDX-License-Identifier: <SPDX-License>
pragma solidity 0.7.5;
pragma abicoder v2;
/**
 * @title Storage
 * @dev Store & retrieve value in a variable
 */
contract mappingStorage {

        struct Entity{
        
            uint data;
            address _address;
        }

    mapping(address => Entity) entityMap;
    
    function addEntity(Entity memory _entity ) public {
        
        entityMap[_entity._address].data = _entity.data ;
        entityMap[_entity._address]._address = _entity._address ;
        
    } 
    
    function updateEntity(Entity memory _entity ) public {
        
        entityMap[_entity._address].data = _entity.data ;
    }
    
    function getEntityData(address _address) public view returns (uint _data){
        
        return (entityMap[_address].data);
        
    }

}

Storage Array

pragma solidity 0.7.5;
pragma abicoder v2;

contract arrayStorage {

        struct Entity{
        
            uint data;
            address _address;
        }
        
        Entity[] public entityList;
        
        address[] public addressList;
        

        

    function addEntity(Entity memory _entity ) public {
        
        bool _exists = false;
        int loc = 0;
        
        (_exists, loc) = isExists(_entity._address);
       
        if (_exists == false) {
            
            entityList.push( _entity );
            addressList.push(_entity._address);
        }
        
        
    } 

     function updateEntity(Entity memory _entity ) public {
        
         bool _exists = false;
        int loc = 0;
        
        (_exists, loc) = isExists(_entity._address);
        
          if (_exists == true){
            
            entityList[uint(loc)].data = _entity.data;
            entityList[uint(loc)]._address = _entity._address;
        }
    }
    
    function getEntityData(address _address)public returns (uint _data) {
        
        bool _exists = false;
        int loc = 0;
        
        (_exists, loc) = isExists(_address);
        
        if (_exists == true){
            
            return(entityList[uint(loc)].data);
        }
        else return 0;
        
    }
    
    function isExists( address _address) public returns (bool _isExists, int _loc) {
        
        
        for ( uint i = 0; i < addressList.length ; i++ ) {
            
            if (addressList[i] == _address) {
                
                _isExists =  true ;
                _loc = int(i) ;
                return (_isExists, _loc);
            } 
            else {
                _isExists = false ;
                _loc = -1;
            }
            
        }
        
        
    } 
    
    

}
1 Like

I did them in the same contract

pragma solidity 0.8.0;
//only mapping
// addEntity()  Creates a new entity for msg.sender and adds it to the mapping/array
//updateEntity()  Updates the data in a saved entity for msg.sender
//mapping and array better- array keeps track of total number, mapping does most and cheaper

contract MappingContract {

    struct EntityStruct {
        uint data;
        address _address;
    }
    
    mapping(address => EntityStruct) private ledger;
    
    function addEntity(uint entityData) public {         
        ledger[msg.sender] = EntityStruct(entityData, msg.sender);
    }
    
    function updateEntity(uint entityData) public  {
        ledger[msg.sender].data = entityData;
    }
}

contract ArrayContract {
    
    struct EntityStruct {
        uint data;
        address _address;
    }
    
    EntityStruct[] private record;
    
    function addEntity(uint entityData) public  {
        record.push(EntityStruct(entityData, msg.sender));
    }
    
    function updateEntity(uint _index, uint newData) public {
        record[_index].data = newData;
    }   
}
  1. When executing the addEntity function, which design consumes the most gas (execution cost)? Is it a significant difference? Why/why not?
    The execution cost for the Array was 62286. The execution cost for the Mapping was 41450. The mapping was less expensive by about 1/3 .

  2. Add 5 Entities into storage using the addEntity function and 5 different addresses. Then update the data of the fifth address you used. Do this for both contracts and take note of the gas consumption (execution cost). Which solution consumes more gas and why?
    The execution cost to update the array was 6497. The execution cost to update the mapping was 5515. The update function for the mapping costs less. The array update could cost much more because the user may have to try multiple times to find the right location in the array to update.

1 Like

Mapping:

pragma solidity 0.7.5;

contract MappingExample {
    
    struct Entity{
    uint data;
    address _address;
    }
    
    mapping(address => Entity) public entityStructsMapping;
    
    function addEntity(uint _data) public {
       // if(entityStructsMapping[msg.sender]._address != 0x0000000000000000000000000000000000000000) revert("This entity exists already!");
        entityStructsMapping[msg.sender]._address = msg.sender;
        entityStructsMapping[msg.sender].data = _data;
    }
    
    function updateEntity(uint _newdata) public {
       // if(entityStructsMapping[msg.sender]._address == 0x0000000000000000000000000000000000000000) revert("This entity doesn't exist.");
        entityStructsMapping[msg.sender].data = _newdata;
    }
    
    function viewEntity(address _address) view public returns(address __address, uint _data){
        return(entityStructsMapping[_address]._address, entityStructsMapping[_address].data);
    }
}

Array:

pragma solidity 0.7.5;

contract ArrayExample {
    
    struct Entity{
    uint data;
    address _address;
    }
    
    Entity[] public EntityArray;
    
    function addEntity(uint _data) public {
        Entity memory newEntity;
        newEntity.data = _data;
        newEntity._address = msg.sender;
        EntityArray.push(newEntity);
    }
    
    function updateEntity(uint _newdata) public {
        for(uint i = 0; i < EntityArray.length; i++){
            if(msg.sender == EntityArray[i]._address) EntityArray[i].data = _newdata;
        }
    }
    
    function viewEntity(address _address) view public returns(address __address, uint _data){
        for(uint i = 0; i < EntityArray.length; i++){
            if(_address == EntityArray[i]._address) return(EntityArray[i]._address, EntityArray[i].data);
        }
        
    }
}

I have read some previous answers, and it looks like some of the previous students had more insightful things to say than me.

I wasn’t sure on the difference between transaction & execution cost, so I googled it. It looks like transaction costs include a fixed amount for all contracts, plus the mount of memory used, whereas execution cost is only for operations like add and subtract.

I believe that arrays are more expensive on all counts because organizing the array takes some memory, and because editing an entry requires the use of a for loop (unless you know the index). It looks like adding an element to an array is roughly 40% more expensive, and editing an element in an array is 120% more expensive with 5 elements. I believe the cost of finding an element in an array should be O(n), however, so the inefficiency of searching for something in an array should increase with the number of elements. I guess that’s why there are lots of sorting algorithms for arrays.

Array:
Add Entity:
|transaction cost|83672 gas |
|execution cost|62208 gas|
total: 145880
Edit Entity:
|transaction cost|45958 gas |
|execution cost|24494 gas|
total:70452

Mapping:
Add Entity:
|transaction cost|62740 gas |
|execution cost|41276 gas|
total: 104016
Edit Entity:
|transaction cost|26801 gas |
|execution cost|5337 gas|
total: 32138

1 Like

Here’s my code to test the gas cost of EXECUTION of the four functions we were told to design. The relative costs and conclusions can be found in the comments:

//SPDX-License-Identifier: GPL:3-0
pragma solidity ^0.8.0;

contract data_design_assignment{
    
    struct entity{
        address entAddress;
        uint entData;
    }
    
    mapping(address => entity) mEntitys;
    
    function madd_entity(uint _data) public returns(uint retData){
        mEntitys[msg.sender].entData = _data;
        return mEntitys[msg.sender].entData;
    }
    
    function mupdate_entity(uint _newValue) public returns(bool success){
        mEntitys[msg.sender].entData = _newValue;
        return true;
    }
}
//Cost to add to mapping: 21304, 21304, 21282, 21282, 21282
//cost to update mapping data: 5393, 5393, 5415, 5415, 5415
//cost continues to be the same depending of operation (add vs update)

contract data_design_with_array is data_design_assignment{
    
    entity[] aEntitys;
    
    function add_entity(uint _data) public returns(uint _newValue){
        entity memory newEntity = entity(msg.sender, _data);
        aEntitys.push(newEntity);
        return aEntitys[aEntitys.length - 1].entData;
    }
    
    function update_entity(uint _newValue) public returns(bool success){
        uint i = 0;
        while(!(aEntitys[i].entAddress == msg.sender)){
            i++;
        }
        if(aEntitys[i].entAddress == msg.sender){
            aEntitys[i].entData = _newValue;
            return true;
        }
        return false;
    }
//cost to add to array of addresses: 64767, 49767, 49767, 49767, 49767
//cost to update array of addresses: 9801, 11598, 13395, 15192, 16989
//Initial high cost to start array, consistent and less to add to it
//additional 1797 gas for each step through the array for updating
}
1 Like

Array Solution:
Gas costs (transaction + Execution) was higher for the first record added, but subsequent record added were less gas.

1st record added gas: 147,934
subsequent record additions : 117,934

Update entity gas: 34,968

Mapping Solutions:
Gas cost (transaction + Execution) was the same for all entries.

Add record gas: 106,264

Update entity gas: 34,304

Array Code

pragma solidity 0.8.0;

contract onlyArray {

 struct Entity{
    uint data;
    address _address;
    }

 Entity[] universeArray;
 
 function addEntity(address newAddress, uint newData) public {
     universeArray.push(Entity(newData, newAddress));
 }


function updateEntity(uint dataToUpdate, uint index) public{
    universeArray[index-1].data=dataToUpdate;
}

}

Mapping Code

pragma solidity 0.8.0;

contract onlyMapping {

struct Entity{
    uint data;
    address _address;
    }

    mapping(address=>Entity) universeMapping;
    
     function addEntity(address newAddress, uint newData) public {
         universeMapping[newAddress]=Entity(newData, newAddress);
    }  


    function updateEntity(address addressToUpdate, uint dataToUpdate) public{
        universeMapping[addressToUpdate].data=dataToUpdate;
    }

}

1 Like

Array:

contract array1 {
    
    struct Entity{
    uint data;
    address _address;
    }
    
    Entity [] public entityList;
    
    function addEntity (uint entdata) public 
    {
        Entity memory entity;
        entityList.push(Entity(entdata, msg.sender));
    }
    function updateEntity (uint data) public 
    {
        for (uint i=0; i<= entityList.length; i++){
            if (entityList[i]._address == msg.sender){
                entityList[i].data = data;
                break;
            }
        }
    }
}

Mapping:
contract mapping1 {
    
    struct Entity{
    uint data;
    address _address;
    }
    
    mapping(address => Entity) public entityStructs;
    
    function addEntity(uint entitydata) public 
    {
        entityStructs[msg.sender].data = entitydata;
    }
    function updateEntity(uint entitydata) public 
    {
        entityStructs[msg.sender].data = entitydata;
    }
}

Array:
Execution cost: 62286
Mapping:
Execution cost: 47221

There is a significant difference because the array takes more gas as it has to iterate through the array and there is more code written to find an entity. The better option is the mapping because it costs less and is easier to find an entity.

1 Like

Okay here it goes:

mappingOnly

addEntity execution cost : 41127 gas

update 5th address execution cost : 6337 gas

pragma solidity 0.8.0;

contract mappingOnly {
    
  struct Entity{
      uint data;
      address _address;
  }

  mapping(address => Entity) entityStruct;
  
  function addEntity(uint entityData)public{
      entityStruct[msg.sender].data = entityData;
      entityStruct[msg.sender]._address = msg.sender;
  }
  
  function iamEntity()public view returns(bool uAreIndeed){
      if(entityStruct[msg.sender].data == 0){
          return false;
      }
      return true;
  }
  
  function updateEntity (uint entityData) public returns(bool success){
      require(iamEntity() == true, "must be an entity to update");
      entityStruct[msg.sender].data = entityData;
       return true;
  }
  
  function getMyData ()public view returns (uint){
      return entityStruct[msg.sender].data;
  }
  
}

arrayOnly

addEntity execution cost : 62001 gas

update 5th address execution cost : 7100 gas

pragma solidity 0.8.0;

contract arrayOnly {
    
  struct Entity{
      uint data;
      address _address;
  }

  Entity [] public entityStruct;
  
  function addEntity(uint entityData)public{
      entityStruct.push(Entity(entityData, msg.sender));
  }
  
  function updateEntity (uint entityIndex, uint newData) public returns(bool success){
      require(entityStruct.length >= entityIndex, "That entity does not exist");
      entityStruct[entityIndex].data = newData;
       return true;
  }
  
}

When executing the addEntity function, which design consumes the most gas (execution cost)?
When executing tha addEntity function the expensivest design is the array only design.

Is it a significant difference? Why/why not?
It is indeed a significant difference of almost 50%, but depending on what your usecase is, that difference may not be that significant because the array offers advantages that the mapping does not, but objectively speaking the mapping solution is 50% cheaper.

update the data of the fifth address you used. Do this for both contracts and take note of the gas consumption (execution cost). Which solution consumes more gas and why?

The array solution consumes more gas, hmm without knowing the intricacies of this topic very well, i would say that the array solution is expensiver, because not only it is saving the same data as the mapping, but it is also assigning indexes to that data and sort of creating a room for that data, now i dont understand really well how this stuff works in the backend, but i guess that an array has to have a lot of hidden processes to keep things together and ordered.

oh! and also what i think makes this specific process more expensive, is that with mapping solution you kind of go straigh to the struct that you want to modify, and i think that with arrays this process is not as straight forward as with mappings, because there is not like the same direct connection that there is with mappings, with arrays you sort of have to look the trought the array untill you get to the value you need.

i know i messed it up somewhere, btw thank you @thecil for helping me with my multisignature wallet issue the other day, itried both solutions that you gave me and they obviously worked xD, and also you were right about my onlyOwner modifier being misused, and so i fixed those little details for an improved version that i made of the multisig wallet, and i might or might not have copied a lot of the things from your multisig wallet contract xD, so yes thank you for the input senior

Couple thoughts

  • Doesn’t make sense to save address _address in a map as a value since it’s already the key
  • Mapping won’t be able to just update the fifth entry in the map since it has no idea the ordering of the insertions (unless we were to maintain an auxiliary insertion array as well). But since the instructions say to only use one or the other I’ll pretend whoever calls update method within the MapTest.sol file is the fifth caller.

When executing the addEntity function, which design consumes the most gas (execution cost)? Is it a significant difference? Why/why not?

  • An add with Mapping costs 42k gas in execution cost.
  • An add with array implementation costs 66k gas in execution cost

Conclusion: An add with mapping is 36% cheaper

Add 5 Entities into storage using the addEntity function and 5 different addresses. Then update the data of the fifth address you used. Do this for both contracts and take note of the gas consumption (execution cost). Which solution consumes more gas and why?

  • An update with mapping implementation pretending the caller (msg.sender) must be the fifth address costs 6.7k in execution cost. As mentioned before if we were to assume msg.sender is not the fifth address then we would need to maintain an auxiliary array in which case storage becomes much more expensive.
  • As for the array implementation also taking in msg.sender as the argument we will assume msg.sender is the fifth caller as well. In the implementation we could either directly update the fifth element via entityStructs[4].data = inputData.

Anyway I think the question is a bit confusing and malformed. Because now in both cases array is losing but it really should be winning in the second case.

I think it would make more since for the update method to be a passed an argument like recordNumber to be updated instead of msg.sender if it’s a # caller we are interested in about

1 Like

Mapping

pragma solidity 0.8.0;

contract assignmentStorageDesignMapping {
    
    struct Entity {
        uint data;
        address _address;
   
    }

    mapping(address => Entity) public existents;
    
    function addEntity(uint data, address entityAddress) public {
        existents[entityAddress].data = data;
        existents[entityAddress]._address = entityAddress;

    }
    
    function updateEntity(address entityAddress, uint entityData) public {
        existents[entityAddress].data = entityData;
        
    }
    
}

Array

pragma solidity 0.8.0;

contract assignmentStorageDesignArray {
    
    struct Entity {
        uint data;
        address _address;
   
    }

    Entity[] existents;
    
    function addEntity(uint _data, address entityAddress) public {
        Entity memory newEntity;
        newEntity.data = _data;
        newEntity._address = entityAddress;
        existents.push(newEntity);

    }
    
    function updateEntity(uint index, uint newEntry) public {
       existents[index].data = newEntry;
    }
    
}

Mapping Gas costs
addEntity function: (1st) 41723
(2nd) 41723
(3rd) 41723
(4th) 41723
(5th) 41723

updateEntity function: 5716

Array Gas costs
addEntity function: (1st) 62631
(2nd) 47631
(3rd) 47631
(4th) 47631
(5th) 47631

updateEntity function: 6468

  1. The array consumes more gas which amounts to 62631 in comparison to the mapping which is only 41723. Yes for me the amount is significant, if you are concerned with you funds not to get wasted by gas fees then its better to use mapping instead of array, but again it all depends on a case to case basis depending on the use case of your smart contract since both of the array and mapping has its own pros and cons.

  2. The array consumes more gas with the first execution of addEntity function at 62631 and the next 4 consecutive addEntity execution at 47631 and when I update the fifth address it now costs 6468. meanwhile mapping gas costs on addEntity function is only 41723 the same amount is consistent in every execution from first up to the fifth and when I update the fifth address it only 5716.
    Mapping costs cheaper its because its more direct to the point like the address is already pointing directly to the struct, unlike in array that it will still have to iterate the list of items to find the entity which makes it also more expensive when it comes to gas cost.

1 Like

Here are my two contracts:

pragma solidity 0.8.0;

contract MappingStorage { 
    
    struct Entity {
        uint data;
        address _address;
    }
    
    mapping (address => Entity) entityMapping;

    function addEntity(uint _data) public returns(bool success) {
        entityMapping[msg.sender] = Entity(_data, msg.sender);
        return true; 
        
    }

    function updateEntity(uint _data) public returns(bool success) {
        entityMapping[msg.sender].data = _data;
        return true;
       
    }
}

contract ArrayStorage { 
    
    struct Entity {
        uint data;
        address _address;
    }
    
    Entity[] entityArray;
    
    function addEntity(uint _data) public returns(bool success) {
        entityArray.push(Entity(_data, msg.sender));
        return true;
       
    }
    
    function updateEntity(uint _index, uint _data) public returns(bool success) {
        entityArray[_index].data = _data;
        return true;
        
    }
}
  1. When I executed the addEntity function the design that consumed most gas was the mapping, because the mapping execution cost was 41649 and the array execution cost was 62485.

  2. After I added 5 entities into the storage, I updated the data on the fifth address and noticed the mapping was less because its execution’s cost was 5717 and the array cost came out to be 6692. The mapping came out to be less perhaps because it doesn’t need to be indexed?

1 Like

When executing the addEntity function, which design consumes the most gas (execution cost)?
Is it a significant difference? Why/why not?
The array method uses slightly more gas when adding. I wouldnt consider it a significant amount more. I think it is just because k/v pairs are slightly less write intensive.

Which solution consumes more gas and why?
The array solution uses more overall, especially when it comes to updating. I think this is due to how you need to loop through it, more loops more gas!

code:

pragma solidity 0.8.0;

contract ArrayStorage {
    
    struct Entity {
        uint data;
        address _address;
    }
    
    Entity[] entities;
    
    function addEntity(uint data) public returns (bool success) {
        Entity memory tempEntity;
        tempEntity.data = data;
        tempEntity._address = msg.sender;
        entities.push(tempEntity);
        return true;
    }
    
    function updateEntity(uint data) public returns (bool success) {
        for (uint i = 0; i < entities.length; i++) {
            if (entities[i]._address == msg.sender) {
                entities[i].data = data;
                return true;
            }
        }
        return false;
    }
    
    function getEntities() public view returns (Entity[] memory) {
        return entities;
    }
}

pragma solidity 0.8.0;

contract MappingStorage {
   
    struct Entity {
        uint data;
        address _address;
    }
    
    mapping(address => Entity) entities;
    
    function addEntity(uint data) public returns (bool success) {
        entities[msg.sender].data = data;
        entities[msg.sender]._address = msg.sender;
        return true;
    }
    
    function updateEntity(uint data) public returns (bool success) {
        entities[msg.sender].data = data;
        return true;
    }
    
    function getEntityData() public view returns (uint data) {
        return entities[msg.sender].data;
    }
}
1 Like