Assignment - Storage Design

2 contracts as per below

1. arrayStorage contract:

pragma solidity 0.8.0;

contract arrayStorage {
    
    struct Entity{
        uint data;
        address _address;
    }
    
    Entity [] public entityArray;
    
    function addEntity(uint _data) public returns (bool entityAdded) {
        Entity memory newEntity;
        newEntity._address = msg.sender;
        newEntity.data = _data;
        entityArray.push(newEntity);
        return true;
    }
    // assuming the index of the address to be updated is known
    function updateEntity(uint index, uint _data) public returns (bool success) {
        require(entityArray[index]._address == msg.sender, "only address owner can update data");
        entityArray[index].data = _data;
        return true;
    } 
    
}

2. mappedStorage contract:

pragma solidity 0.8.0;

contract MappedStorage {
    
    struct Entity{
        uint entityData;
        address entityAddress;
    }
    
    mapping (address => Entity) public mappedEntities;
    
    function addEntity(uint entityData) public returns (bool entityAdded) {
        Entity memory newEntity;
        newEntity.entityData = entityData;
        newEntity.entityAddress = msg.sender;
        mappedEntities[msg.sender] = newEntity;
        return true;
    }
    
    function updateEntity(uint newData) public returns (bool entityUpdated) {
        mappedEntities[msg.sender].entityData = newData;
        return true;
        
    }
    
}

Gas cost summary:

1. Adding an entity:
Array: 1st addition: 88489, following 4 additions 71389 each, total 374045 gas
Mapping: all 5 additions cost 66375 of gas, total 331765
Conclusion: Mapping is cheaper: for the first addition mapping spends aprox. 25% less, the total gast cost of adding 5 entities differs in favour of mapping of aprox. 11,3%. The larger difference regarding the first addition is due to the fact that array addition gas cost drop as of the second addition while the gas cost for mapping additions remains the same.

2. Updating the 5th entity:
Array: 31725
Mapping: 26943
Conclusion: updating an entity of a mapping is aprox 15% cheaper than updating an array entity.

Why is mapping cheaper than array:
“An Array in Solidity is basically a struct with this structure”

struct Array{
  mapping(uint => someType) items;
  uint length;
}

