Assignment - Storage Design

Gas(Execution costs) - Add Entity operation:

Simple Array : 89124
Simple Mapping : 66897

Result: Simple Array add entity operation consumes 28.49% more gas than the simple mapping add entity operation. There is noticeable difference. This is possibly due to the nature of the push operation in which the element is inserted at the last of array. The mapping add entity is a direct insert of a struct into the key (address) and thus consuming less gas.

Gas(Execution costs) - Update Entity operation:

Simple Array : 43566
Simple Mapping : 27490

Result: Simple Array Update entity operation consumes 45.25% more than the simple mapping update entity operation. The difference is significant. This is because the array elements have to be iterated and condition checked to confirm the address intended for update. The mapping update entity is a direct update of a entity value for the given key (address) and thus consuming less gas.

simpleArray.sol

pragma solidity 0.8.0;

contract simpleArray {

  struct EntityStruct {
    uint entityData;
    address _entityAddress;
  }

  EntityStruct[] public entityStructs;

  function addEntity(uint _data, address _address) public returns(bool success) {
    EntityStruct memory newEntity;
    newEntity.entityData = _data;
    newEntity._entityAddress = _address;
    entityStructs.push(newEntity);
    return true;
  }

  function updateEntity(uint _data) public returns(bool success) {
    for (uint i = 0; i <= entityStructs.length - 1; i++) {
        if(entityStructs[i]._entityAddress == msg.sender) {
            entityStructs[i].entityData = _data;
        }
    }
    return true;
  }
}

simpleMapping.sol

pragma solidity 0.8.0;

contract simpleMapping {

  struct EntityStruct {
    uint entityData;
    address _entityAddress;
  }

  mapping (address => EntityStruct) public entityStructs;

  function addEntity(uint _data, address _address) public returns(bool success) {
    entityStructs[_address].entityData = _data;
    entityStructs[_address]._entityAddress = _address;
    return true;
  }
  
  function updateEntity(uint _data, address _address) public returns(bool success) {
    entityStructs[_address].entityData = _data;
    return true;
  }
}

Array addFunction execution cost: initial = 88290, after adding 5 addresses and updating the last = 39409

Mapping addFunction execution cost: 67214, after adding 5 addresses and updating the last = 26982

Mapping is less expensive in general, because it allows the function to point directly to specific data during execution, whereas storing data in an array causes it to be indexed which means that the function has to look through the entire array to find it.

1 Like

Storage Design and Comparing Gas Costs:

Gas execution cost for addEntity()
Mapping: 66054
Array: 88190
The array solution used about 1.3 times more gas. The difference is decently significant if you are really trying to save on gas. The reason for this difference comes down to the fact that an array takes up more data than a mapping to store.

Gas execution cost for updateEntity()
Mapping: 26719
Array: 42246

The array solution consumed about 1.6 times more gas due to the fact that the array must be looped through in order to find the desired address and then updates the data. The mapping just needs the address and then points directly to the data and updates it.

Mapping storage

pragma solidity 0.8.0;

