Assignment - Storage Design

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

Mapping: 66’058 per Entity | cost all 5 = 330’290
Array: 88’290 after that 71’190 per Entity | cost all 5 = 373’050
The Array Solution is roundabout 13% more expensive for all 5 Operations than the Mapping version.
This is quite alot considering there not beeing a need to do more than save and update.


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?

Total Cost
Mapping: 592’472
Array: 654’822
Update Cost:
Mapping: 26’7’19
Array: 29’134
Since we only replace the Data in a Entity and do not delete anything the operation is pretty simple, but the Array version is still 9% more Expensive.
I guess its due to the simple Operation producing more Effort within an Array than within a Mapping.

Mapping Contract

pragma solidity 0.8.0;

contract storageDesignMapping {

    struct Entity{
        uint data;
        address _address;
    }
    
    mapping (address => Entity) Entities;
            
    function addEntity (uint data) public {
        Entities[msg.sender].data = data;
        Entities[msg.sender]._address = msg.sender; 
    }

    function updateEntity (uint data) public {
        Entities[msg.sender].data = data;
    }
} 

Array Contract

pragma solidity 0.8.0;

contract storageDesignArray {

struct Entity{
    uint data;
    address _address;
}

Entity [] public Entities;

    //addEntity(). Creates a new entity for msg.sender and adds it to the mapping/array.
    function addEntity (uint data) public {
        Entity memory addedEntity;
        addedEntity.data = data;
        addedEntity._address = msg.sender;
        Entities.push(addedEntity);    
    }
    
    //updateEntity(). Updates the data in a saved entity for msg.sender
    function updateEntity (uint _index, uint data) public {
        Entities[_index].data = data;
    }
}
1 Like

Gas cost comparison for different types of storage :

-> Array Storage ::

  • Deployment cost -> 206294 wei
  • addEntity cost -> 88184 wei
  • updateEntity cost -> 41382 wei

-> Mapping Storage ::

  • Deployment cost -> 185191 wei
  • addEntity cost -> 66051 wei
  • updateEntity cost -> 26631 wei

There are two operations in question here -> create and update. For both the operations, the solution using array to store the struct consumes more data as the number of steps required to conduct the operations is more. For addEntity function, we create the struct and then proceed to push the struct to the array. For updateEntity, we loop through the array to find the struct containing the address of the message sender and then update the data.

2 Likes

89024 gas for the array implementation vs 66888 gas for the mapping implementation. About 33% more gas for the array implementation which I would consider to be quite significant.

29336 gas for the array implementation vs 26921 gas for the mapping implementation. Not exactly sure why, has something to do with how these data structures are stored in memory I guess?

Both my contracts work in terms of functionality altough I can’t see the gas costs because remix shows just pending. How to fix this?
Transaction_pending

Mapping contract:

contract storageDesign1 {
    
    struct Entity{
        uint data;
        address _address;
    }
    
    mapping (address => Entity) entityMapping;
    
    function addEntity (uint _data) public returns (uint, address) {
        Entity memory newEntity;
        newEntity.data = _data;
        newEntity._address = msg.sender;
        entityMapping [msg.sender] = newEntity;
        return (entityMapping [msg.sender].data,  entityMapping [msg.sender]._address);
    }
    
    function updateEntity (uint _newData) public returns (uint, address) {
        entityMapping [msg.sender].data = _newData;
        return (entityMapping [msg.sender].data,  entityMapping [msg.sender]._address);
    }
    
    function getEntity () public view returns (uint, address) {
        return (entityMapping [msg.sender].data, entityMapping [msg.sender]._address);
    }

}

Array contract:

contract storageDesign2 {
    struct Entity{
        uint data;
        address _address;
    }
    
    Entity [] entityArray;
    
    function addEntity (uint _data) public returns (uint, address) {
        Entity memory newEntity;
        newEntity.data = _data;
        newEntity._address = msg.sender;
        entityArray.push (newEntity);
        return (entityArray [entityArray.length -1].data, entityArray [entityArray.length -1]._address);
    }
    
    function updateEntity (uint _newData) public {
        for (uint i = 0; i < entityArray.length; i++) {
            if (entityArray [i]._address == msg.sender) {
                entityArray [i].data = _newData;
            }
        }
        
    }
    
    function getEntity () public view returns (Entity [] memory) {
        return entityArray;
    }
        
}
1 Like