(source: (https://ethereum.stackexchange.com/questions/37549/array-or-mapping-which-costs-more-gas).
It appears that adding this additional struct code and mapping for processing an array in solidity adds additional load to the execution of the code, hence the higher cost of arrays.

Doubt:
Comparing my code to my fellow colleagues in the forum I’ve noted that their updating gas cost is much cheaper than mine but I couldn’t figure out why that is. I’d therefore greatly appreciate any hints from your end to figure this out.
Thanks for your help!
Yestome.

1 Like

Hello there, I’ll leave my code and answers down below.

ONLY ARRAY

pragma solidity 0.8.0;

contract onlyArray{
    
    struct Entity{
        uint data;
        address _address;
    }
    
    Entity [] entities;
    
    function addEntity(uint data) public {
        entities.push(Entity(data, msg.sender));
    }
    
    function updateEntity(uint data) public {
    
    for(uint i=0; i < entities.length; i++){
        if(entities[i]._address == msg.sender) entities[i].data = data;
    }
  }
}
---------------------------------------------------------------------------------

ONLY MAPPING

pragma solidity 0.8.0;

contract onlyMapping{
    
    struct Entity{
        uint data;
        address _address;
    }
    
    mapping(address => Entity) entity;
    
    function addEntity(uint data) public {
        entity[msg.sender] = Entity(data, msg.sender);
    }
    
    function updateEntity(uint data) public {
        entity[msg.sender].data = data;
    }
}

addEntity();
                    |        onlyMapping           |         onlyArray
---------------------------------------------------------------------------------
ExecutionCost       |         46,142 gas            |         68,278 gas

The onlyArray contract consumes 22,136 more gas than the onlyMapping contract, it consumes around 30% more gas.

updateEntity();
                    |        onlyMapping           |         onlyArray
---------------------------------------------------------------------------------
ExecutionCost       |         26,719 gas            |         42,246 gas

The onlyArray contract consumes 53,471 more gas than the onlyMapping contract, it consumes almost 40% more.

After executing addEntity() we can clearly see that the onlyMapping contract more gas efficient than the onlyArray contract. This is most likely because it takes more space to save data in an array than in a mapping, and therefore the more data an array contains the more expensive it gets to iterate through it in order to find a specific address, it is due to this reasoning that we say that for this specific use-case it is a lot more cost-efficient to use mappings to find certain data from specific addresses.

1 Like

1. Only Array

pragma solidity 0.8.1;

contract ArrayGas {
 
 struct Entity {
     uint data;
     address _address;
 }
 
 Entity[] public entityArray;
 
 function addEntity(uint _data) public returns(Entity memory) {
     for(uint i; i < entityArray.length; i++) {
         if(entityArray[i]._address == msg.sender) revert();
     }
     entityArray.push(Entity(_data, msg.sender));
     return entityArray[entityArray.length - 1];
 }
 
 function uptadeData(uint _data) public returns(bool) {
     for(uint i; i < entityArray.length; i++) {
         if(entityArray[i]._address == msg.sender) entityArray[i].data = _data;
     }
     return true;
 } 
}

2. Only Mapping

contract MappingGas{
    
    struct Entity{
        uint data;
        address _address;
    }
    
    mapping(address => Entity) public entityMap;
    
    function addEntity(uint _data) public returns(Entity memory) {
        entityMap[msg.sender] = Entity(_data, msg.sender);
        return entityMap[msg.sender];
    }
    
    function updateEntity(uint _data) public returns(Entity memory) {
        entityMap[msg.sender].data = _data;
        return entityMap[msg.sender];
    }
}

Gas Consumption

                     onlyArray     |    onlyMapping
--------------------------------------------------------
addEntity()    |       89640       |       66614 
--------------------------------------------------------
updateEntity() |       31820       |       29636 

Conclusion:
As the array consumes mores space, the transactions are more expensive.

1 Like

The result of my Storage Design is as follows.

Deploying both contracts cost more gas than calling its functions.

The initial addEntity function for our array contract cost more in gas than the proceeding four calls to “addEntity”.

For every additional call to the “addEntity” function in our array contract, the gas consumption was the same price and lower than the initial call.

updating our array at its fifth index is significantly cheaper than adding to our array.

While for mapping deploying the contract is also more expensive than calling its function. It’s still the least expensive option than our array contract.

every call to the “addEntity” function costs the same amount of gas and is cheaper than the array contracts calls to the “addEntity” function.
The updateEntity is also significantly cheaper than all the array contracts function calls and the mapping “addEntity” function calls.

It’s the Array contract that consumes more gas, although they both write to storage, mapping has the benefit of directly accessing the values associated with its key instantly, while our array has to reference and traverse first.

1 Like

Here are my findings. I find it interesting that the cost for adding with mapping is always constant which is as expected. But the cost for adding in the array is slightly bigger the first round because the array needs to be initialized. The update shows a bigger difference as expected, having more addresses would only increase this difference.

Array:
add: 88203, 71103, 71103, 71103, 71103
update (5th address): 42246

Mapping:
Mapping: 66058 (5x)
update (5th address): 28987

OnlyArray.sol

contract OnlyArray {
    
    Entity[] storageArray;
    
    struct Entity{
        uint data;
        address _address;
    }
    
    function addEntity(uint data) public {
        
        Entity memory en = Entity(data, msg.sender);
        storageArray.push(en);
    } 
    
    function UpdateEntity(uint data) public {
        
        for (uint i = 0; i < storageArray.length; i++)
        {
            if(storageArray[i]._address == msg.sender)
            {
                storageArray[i].data = data;
            }
        }
    }
    
}

OnlyMapping.sol

contract OnlyMapping {
    
    mapping(address => Entity) storageMap;
    
    struct Entity{
        uint data;
        address _address;
    }
    
    
    function addEntity(uint data) public {
        
        storageMap[msg.sender]._address = msg.sender;
        storageMap[msg.sender].data = data;
    } 
    
    function UpdateEntity(uint data) public {
        
        if(storageMap[msg.sender]._address == msg.sender)
        {
            storageMap[msg.sender].data = data;            
        }
    }
    
}
1 Like

Also, running:

storageArray.push(Entity(data, msg.sender));

instead of:

Entity memory en = Entity(data, msg.sender);
storageArray.push(en);

saves you 13 gas :slight_smile:

1 Like

Here’s my code, thanks in advance for feedback!

pragma solidity 0.8.0;

import "./_Owners";
contract mappingSolution is Owner{
    struct Entity{
        uint data;
        address _address;
    }
    
    event BusinessCreated(address _address, uint _data);
    mapping(address => Entity) public Business;
    
    function addEntity(address _address_, uint _data) public isOwner returns(bool){
    Business[_address]._address = _address_;
    Business[_address].data = _data;
    emit BusinessCreated(_address_, _data);
    return true;
    }
    
    function updateEntity(uint newData) internal isOwner returns(bool){
        Business[msg.sender].data = newData;
        return true;
    }
}```

pragma solidity 0.8.0;

import "./_Owner";
contract arraySolution is Owner{
    struct Entity{
        uint data;
        address _address;
    }
    event businessCreated(address _address_, _data_);
    Entity [] public Businesses;
    
    function addEntity(address yourAddress, uint yourData) public isOwner returns(bool){
        Entity memory localArray;
        localArray.data = yourData;
        localArray._address = yourAddress;
        Businesses.push(localArray);
        emit businessCreated(yourAddress, yourData);
        return true;
    }
    
    function updateEntity(uint newData) internal isOwner returns(bool){
        Businesses[msg.sender].data = newData;
        return true;
    }
}
1 Like

This is my solution:

pragma solidity 0.8.0;

contract StorageArray {
    
    struct Entity {
        uint data;
        address _address;
    }
    
    Entity[] public entities;

    function addEntity(uint _data) public returns(Entity memory) {
        Entity memory newEntity;
        newEntity.data = _data;
        newEntity._address = msg.sender;
        entities.push(newEntity);
        return entities[entities.length - 1];
    }
    
    function updateData(uint _index, uint _data) public returns(bool success) {
        entities[_index].data = _data;
        return true;
    }
    
}

contract StorageMapping {
    
    struct Entity {
        uint data;
        address _address;
    }
    
    mapping(address => Entity) public entities;
    
    function addEntity(uint _data) public returns(bool success) {
        entities[msg.sender].data = _data;
        entities[msg.sender]._address = msg.sender;
        return true;
    }
    
    function updateData(uint _data) public returns(bool success) {
        entities[msg.sender].data = _data;
        return true;
    }
}

Question 1 When executing the addEntity function, which design consumes the most gas (execution cost)? Is it a significant difference? Why/why not?
Question 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?

Answer 1: The array storage consumes more gas because we keep adding new entity to the list and data gets bigger, on other hand if we use mapping storage, mapping will not get bigger because its a key pair value, we dont need to loop though the mapping, well cant loop the mapping, in this case mapping consumes less gas than array storage
Answer 2: Array storage consumes more gas 389,046 execution cost for adding 5 entities and updating the last one. When mapping storage had 358,419 execution cost for adding 5 entities and updating the last added one. If data in the array keeps growing it will be very costly to update the specific data in the array, because first we need to know which data to update for that we need to loop. For mapping we dont need to loop and we can always update the data easily by key

1 Like

Hiya,

SimpleMapping Contract:

pragma solidity 0.8.0;

contract simpleMapping{

   struct Entity{
    uint data;
    address entityAddress;
    }

    mapping(address=>uint) public entityMapping;

    // add msg.sender address as key and data as value
    function addEntity(uint _data) public returns(uint data){

        entityMapping[msg.sender] = _data;

        return(entityMapping[msg.sender]);
    }

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

}

Stats
Add Entity
gas = 80000000 gas
transaction cost = 80000000 gas
execution cost = 44188 gas

Update Entity
gas = 80000000 gas
transaction cost = 80000000 gas
execution cost = 26949 gas

Updating 5th entry -
execution cost = 26949 gas

Simple Array Contract

pragma solidity 0.8.0;

contract simpleArray{

   struct Entity{
    uint data;
    address entityAddress;
    }

    Entity[] public entityArray;

    // add msg.sender address into the array with the input data
    function addEntity(uint _data) public returns(uint index){
        Entity memory newEntity;
        newEntity.data = _data;
        newEntity.entityAddress = msg.sender;

        entityArray.push(newEntity);
        return(entityArray.length-1);
    }

    // loop through array and update data for msg.sender
    function updateEntity(uint _data) public returns(bool success){
        for(uint i=0;i <= entityArray.length-1;i++){
            if(entityArray[i].entityAddress == msg.sender){
                entityArray[i].data = _data;
                return true;
            }
        }
    }

}

Stats

Add Entity
gas = 80000000 gas
transaction cost= 80000000 gas
execution cost = 71679 gas

Update Entity
gas = 80000000 gas
transaction cost = 80000000 gas
execution cost = 37432 gas

Updating 5th entry = 43116 gas

So the simpleArray contract consumes more gas for both addEntity and updateEntity.
The array contract requires more gas because the addEntity requires more lines of code and updateEntity consumes more gas because it has to loop through the array to find the address.

Why is my transaction cost always 80000000 gas, it seems high? I see others here have varying transaction costs…

1 Like

These are my contracts:

Map Storage

pragma solidity 0.8.0;

contract MapStorage {
    
    struct Entity {
        uint data;
        address _address;
        bool _isEntity;
    }
    
    mapping(address => Entity) public entityMap;
    
    function addEntity(uint data) public returns(bool){
        entityMap[msg.sender] = Entity(data, msg.sender, true);
        return true;
    }
    
    function addEntity1(uint data) public returns(bool){
        entityMap[msg.sender]._address = msg.sender;
        entityMap[msg.sender].data = data;
        entityMap[msg.sender]._isEntity = true;
        return true;
    }
    
    function updateEntity(uint newValue) public returns(bool){
        entityMap[msg.sender].data = newValue;
        return true;
    }
}

Array Storage:

// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.0;

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

    function addEntity(uint data) public returns(bool){
        entityList.push(Entity(data, msg.sender));
        return true;
    }
    
    function updateEntity(uint newValue) public returns(bool) {
        for(uint i = 0; i < entityList.length; i++) {
            if(entityList[i]._address == msg.sender) {
                entityList[i].data = newValue;
                return true;
            }
        }
        return false;
    }
}

Questions & Answers

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

Array execution cost: 71311
Map execution cost: 66609 (this is the cost for addEntity. I have also tested another function addEntity1 where the cost seems to be a little higher and depends on the number of elements in the struct).

The Array consumes about 6% more gas.

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?

Array: 44858 (starts from 31573 for the 1st element and is increased with the index of the element that update)
Map: 26921 (constant cost)

Explanation: The looping over the previous elements in the array, until we find our element causes the additional “search” cost. With the map we have direct access to the element that we need (although I have not added verification in the Map that the element does indeed exist) …

Gerald

1 Like

Hey @Jazmin

Why is my transaction cost always 80000000 gas, it seems high? I see others here have varying transaction costs…

The amount of gas spent in a transaction depends on the amount / type of code executed in the transaction itself.
What you need to consider it the “execution cost”.

Cheers,
Dani

ArrayEntity.sol

pragma solidity 0.7.5;

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

MappingEntity.sol

pragma solidity 0.7.5;

contract MappingEntity{
    
    struct Entity{
        uint data;
        address _address;
    }
    
    mapping(address => Entity) entity;
    
    function addEntity(uint _data)public{
        entity[msg.sender] = Entity(_data,msg.sender);
    }
    
    function updateEntity(uint _data)public{
        entity[msg.sender].data = _data;
    }
}

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

Array does cost more gas. Also, it’s nearly 25% higher than that of mapping, which is a significant difference. I think the reason could be that the array has to look for metadata as well, which can be maintaining length, validating index bounds, etc.

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?

Array costs more gas here than maps. The significant difference is around 35%. The reason could be: a compiler has to iterate through an array to update an element.
Generally, an update in an array is an O(n) operation while the same in mapping is O(1).

I’ve got a question here: 1st push() in array costs me 88012 gas and 2nd push costs me 70912 gas. Why the difference is significant here?

addEntity array first call: 88190 gas / subsequent calls: 71090 gas
addEntity mapping first call: 66054 gas / subsequent calls: 66054 gas

==================================

5 x addEntity + 1 updateEntity:

mapping 66054 + 66054 + 66054 + 66054 + 66054 + 26719 = 356,989
array 88190 + 71090 + 71090 + 71090 + 71090 + 41969 = 414,519

==================================

Seems like the mapping is more efficient on every operation probably has to do with the internals of the mapping vs. the array. The difference is significant in my opinion. Perhaps the mapping pre-allocates a certain number of slots which makes it more efficient to add new items?

pragma solidity 0.8.0;

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

    Entity[] items;

    function addEntity(uint _data) public
    {
        items.push(Entity( {data: _data, _address : msg.sender}));
    }
    
    function updateEntity(uint _data) public
    {
        for(uint i = 0; i< items.length; i++)
        {
            if ( items[i]._address == msg.sender )
            {
                items[i].data = _data;
                break;
            }
        }
    }
}
pragma solidity 0.8.0;

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

    mapping(address => Entity) Items;

    function addEntity(uint _data) public
    {
        Items[msg.sender] = Entity( {data: _data, _address : msg.sender});
    }
    
    function updateEntity(uint _data) public
    {
        Items[msg.sender].data = _data;
    }
}
1 Like

Hey @szaslavsky thats a good observation. Yeah mappings are always less costly in comparison to arrays. If you can solve somethings with a mapping then you should always do so. Arrays are very handy for iteration which in some cases you may need it depends on the problem at hand. Read these two posts they have goof information on this topic

https://ethereum.stackexchange.com/questions/37549/array-or-mapping-which-costs-more-gas/37594

https://ethereum.stackexchange.com/questions/2592/store-data-in-mapping-vs-array

1 Like

program with mapping…

pragma solidity 0.7.5;
pragma abicoder v2;

contract datamapping {
    
    struct Entity{
        uint data;
        address _address;
    }
    
    mapping (address => Entity) EntityKey;

    function addEntity(uint value) public returns (bool) {
        
        EntityKey[msg.sender].data = value;
        EntityKey[msg.sender]._address = msg.sender;
        return true;
    }

    function UpdateEntity(uint value) public returns (bool){
        
        if (EntityKey[msg.sender]._address != address(0)) { 
            EntityKey[msg.sender].data = value;
            return true;
        }
        return false;
    }

    
}

program with array…

pragma solidity 0.7.5;
pragma abicoder v2;

contract dataarray {
    
    struct Entity{
        uint data;
        address _address;
    }
    
    Entity[] public EntityKey;

    function addEntity(uint value) public returns (bool) {
        
        Entity memory new_entry;
        new_entry.data = value;
        new_entry._address = msg.sender;
        
        if (!isEntity(msg.sender)) EntityKey.push(new_entry);
        //EntityKey[msg.sender]._address = msg.sender;
        return true;
    }

    function UpdateEntity(uint value) public returns (bool){
        
        if (isEntity(msg.sender)) { 
            uint location = EntityLoc(msg.sender);
            EntityKey[location].data = value;
            return true;
        }
        return false;
    }

    function isEntity(address check) private view returns (bool) {
        
        uint length = EntityKey.length;    
        if (length > 0){
            for (uint i=0;i < length; i++){
                if (EntityKey[i]._address == check) {
                    return true;
                }
            }
        }
        
        return false;
    }
    
    function EntityLoc(address check) private view returns (uint) {
        
        uint length = EntityKey.length;    
        if (length > 0){
            for (uint i=0;i < length; i++){
                if (EntityKey[i]._address == check) {
                    return i;
                }
            }
        }
        
        return 0;
        
    }
    
}

For all cases the gas execution cost higher , sometimes substantially for struct array contract.

This is because I had to create a function to look through the array to make sure Entity existed in the case of an update, and make sure it did NOT exist in the case of Adding.

For mapping solution:

#1 Doesn’t matter if entity already existing for adding, it will just override the existing entry
#2 For case of adding it’s easy to see if entry did not exist with this 1 line:

EntityKey[msg.sender]._address != address(0)

If address is not set, then it was never added.

1 Like

Solution using mappings

pragma solidity 0.8.0;

contract EntityMapping {

    struct Entity{
        uint data;
        address _address;
   }

  mapping (address => Entity) public Entities;

  function addEntity(uint _data) public returns(bool success) {
    //execution cost 66260
    Entities[msg.sender].data = _data;
    Entities[msg.sender]._address = msg.sender;
    return true;
  }

  function updateEntity(uint _data) public returns(bool success) {
    //execution cost 26933
    Entities[msg.sender].data = _data;
    return true;
  }
}

Solution using arrays

pragma solidity 0.8.0;

contract EntityWithArray {

    struct Entity{
        uint data;
        address _address;
   }

  Entity[] EntityArray;

  function addEntity(uint _data) public returns(bool success) {
    //execution cost 71389
    Entity memory newEntity;
    newEntity.data = _data;
    newEntity._address = msg.sender;
    EntityArray.push(newEntity);
    return true;
  }

  function updateEntity(uint _data) public returns(bool success) {
    //execution cost 42191
    for (uint i = 0; i < EntityArray.length; i++) {
        if(EntityArray[i]._address == msg.sender) {
            EntityArray[i].data = _data;
            return true;
        }
    }
      
  }
}

For adding new entities the cost for mappings are about 8% cheaper than for arrays. But updating entities is 57% cheaper for mappings than for arrays. This is due to the fact that we need to loop through the array in order to find the address msg.sender.

1 Like

The Array Function uses more gas than the Mapping function as the array function uses more memory to store the struct in the array.

Here is the array contract :

pragma solidity 0.7.5;
contract StorageDesign_Array {



   

  struct Entity{
    uint data;
    address entityAddress;
  }
    
    Entity[] public Entities;
    
    event noAddress( string message);
    
function addEntity(uint _data) public returns (bool success) {
  
    Entity memory newEntity;
    newEntity.data = _data;
    newEntity.entityAddress = msg.sender;
    Entities.push(newEntity);
    return true;
    
}

function updateEntity(uint _data) public returns (bool success){
    uint count;
    for (count = 0; count<Entities.length; count++) {
          
     if (Entities[count].entityAddress == msg.sender) {
         
    Entities[count].data = _data;
    return true;
     }
     
    else 
    
    emit noAddress("Address does not exist!");
     
}

}

}

and here is the mapping function :

pragma solidity 0.7.5;
contract StorageDesign {



   

  struct Entity{
    uint data;
    address entityAddress;
  }
    
     mapping(address => Entity) public Entities;

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

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



}
2 Likes

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

The array storage design consumes the most gas per execution. For the first execution (67888 gas), there is a difference of 22136 gas compared to the mapping solution (45752 gas). However, on subsequent executions the execution cost lowers to 50788 gas, a difference of 5036 gas. This difference is significant, especially if this function were to be called often.

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 array design solution consumes more (58966 gas) compared to the mapping solution (43439 gas) because the update logic needs to iterate the entire list before performing the update. As a result, the mapping design solution is more efficient becaue it can performing a direct look up of the entity by the provided address without having to iterate.

Contracts

StorageMapping.sol

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.6 <0.9.0;

contract Wallet {

    struct Entity {
        uint data;
        address _address;
    }
    
    mapping(address => Entity) public entities;
    
    // addEntity - Creates a new entity for msg.sender and adds it to the mapping.
    // execution cost =  45752 gas (all executions)
    function addEntity() public {
        entities[msg.sender] = Entity(0, msg.sender);
    }

    // updateEntity - Updates the data in a saved entity for msg.sender
    // execution cost = 43439 gas
    function updateEntity() public {
        entities[msg.sender].data = 1;
    }

}

StorageArray.sol

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.6 <0.9.0;

contract Wallet {

    struct Entity {
        uint data;
        address _address;
    }
    
    Entity[] public entities;
    
    // addEntity - Creates a new entity for msg.sender and adds it to the array.
    // execution cost = 67888 gas (first execution) 50788 gas (after)
    function addEntity() public {
        entities.push(Entity(0, msg.sender));
    }

    // updateEntity - Updates the data in a saved entity for msg.sender
    // execution cost = 48338 gas (1 entity) - 58966 gas (5 entities - updating the last element)
    function updateEntity() public {
        for (uint i = 0; i < entities.length; i++) {
            if (entities[i]._address == msg.sender) {
                entities[i].data = 1;
            }
        }
    }

}
2 Likes

Contract One (Array Implementation)

pragma solidity 0.7.5;

contract contract_one{
    
    struct Entity{
        uint data;
        address _address;
    }
    
    Entity[] public entities;
    
    function addEntity(uint data, address user_address) public {
        entities.push(Entity(data, user_address));
    }
    
    function updateEntity(uint index, uint _data) public {
        require(entities[index]._address == msg.sender, "You are not authorized to access the data.");
        entities[index].data = _data;
    }
}

Contract Two (Mapping Implementation)

pragma solidity 0.7.5;

contract contract_two{
    
    struct Entity{
        uint data;
        address _address;
    }
    
    mapping(address => Entity) public entities;
    
    function addEntity(uint data, address user_address) public {
        entities[msg.sender] = Entity(data, user_address);
    }
    
    function updateEntity(uint _data) public {
        entities[msg.sender].data = _data;
    }
}

Discussion

  1. For contract_one, executing the ‘addEntity’ function costs 88460, whereas, executing the same function in contract_two costs 66324. Here it is clear that array as a storage design requires the most gas for execution. In comparison, an array costs 28.6% more to execute than a mapping to store elements. This percentage difference becomes significant when the number of elements in the array grows substantially. Therefore, the viable storage design option for using a struct such as ‘entity’ would be a mapping.

  2. When updating an entity’s data, the execution cost of using an array is 31179, and using a mapping costs 26541. An array design solution has a higher gas consumption than a mapping, with the difference being 16%. The underlying reason is that when updating an element in an array, the EVM has to store and perform additional operations in the stack, compared to a mapping.

2 Likes

Here’s my code

pragma solidity 0.8.4;

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

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

}


contract ArrayStorage {

    struct Entity {
        uint data;
        address _address;
    }

    Entity[] entityList;

    function addEntity(uint _data) public returns (uint) {
        for (uint i = 0; i < entityList.length;i++) {
            if (entityList[i]._address == msg.sender) {
                require(false,"Entity already exists");
            }
        }
        entityList.push(Entity(_data, msg.sender));
        return entityList.length;
    }
    
    function updateEntity(uint _data) public returns (bool success) {
        for (uint i = 0; i < entityList.length; i++) {
            if (entityList[i]._address == msg.sender) {
                entityList[i].data = _data;
                return true;
            }
        }
        return false;
    }
    
}

Results:

MappingStorage.addEntity execution cost 43999 gas
MappingStorage.updateEntity execution cost 26933 gas
Update 5th address execution cost 26933 gas

ArrayStorage.addEntity execution cost 88639 gas
ArrayStorage.updateEntity execution cost 31563 gas
Update 5th address execution cost 82167 gas

Analysis:
Mapping took much less gas (half as much to add, 2/3 as much to update), and it took the same gas to update the 5th address as the first.

ArrayList
Adding and updating took more gas, and it took almost twice as much gas to update the 5th address as the first.

Overall, much less efficient using array as mapping.
Partly because of extra code to iterate the array and to determine if the address already exists, which mapping does not require.

1 Like