contract MappingStorage {

    struct Entity{
        uint data;
        address _address;
    }

    mapping(address => Entity) entityMap;
     
    function addEntity(uint _data) public {
        entityMap[msg.sender] = Entity(_data, msg.sender);
    }

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

Array storage

pragma solidity 0.8.0;

contract ArrayStorage {

    struct Entity{
        uint data;
        address _address;
    }

    Entity[] entityArray;

    function addEntity(uint _data) public {
        entityArray.push(Entity(_data, msg.sender));
    }

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

For gas cost when adding new entity, the storage cost was about 23,000 higher with the array than using a mapping. there was less steps and code in the mapping solution.

there was around a 2000 difference in gas cost for updating using mapping vs array,

pragma solidity 0.8.0;

contract AssignmentMapping {

    struct Entity{
        uint data;
        address _address;
    }

    mapping (address => Entity) public entities;

  function addEntity(uint entityData) public returns(bool success) {
    Entity memory newEntity;
    newEntity.data = entityData;
    newEntity._address = msg.sender;
    entities[msg.sender] = newEntity;
    return true;
  }

    function updateEntity(address entityAddress, uint entityData) public returns(bool success) {
    entities[entityAddress].data = entityData;
    return true;
  }
}
pragma solidity 0.8.0;

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

    Entity[] public entities;

  function addEntity(uint entityData) public returns(Entity memory) {
    Entity memory newEntity;
    newEntity._address = msg.sender;
    newEntity.data = entityData;
    entities.push(newEntity);
    return entities[entities.length - 1];
  }

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


}

Gas costs for addEntity()
Mapping: 66409;
Array: 89612;

Gas costs for updateEntity()
Mapping: 27502;
Array: 29370;

Of course, Mapping is less costly because it is faster and doesn’t require loops.

2 Likes

wow im stunned amazing effort man. trully detailed post amazing to see this passion. keep it up and keep learning

1 Like

Here is my assignment. First contract only store mapping value for adding entity operation.

pragma solidity 0.8.0;

contract AssignmentDataStorage {

  struct Entity{
    uint data;
    address _address;
  }

  mapping(address => Entity) public entityStruct;

  function addEntity(address addressEntity, uint dataEntity) public returns (bool success){
    entityStruct[addressEntity]._address = addressEntity;
    entityStruct[addressEntity].data = dataEntity;
    return true;
  }

  function updateEntity(address addressEntity, uint dataEntity) public returns (bool success){
    entityStruct[addressEntity].data = dataEntity;
    return true;
  }



}

For this contract, “addEntity” function costs 53996 gas consumption. But in this arrayStorage contract :

pragma solidity 0.8.0;

contract arrayStorage {
    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;
    }
}

In conclusion “adding entity” operation costs like 80k gas consumption.

Also, when I created 5 entities and updated one of them:
–> Array storage function costs : 29k gas consumption
–> Mapping storage function costs : 45k gas consumption

It seems using mappings with arrays are simple, readable and cost less gas consumption because if we are only use arrays, it will be so expensive; if we are only use mappings, we cannot reach specific variables easily. For these reasons, developers should analyze which way is logically proper for choosing data structure.

2 Likes

Mapping Storage:

pragma solidity 0.8.0;

contract onlyMappingStorage {

    struct Entity{
    uint data;
    address _address;
    }

    mapping (address=> Entity) public entities;

    function addEntity(uint entityData)public {
        entities[msg.sender].data = entityData;
    }

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

}

Simple Array Storage:

pragma solidity 0.8.0;

contract onlyArrayStorage {

    struct Entity {
        uint data;
        address _address;
    }

    address owner;

    constructor(){
        owner = msg.sender;
    }

    Entity[] public entities;

    function addEntity(uint entityData)public {
        Entity memory newEntity;
        newEntity._address = owner;
        newEntity.data = entityData;
        entities.push(newEntity);
    }

    function updateEntity(uint newData) public {
         for(uint i = 0; i < entities.length; i++){
            if (entities[i]._address == owner){
                entities[i].data = newData;
            }
         }
    }
    
}

Improved Array Storage:

pragma solidity 0.8.0;

contract onlyArrayNewStorage {

    struct Entity {
        uint data;
        address _address;
    }

    Entity[] public entities;

    function addEntity(uint entityData)public {
        Entity memory newEntity;
        newEntity._address = msg.sender;
        newEntity.data = entityData;
        entities.push(newEntity);
    }

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

}

Executing the addEntity function costs:
in OnlyMappingStorage contract: 43819 gas
in OnlyArrayStorage contract with constructor: 90424 gas
in OnlyArrayStorage contract without constructor: 88290 gas

The contract with the data stored in an array consumes the most gas. Regardless if it is deployed with or without a constructor. Both my Array contracts use more than double the amount of gas in comparison with the Mapping contract.

This is a significant difference no doubt. Even when it concerns ‘only’ 46610 gas = 46610 gwei = 0.000109 ETH = 0.12 USD. When this function is run 10.000 times, we’ll get a different outcome of $1200 in costs. If a contract has multiple functions like this and the gas fees are variable depending on demand, then every solution to reduce gass counts.

After adding 5 entities into storage, the result when updating the data of the 5th address was as following:
in OnlyMappingStorage contract: 26943 gas
in OnlyArrayStorage contract with constructor: 65782 gas
in OnlyArrayStorage contract without constructor: 31640 gas