For adding one record, the array costs 32% more than mappings, for the updates around 18%.
With multiple elements, the updates in the arrays will increase in relation to the number of elements, in my case with a total of 6, the array contract costs 67% more than the mapping. The mappings contract update cost, don’t increment, is still the same.

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

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

1 Like

Had same issue, just restarting remix will do the trick!

1 Like

Mapping contract:

pragma solidity >=0.7.0 <0.9.0;

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

Array contract:

pragma solidity >=0.7.0 <0.9.0;

contract StorageDesignContractArray{
    
    struct Entity{
        uint data;
        address entityAddress;
    }
    
    Entity [] public entityArray;
    
    function addEntity(uint data) public returns(Entity memory) {
        Entity memory newEntity;
        newEntity.entityAddress = msg.sender;
        newEntity.data = data;
        entityArray.push(newEntity);
        return entityArray[entityArray.length - 1];
    }
    
    function updateEntity(uint _index, uint data) public {
        entityArray[_index].data = data;
    }
}
  1. When running addEntity function in the mapping contract: 66,286 gas. When running addEntity function in the array contract: 89828 gas. There is a very significant difference in these gas prices because the array contract requires more storage.
  2. Regarding the 5 Entities part of this assignment:
    For the updateEntity functions, the mapping contract resulted in cheaper gas cost compared to the array contract. I believe this is due to the array contract saving to memory and the mapping contract is not saving to memory.
1 Like

Mapping contract

// SPDX-Licence-Identifier: UNLICENSED
pragma solidity 0.8.0;

contract MappingStorage {
    
    struct Entity{
        uint data;
        address _address;
    }
   // mapping from Address to Entity struct;
    mapping(address => Entity) public entitiesMapp;
    
    //Creates a new entity in the struct and adds it to the mapping
    function addEntity(uint data) public {
        entitiesMapp[msg.sender] = Entity(
            data,
            msg.sender
        );   
    }
    
    //Updates the data in a saved entity for msg.sender
    function updateEntity(uint data) public returns(uint){
        return entitiesMapp[msg.sender].data = data;
    }
    
    //returns the Struct
    function getStruct() public view returns (uint, address){
        return (entitiesMapp[msg.sender].data, entitiesMapp[msg.sender]._address);
        
    }
    
}

Array contract

// SPDX-Licence-Identifier: UNLICENSED
pragma solidity 0.8.0;

contract ArrayStorage {
    
    struct Entity{
    uint data;
    address _address;
}
    // we declare the array 
    Entity[] public entitiesArray;
    
    //Creates a new entity for msg.sender adds it to the array and returns the struct
    function addEntity(uint _data) public returns (Entity memory){
        Entity memory newEntity; 
        newEntity._address = msg.sender;
        newEntity.data  = _data;
        entitiesArray.push(newEntity);
        return entitiesArray[entitiesArray.length - 1];
    }
    
    //Updates the data in a saved entity for msg.sender 
    function updateEntity(uint _newData) public {
        for(uint i = 0; i < entitiesArray.length; i++){
            if(entitiesArray[i]._address == msg.sender){
                entitiesArray[i].data = _newData;
            }
        }
         
    }
    
    //Returns the length of the array
    function getStruct() public view returns (uint entityCount){
        return entitiesArray.length;
        
    }
    
}
    

When executing the addEntity function, which design consumes the most gas?
Mapping Consumption: 66088 gas
Array Consumption: 89634 gas
Yes, there is 36% increase in gas consumption when using the array storage design.

Add 5 Entities into storage, update the data of the fifth address you used, Which solution consumes more gas and why?

It is more expensive to use overall the array storage design. When updating the price of the 5th Entity I see an increment of 134%
Update with mapping: 26944 gas
Update with array: 63124 gas

This big difference in price must be due to the use of a for loop, which is more expensive to loop thru the array than to use a key-value type of storage.

1 Like

When I used the Array format the cost for the initial creation was about 89k gas and every subsequent addition was roughly 72k gas with an update costing 29k gas. When I used the mapping each creation was 66k gas and an update was 26k gas. The code I used did not loop through the array for update, when I run a code that uses a loop gas increases.

My determination is that the Array example is significantly more costly to create an entity than the mapping, when it comes to the update function the Array is more costly However I would say the difference is only significant when you add a loop to the update functionality.

Codes I used were codes posted by previous students for the purposes of running different examples.

1 Like

I found that the mapping used more gas than the array when I used the bare basics.
the end result was:
mapping: 66308image
array: 22019image

Mapping

pragma solidity 0.8.7;