Still the array storage model consumes the most gas in comparison with storage by mapping. In the Array contract the EVM must loop through the array in order to find the location of the stored address and update its data. In the Mapping contract the address itself is the key to the location of the corresponding data. Which means this data can be directly accessed and updated.

In conclusion the mapping solution costs less energy, and thus gas fees, to execute.

2 Likes

nice slution and great understanding. yeah in a lot of caseses implementing a mapping with an array is generally the approach.

Here is my assignment, with both solutions array and mapping, I don’t know if Im doing something wrong but in compare to others students I don’t have this big difference between array and mapping even if in theory I understood that a mapping will be more gas efficient.

MAPPING:

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.7.5;

pragma abicoder v2;

contract mappingWithStruct {

  struct EntityStruct {
    uint data;
    address _address;
  }

  mapping (address => EntityStruct) public entityStructs;

  function addEntity(address _address, uint _data)external{

   entityStructs[_address]._address = _address;

   entityStructs[_address].data = _data;

  }

  function updateEntity( uint _data)external{

    entityStructs[msg.sender].data = _data;

  }
}

The result of gas consumption is 26741 gas
ARRAY:

contract arrayWithStruct {

  struct EntityStruct {
    uint data;
    address _address;
  }

 EntityStruct [] arrayStruct;

  function addEntity(address _address, uint _data)external{

   EntityStruct memory newEntity;

    newEntity._address = _address;

    newEntity.data = _data;

    arrayStruct.push(newEntity);

  }

  function updateEntity( uint _index, uint _data)external{

    arrayStruct[_index].data = _data;

  }
 }

The result of gas consumption is 29122 gas
the difference is 2381 gas not a lot like others students.

2 Likes

Important note : In order to avoid entity duplication I adapted both codes while respecting the guidelines.
For the array solution, I believe the for loop is the biggest drawback in terms of gas usage that’s why I returned 2 parameters so that we don’t have to run it twice when updating entities.

When adding the first entity I get the following execution costs :
MappingSol : 66403 gas
ArraySol : 88459 gas

At that stage they are pretty much the same, but I believe that the more entities I add the more the ArraySol execution cost will increase, while the MappingSol one should remain pretty similar.

Execution costs for the Mapping Solution :
Adding 1st entity : 66403 gas
Adding 2nd entity : 66403 gas
Adding 3rd entity : 66403 gas
Adding 4th entity : 66391 gas
Adding 5th entity : 66403 gas
Updating 5th entity : 29071 gas

Execution costs for the Array Solution :
Adding 1st entity : 88459 gas
Adding 2nd entity : 74029 gas
Adding 3rd entity : 76663 gas (+ 2634 gas)
Adding 4th entity : 79333 gas (+ 2670 gas)
Adding 5th entity : 81991 gas (+ 2659 gas)
Updating 5th entity : 42501 gas

We clearly see that the more entities we add in the array solution the more gas it costs to execute the addEntity function. To be more precise we see that each new entity we add will cost about 2600 gas more than when we added the previous entity. This means that it grows about linearly.

We can also note that when we add the 1st entity in the array it costs a bit more, probably because we have to actually create the array.

Anyway, with these data we see that it’s more gas efficient to use the mapping solution (for this simple assignment) and that the more entities we add the better the mapping solution is.

contract MappingSol {

    struct Entity{
        uint data;
        address _address;
    }

    mapping (address => Entity) entities ;

    function addEntity(uint _data) public { // Creates a new entity for msg.sender and adds it to the mapping
        require (!isEntity(msg.sender),"An entity already exists with your address, please update it instead.");
        entities[msg.sender]._address = msg.sender ;
        entities[msg.sender].data = _data ;
    } 

    function updateEntity(uint _data) public { // Updates the data in a saved entity for msg.sender
        require (isEntity(msg.sender),"No entity exists with your address, please create one instead.");
        entities[msg.sender].data = _data ;
    }
    function isEntity(address addressToTest) internal view returns (bool) {
        if(entities[addressToTest]._address == addressToTest) return true;
        return false;
    }


}

contract ArraySol {

    struct Entity{
        uint data;
        address _address;
    }

    Entity[] entities ;

    function addEntity(uint _data) public { // Creates a new entity for msg.sender and adds it to the mapping
        (bool res,) = isEntity(msg.sender) ;
        require (res==false,"An entity already exists with your address, please update it instead.");
        Entity memory newEntity = Entity(_data, msg.sender);
        entities.push(newEntity);
        //entities.push(_data,msg.sender); // doesn't work " Member "push" not found or not visible after argument-dependent lookup in struct ArraySol.Entity storage ref[] storage ref."
        

    } 

    function updateEntity(uint _data) public { // Updates the data in a saved entity for msg.sender
        (bool res, uint pos) = isEntity(msg.sender) ;
        require (res==true,"No entity exists with your address, please create one instead.");
        entities[pos-1].data = _data;
        
    }

    function isEntity(address addressToTest) internal view returns (bool result, uint position) {
        for (uint i=0;i<entities.length;i++) {
            if(entities[i]._address == addressToTest) return (true, i+1);
        }
        return (false,0);
    }

    function ListEntities() public view returns (Entity[] memory) {
        return entities;
    }



}

Question : I tried to compare the results when I set all functions to public vs external and I get the same exact result. How come external isn’t slightly cheaper ?

3 Likes

Array approach consumes more gas than the mapping approach.

1 Like

Gas costs for
Array Solution:
addEntity 72500, 72500, 72512, 72512, 72500
updateEntity 43310

Mapping Solution:
addEntity 66678, 66678, 66690, 66690, 66678
updateEntity 29668

In general it is less costly to use the mapping solution to add elements, I suspect this is the case because the push operation is more costly that direct assignment to a mapping

It was almost twice as expensive to update that in the array solution due to iterating through the array to the last element while the mapping solution requires no searching and the Entity can be directly accessed with the sender’s address

The code:

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

  Entity[] public entities;

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

 function updateEntity(uint _data) public returns(Entity memory) {
        uint itemIndex = 0;
        
        for(uint i=0; i < entities.length; i++) {
            if (entities[i]._address == msg.sender) {
                entities[i].data = _data;
                itemIndex = i;
            } 
        }
    return entities[itemIndex];
  }
}

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

  mapping (address => Entity) public entities;

  function addEntity(uint _data) public returns(Entity memory) {
    Entity memory newEntity;
    newEntity._address = msg.sender;
    newEntity.data    = _data;
    entities[msg.sender] = newEntity;
    return newEntity;
  }

 function updateEntity(uint _data) public returns(Entity memory) {
    entities[msg.sender].data = _data;
    return entities[msg.sender];
  }
}
2 Likes
pragma solidity 0.8.0;

contract mappedWithUnorderedIndexAndDelete {

  struct EntityStruct {
    uint entityData;
    address _address;
  }

  mapping(address => EntityStruct) public entityStructs;


  function newEntity(address entityAddress, uint entityData) public returns(bool success) {
    entityStructs[entityAddress].entityData = entityData;
    return true;
  }

  function updateEntity(address entityAddress, uint entityData) public returns(bool success) {
    entityStructs[entityAddress].entityData = entityData;
    return true;
  }
 
}

execution 44206

execution 24962

pragma solidity 0.8.0;

contract nonmap {


    struct EntityStruct{
    uint entityData;
    address entityAddress;
}
 
    EntityStruct[] public entityStructs;
 
 function newEntity(address entityAddress, uint _entityData) public returns(bool success) {
     EntityStruct memory newEntity;
     newEntity.entityAddress = entityAddress;
     newEntity.entityData    = _entityData;
     entityStructs.push(newEntity);
     return true;
  }

  function updateEntity(uint enitityID, uint _entityData) public returns(bool success) {
    entityStructs[enitityID].entityData    = _entityData;
    return true;
  }
 
}

excution cost 71437

update 28896

  1. array costs more gas. it is a significant difference because you are storing same data twice. in memory and in storage
// SPDX-License-Identifier: MIT

pragma solidity 0.8.0;

contract MappingStorage {

    struct Entity {
        uint data;
        address _address;
    }

    mapping (address => Entity) hashmap;

    function addEntity(uint _data)
    external {
        hashmap[msg.sender] = Entity(_data, msg.sender);
    }

    function updateEntity(uint _data) 
    external {
        hashmap[msg.sender].data = _data;
    }

    function getEntity(address input)
    external
    view
    returns(uint) {
        return hashmap[input].data;
    }

}

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

    Entity[] logs;

    function addEntity(uint _data)
    external {
        logs.push(Entity(_data, msg.sender));
    }

    function updateEntity(uint idx, uint _data)
    external {
        logs[idx].data = _data;
    }

    function getEntity(uint idx)
    external
    view
    returns(uint) {
        return logs[idx].data;
    }

}
1 Like
pragma solidity 0.8.0;