contract StorageMap {
    
    struct Entity {
        uint data;
        address _address;
    }
    
    mapping(address => Entity) public entitiesMap;
    
    modifier canAdd(address _address) {
        require(entitiesMap[_address].data == 0, "There is already an entitity with that address");
        _;
    }
    modifier canUpdate(address _address, uint data) {
        require(entitiesMap[_address].data > 0, "There is no entity with that address");
        require(data > 0, "Data must be a number greater than 0");
        _;
    }
    
    function addEntity() public canAdd(msg.sender) {
        entitiesMap[msg.sender] = Entity(1, msg.sender);
    }
    
    function updateEntity(uint data) public canUpdate(msg.sender, data) {
        entitiesMap[msg.sender].data = data;
    }
}

Array

pragma solidity 0.8.7;

contract StorageArray {
    
    struct Entity {
        uint data;
        address _address;
    }
    
    Entity[] public entities;
    
    modifier canAdd(address _address) {
        bool isInArray = false;
        
        for (uint i = 0; i < entities.length; i++) {
            if (entities[i]._address == _address) {
                isInArray = true;
            }
        }
        
        require(isInArray == false, "That entity is already in the array");
        _;
    }
    
    modifier canUpdate(address _address) {
        bool isInArray = false;
        
        for (uint i = 0; i < entities.length; i++) {
            if (entities[i]._address == _address) {
                isInArray = true;
            }
        }
        
        require(isInArray == true, "There is no entity with that address");
        _;
    }
    
    function addEntity() public canAdd(msg.sender) {
        entities.push(Entity(1, msg.sender));
    }
    
    function updateEntity(uint data) public canUpdate(msg.sender) {
        for (uint i = 0; i < entities.length; i++) {
            if (entities[i]._address == msg.sender) {
                entities[i].data = data;
            }
        }
    }
}

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

  • The array design consumes a little bit more gas but not much more.

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?

  • Updating costs almost twice as much gas for the Array method. This is because we need to loop through the list of entities to find the one that matches the address for msg.sender and then update it. As the list grows it’ll cost more gas to update.
1 Like

Here’s my code for the Storage Design Assignment:

StorageDesignMapping.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.4;

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

StorageDesignArray.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.4;

contract StorageDesignArray {
 
    struct Entity{
        uint data;
        address _address;
    } 
    
    Entity[] public entityStructs; 
    
    function addEntity(uint data, address _address) public {
        entityStructs.push(Entity({
            data: data,
            _address: msg.sender
        }));
    }

    function updateEntity(uint data) public {
        for (uint i=0; i<entityStructs.length; i++) {
            if (entityStructs[i]._address == msg.sender) {
                entityStructs[i].data = data;
            }
        }
    }
    
}

1) Test an add and update - Which design consumes the most gas?

  • Using the mapping approach to add data and an address resulted in a 33% savings in gas costs, compared to the array approach.
  • As for updating the data, the mapping saved 18% over the array.

33% and 18% savings respectively are significant. Coding purposely to lower execution gas fees may not make the miners happy, but it will increase efficiency and create a better user experience.

2) Add 5 entities and update the 5th - which consumes more gas & why?

  • When comparing the total execution costs with 6 transactions, the mapping approach saved 16%.
  • Regarding just the update costs between the two approaches, the mapping approach saved 58%.
  • In looking at the array approach alone, I found it interesting that after the first transaction, subsequent array additions recorded 19% less execution cost than the first transaction.

Consistent with the first question, the mapping approach consumed less gas than the array approach, because there’s more going on under the hood with an array than with a mapping, i.e., items in an array are sequential in storage, and a mapping only has the key-value pair.

1 Like

storageDesignArray - execution cost:
Adding: 88489
Updating: 31551
storageDesignMapping - execution cost:
Adding: 43999
Updating: 26921
Array solution consumes more gas and it is because in AddEntity() it has to do more assigments and in updateEntity() has to do a lot of checks to know at what index is msg.sender.

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.0;
pragma abicoder v2;

contract storageDesignArray {
    struct Entity{
        uint data;
        address _address;
}
    Entity[] Entities;
    //mapping (address => bool) IsEntity;
    
    function addEntity(uint _data) public returns (bool){
        Entity memory NewEntity;
        NewEntity.data = _data;
        NewEntity._address = msg.sender;
        Entities.push(NewEntity);
        //IsEntity[msg.sender] = true;
        return true; //Entity memory Entities[Entities.length - 1];
    }
    function updateEntity(uint _newData) public returns(bool) {
        for(uint i=0; i<Entities.length; i++){
            if(Entities[i]._address == msg.sender){
                Entities[i].data = _newData;
                return true;
            }
        }
    } 

}

contract storageDesignMapping {
    struct Entity{
        uint data;
        address _address;
    }
    mapping(address => Entity) addressToEntities;
    
    function addEntity(uint _data) public returns (bool) {
        //Entity memory NewEntity;
        addressToEntities[msg.sender].data = _data;
        //NewEntity._address = msg.sender;
        //addressToEntities[msg.sender] = NewEntity;
        return true;
    }
    function updateEntity(uint _newData) public returns (bool) {
        addressToEntities[msg.sender].data = _newData;
        return true;
    }    
}
1 Like

Code using array:

pragma solidity 0.8.0;

contract StrorageDesignArray {
    
    struct Entity{
        uint data;
        address _address;
    }
    Entity[] entityArray;
    
    function addEntity(uint _data) public{
        Entity memory _entity;
        _entity.data = _data;
        _entity._address = msg.sender;
        entityArray.push(_entity);
    }
    
    function updateEntity(uint _data) public returns (bool){
     
    
      for(uint i = 0; i < entityArray.length; i++){
          
        if (entityArray[i]._address != msg.sender) continue;
        
        entityArray[i].data = _data;
        return true;
      }
      
      return false;
  }
}

Code using mapping:

pragma solidity 0.8.0;

contract StorageDesignMapping {
    struct Entity{
        uint data;
        address _address;
    }
    mapping (address => Entity) entityMapping;
    
    function addEntity(uint _data) public returns (bool success){
        entityMapping[msg.sender].data = _data;
        entityMapping[msg.sender]._address = msg.sender;
        return true;
    }
    
    function updateEntity(uint _data) public returns (bool success){
        entityMapping[msg.sender].data = _data;
        return true;
        
    }
}
  1. The execution costs on the array one were significantly higher, almost tripled the execution cost of the mapping one.
  2. Updating is way more expensive on the array one for reasons commented on previous lessons, its a harder process.
1 Like
  1. The array-only function consumed the most gas when executed. It took, give or take, 30% more gas to transact the array-only contract, which was a significant difference because knowing that fact, it could not only save you 30% on gas fees for other functions similar in construction, but also could allow for reallocation of the gas being spent if the contract’s parameters would allow for it.
  2. The array-only function used much more gas than the mapping only function because it has to iterate each time through an array that is not able to get an address in an easier manner. It is required by the function to circulate through the whole array until it has found the corresponding address to the update.
1 Like

Hello,

When adding an entity mapping cost 66220 in gas while the array was 88216.

  1. The array configuration cost about 25% for adding the entity.

3.The array configuration cost about 75% more. This is due to the for loop that is needed to find the correct entity in the array.

I am just getting back into this class after sometime away. Look forward to learning and growing more.

Good Luck

1 Like

Here’s my code for the versions:

pragma solidity 0.8.7;

contract OnlyMapping{
    
    struct Entity{
    uint data;
    address _address;
    }
    mapping(address => Entity) entities; 
    
    function isEntity(address _address) view private returns(bool indeed) {
        return entities[_address]._address == _address;
    }
    
    function addEntity(uint _data) public returns(bool success){
        address _address=msg.sender;
        require(!isEntity(_address),"Das ist not ein unique EinTitty address!");
        entities[_address].data=_data;
        entities[_address]._address=_address;
        return true;
    }
    function updateEntity(uint _data) public returns(bool success){
        address _address=msg.sender;
        require(isEntity(_address),"NeinNein!Nein! Their is no such EinTitty!");
        entities[_address].data=_data; 
        return true;
    }
}

contract OnlyArray{
    
    struct Entity{
    uint data;
    address _address;
    }
    Entity[] entities;
    
    function addEntity(uint _data) public returns(bool success){
        Entity memory newEntity;
        newEntity.data = _data;
        newEntity._address = msg.sender;
        entities.push(newEntity);
        return true;
    }
    function findIndex(address _address) private view returns(uint index){
        uint i=0;
        for(i=0;i<entities.length;i++){
            if(entities[i]._address==_address){
                return i;
            }
        }
        require(false,"NeinNein!Nein! Their is no such EinTitty!");
    }
    
    function updateEntity(uint _data) public returns(bool success){
        entities[findIndex(msg.sender)].data=_data;
        return true;
    }
}