contract ArrayGasCost {

    struct Entity {
        uint data;
        address _address;
    }

    Entity[] public entityList;

    function addEntity(uint entityData) public {
        Entity memory newEntity;
        newEntity._address = msg.sender;
        newEntity.data = entityData;
        entityList.push(newEntity);
    }

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

contract MappingGasCost {

    struct Entity {
        uint data;
        address _address;
    }

    mapping(address => Entity) entityStructs;

    function addEntity(uint entityData) public returns (bool success) {
        require(msg.sender != entityStructs[msg.sender]._address, "You already have an entity");
        entityStructs[msg.sender]._address = msg.sender;
        entityStructs[msg.sender].data = entityData;
        return true;
    }

    function updateEntity(uint entityData) public returns (bool success) {
        require(entityStructs[msg.sender]._address != address(0), "You must have an entity");
        entityStructs[msg.sender].data = entityData;
        return true;
    }
}

When executing the addEntity function it costs:
Array: 88312 gas
Mapping: 66528 gas

When executing the updateEntity for the fifth address:
Array: 42268 gas
Mapping: 29190 gas

In conclusion, the mapping contract saves 25-30% on gas compare to the array contract, because of the more the code must do in order to do the same thing tha mapping can just look up.

2 Likes

I’m not able to compile both of my array and mapping contract. May I know why please?

below are my codes:

pragma solidity 0.7.5;

contract arrayContract {

    struct Entity{
        uint data;
        address _address;
    }

    Entity[] public entity;

    function addEntity(uint data, address _address) public returns(bool success) {
        Entity memory newEntity;
        newEntity.data = data;
        newEntity._address = msg.sender;
        entity.push(newEntity);
    }

    function updateEntity(address entityAddress, uint entityData) public returns(bool success) {
        entity[_index].data = _data;
        return true;

}

In the last line, it says “from solidity:
ETH SC Prog 201/assignment - storage design array.sol:23:2: ParserError: Function, variable, struct or modifier declaration expected.
}
^”

pragma solidity 0.7.5;
 
contract mappingContract {

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

In line “entity[msg.sender].data = _data;”, it says “from solidity:
ETH SC Prog 201/assignment - storage design mapping.sol:22:35: DeclarationError: Undeclared identifier.
entity[msg.sender].data = _data;
^—^”

I would appreciate your help!!

1 Like
ETH SC Prog 201/assignment - storage design mapping.sol:22:35: DeclarationError: Undeclared identifier.
entity[msg.sender].data = _data;
^—^”

You have an Undeclared identifier., if you take a look at the updateEntity function, it have an argument that is not being used on the function body address entityAddress. If you remove it, the error should disapear.

Carlos Z

2 Likes

ok so 2381 gas may not seem like much o be saving but wha you need to understand here is the size of these data structures. mappngs are geenrally more costly to initalise than arrays. however as your array grows the cost to update, remove, search items grows a LOT. so arrays are a good solution whenever you are certain there will only be a few items at most. but in cases where your array could have dozens of entires then in order to search this array the amount of computation required is greatly increased casuing the gas to become much more expensive. where as with mapping syou d not see this growth because mapping lookups are much quicker because you already know the keys or indexes before hand.

1 Like

Thanks for the reply. They are still not working. May I know why?

in line 13, it said “from solidity:
ETH SC Prog 201/assignment - storage design mapping.sol:13:24: Warning: Unused function parameter. Remove or comment out the variable name to silence this warning.
function addEntity(address _address, uint data) public returns(bool success) {
^--------------^”


in line 24, it said “from solidity:
ETH SC Prog 201/assignment - storage design array.sol:24:2: ParserError: Function, variable, struct or modifier declaration expected.
}
^”

Why is it so difficult for me to make it right? Is it just me? Is it normal for those have no prior coding experience??? Do those post their codes together with answers to the questions already have prior coding experience???

1 Like