Here’s the execution cost table:
image

Deploying a dynamic array costs about 25% more, otherwise adding an element is only 7% more expensive using the array rather than the mapping. Creating the mapping costs no more than assigning any other key. So the difference converges to 7% with increasing number of elements. I’d say that’s significant with a lot of use.
Updating the mapping costs less than creating it (makes sense, its only 1 variable in the struct). However, updating the array is significantly more expensive (30% in this case) and would get even more expensive as the array would grow. (Note: in my implementation msg.sender is found with a search in the array.) This would not be the case if we used an index to overwrite the array, but it would be unrealistic to assume that we could keep track of the indexes without assigning them to the addresses some way.
Overall: the mapping solution is cheaper for all tasks.

1 Like

OnlyArray

pragma solidity 0.7.5;

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

    Entity [] entities;

    //Creates a new entity for msg.sender and adds it to the array.
    function addEntity(uint _data) public {
        Entity memory newEntity = Entity(_data,msg.sender);
        entities.push(newEntity);
    }
    
    // Updates the data in a saved entity for msg.sender
    function updateEntity(uint newData) public {
        for(uint indx=0;indx<entities.length;indx++){
            if(entities[indx]._address == msg.sender){
                entities[indx].data=newData;
                  break;
            }
        }
    }
    
}

OnlyMapping

pragma solidity 0.7.5;

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

    mapping (address => Entity) entities;
    
    //Creates a new entity for msg.sender and adds it to the mapping.
    function addEntity(uint _data) public {
        entities[msg.sender]=Entity(_data,msg.sender);
    }
    
    // Updates the data in a saved entity for msg.sender
    function updateEntity(uint newData) public {
         entities[msg.sender]=Entity(newData,msg.sender);
    }
}
    

OnlyArray
-addEntity :
1st address : 88111 gas
4 more different addresses : 71011 gas
-updateEntity :
5th address update : 41453 gas
Note : the longer the array, the more gas it consumes to loop through until reaches last index to update.

OnlyMapping
-addEntity :
1st address : 65876 gas
4 more different addresses : same as 1st adress : 65876 gas
-updateEntity :
5th address update : 28898 gas

1 Like

Keeping it as lean as possible:

Mapping Only:

pragma solidity 0.8.7;

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

    mapping(address => Entity) entitiesMapping;

    function addEntity(uint _data) public {
        entitiesMapping[msg.sender].data = _data;
    }
    
    function updateEntity(uint _data) public {
        entitiesMapping[msg.sender].data = _data;
    }
    
    function viewEntity() public view returns(uint){
        return entitiesMapping[msg.sender].data;
    }
    
    
}

Array Only:

pragma solidity 0.8.7;

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

    Entity[] entitiesArray;

    function addEntity(uint _data) public {
        Entity memory newEntity;
        newEntity.data = _data;
        newEntity._address = msg.sender;
        entitiesArray.push(newEntity);
    }
    
    function updateEntity(uint _index, uint _data) public {
        entitiesArray[_index].data = _data;
    }
    
    function viewEntity(uint _index) public view returns(uint){
        return entitiesArray[_index].data;
    }
  
}

Gas cost comparisons:

Deployment - 177355 (mapping) vs. 231980 (array)
addEntity - 43797 (mapping) vs. 88290 (array) for initial entity, 71190 for additional entities
updateEntity - 26719 (mapping) vs. 29156 (array)

A question if anyone cares to help me out - - when using the addEntity function in the Array version of the contract, why is the cost of adding the first entity object more than adding subsequent entity objects?

1 Like

Array


contract arrayStruct{
    
    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){
        entityArray[_index].data = _data;
        return true;
    }
}

Mapping

pragma solidity 0.8.0;

contract mappingStruct{
    
    struct Entity{
        uint data;
        address _address;
    }
    
    mapping (address => Entity) public entityStruct;
    
    function addEntity(uint _data) public returns(bool success) {
    Entity memory newEntity;
    newEntity.data = _data;
    newEntity._address = msg.sender;
    entityStruct[msg.sender] = newEntity;
    return true;
    }
    
    function updateEntity(address account, uint newData) public returns(bool success) {
        entityStruct[account].data = newData;

        return true;
        
    }

    
}

The addEntity function in the array struct costs significantly more. I’m assuming the array function costs more do to sorting.
after adding 5 entities the difference in gas costs between my two contracts remained marginally true.

1 Like