Assignment - Storage Design

Gas:
Add: 66209 gas
Update: 28893 gas

pragma solidity 0.7.5;

contract StorageAssignment {
    
    struct Entity {
        uint data;
        address _address;
    }
    
    mapping(address => Entity) entities;
    
    function addEntity(uint data) public {
        require(!isEntity(msg.sender), "Entity already persisted");
        entities[msg.sender] = Entity(data, msg.sender);
    }
    
    function isEntity(address _address) private view returns (bool) {
        if(entities[_address]._address == _address) {
            return true;
        }
        
        return false;
    }
    
    function updateEntity(uint _data) public {
        require(isEntity(msg.sender), "Should addEntity first");
        entities[msg.sender].data = _data;
    }
    
}

Gas:
Add: 99008 gas
Update: 59807 gas

pragma solidity 0.7.5;

contract StorageAssignment2 {
    
    struct Entity {
        uint data;
        address _address;
    }
    
    Entity[] entities;
    
    function addEntity(uint data) public {
        require(!isEntity(msg.sender), "Entity already persisted");
        entities.push(Entity(data, msg.sender));
    }
    
    function isEntity(address _address) private view returns (bool) {
        for(uint i = 0; i < entities.length; i++) {
            if(entities[i]._address == _address) {
                return true;
            }
        }
        
        return false;
    }
    
    function updateEntity(uint _data) public {
        require(isEntity(msg.sender), "Entity not present");
        for(uint i = 0; i < entities.length; i++) {
            if(entities[i]._address == msg.sender) {
                entities[i].data = _data;
            }
        }
    }
    
}

Conclusion:
Since my array implementation checks if an address is contained in the array before adding and updating this takes linear time which is more expensive than the mapping lookup.

1 Like
pragma solidity 0.7.5;

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


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

Costs:

addEntity (mapping): 65,876
addEntity (array): 70,912
*Cost for adding entity with array is slightly higher, but the array is adding an item to the end using ‘push’ so it’s not considerably more.

updateEntity (mapping): 26,541
updateEntity (array): 41,453
*Cost for updating entity through array is considerably higher because of the requirement for a loop to find the item to update. This cost could likely be reduced by including an ‘index’ variable in the Struct.

Storage with Mapping

pragma solidity >=0.4.22 <0.9.0;

contract StorageDesignMapping {
    
    struct EntityStruct{
        uint data;
        address _address;
    }
    
    mapping(address => EntityStruct) entityMap;
    
    function addEntity(uint _data) public returns(bool success) {
        entityMap[msg.sender] = EntityStruct(_data, msg.sender);
        return true;
    }
    
    function updateEntity(uint _data) public returns(bool success) {
        entityMap[msg.sender].data = _data;
        return true;
    }
    
}

Deployment Cost: 193165 gas
addEntity Cost: 66253 gas
updateEntity Cost: 26921 gas

Storage with Array

pragma solidity >=0.4.22 <0.9.0;

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

Deployment Cost: 252638 gas
addEntity Cost: 88389 gas
updateEntity Cost: 31828 gas

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

  • Array design consumes the most gas when deployed & executed, approximately 20% more expensive than mapping.

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?

  • fifth Array address update cost: 42456 gas
  • fifth Mapping address update cost: 26921 gas
  • Array design consumes more gas because we need to loop through the array while we can point directly with mapping. The bigger the array is, the more costly it will be to update while mapping stays consistent as it is referencing/pointing directly to the source.
1 Like
pragma solidity 0.8.0;

contract StorageDesignArray {
    
    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;
            }
        }
    }
    
}
pragma solidity 0.8.0;

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

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

array - 88383
mapping - 43987

  • The design using an array is more costly.
  • The cost is about twice as much compared to the design using a mapping.
  • This is so because with the array design, storage is allocated for the index, address, and the data of the struct. Comparing to the mapping design, storage is only allocated for the address and the data.

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 - 42246
mapping - 26713

  • The design using an array consumes more gas because the execution is longer where you have to loop through the array to find the corresponding data of an address.
1 Like
contract StorageMapping {
    
    struct Entity{
    uint data;
    address _address;
    }

    mapping(address => Entity) entityStructs;

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

    function updateEntity( uint _data)public{
        entityStructs[msg.sender].data = _data;

    }
  
  


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

    Entity[]  entityStructs;

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


    function updateEntity(uint position, uint newData) public{
        entityStructs[position].data = newData;
    }
}

The array method was more expensive than mapping. To add the first entry, it costs twice as much gas for an array than mapping.

I did some research and it looks that data from an array is stored in a struct. In that struct you are also storring the length of the array, besides the data you are pushing into the array.

For an array the initial transaction costs more, because you are changing two zero storage slots to a non-zero value. The first being the 0th item in the array and the second, being the length of the array.

For mapping writing and retrieving data is always O(1), meaning the time is always constant regardless of the size of the data. You are also just storing key and a value pair. From what I understand mapping is the cheapest way of storing, retrieving and updating data in solidity.

Sources: https://ethereum.stackexchange.com/questions/77376/array-costs-in-solidity
https://ethereum.stackexchange.com/questions/37549/array-or-mapping-which-costs-more-gas/37594
https://hackernoon.com/how-much-can-i-do-in-a-block-163q3xp2

1 Like

Hi! So I focused on the GAS COST of this exercise. That’s why my solutions are as simple as possible, without any require() or additional logic statements, that might impact the gas costs. Simply adding one new Enitity struct per address.

I did few deployments on each contract and about 20-25 (I just assumed 5 tests are not enough to make a conclusion) tests per function - addEntity() and updateEntity(). These are manual tests on remix, so easily prone to error (eg. using the same address on adding the Entity - wchich in case of MemoryStorage.sol is equal to update, and ArrayStorage.sol is just adding another Entity to the array).

Here are the solutions:

ArrayStorage.sol
// SPDX-License-Identifier: UNLICENCED
pragma solidity 0.8.9;
import "./Storage.sol";

contract ArrayStorage is Storage {
    
    Entity[] entityArray;
    
    function addEntity(uint _data) public {
        entityArray.push(Entity(_data, msg.sender));
    }
    
    function updateEntity(uint _data) public {
        for (uint i=0; i<entityArray.length; i++) {
            if (entityArray[i]._address == msg.sender) {
                entityArray[i].data = _data;
                break;
            }
        }
    }
}
MappingStorage.sol
// SPDX-License-Identifier: UNLICENCED
pragma solidity 0.8.9;
import "./Storage.sol";

contract MappingStorage is Storage {
    
    mapping(address => Entity) entityMapping;
    
    function addEntity(uint _data) public {
        entityMapping[msg.sender] = Entity(_data, msg.sender);
    }
    
    function updateEntity(uint _data) public {
        entityMapping[msg.sender].data = _data;
    }
}
Storage.sol
// SPDX-License-Identifier: UNLICENCED
pragma solidity 0.8.9;

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

The GAS CONSUMPTIONS are:

ArrayStorage.sol:

  • Deployment: 229340
  • addEntity(): 51178-88250
  • addEntity() meen value: 70791
  • updateEntity(): 31365-80361
  • updateEntity() meen value: 55863

MemoryStorage.sol:

  • Deployment: 169441
  • addEntity(): 66064-66162
  • addEntity() meen value: 66085
  • updateEntity(): 21907-26779
  • updateEntity() meen value: 26461

CONCLUSION:

  • ArrayStorage.sol is 35.35% more expensive to deploy
  • ArrayStorage.sol is 7.12% more expensive to add
  • ArrayStorage.sol is 211.11% more expensive to update
  • MappingStorage.sol has more predictable and constant gas costs
  • Adding or updating to initial state (data = 0) in both contracts is less expensive in gas
  • Looping impacts costs a lot, you might run out of gas on large arrays

I also tested the difference between:

Entity memory e;
e.data = _data
e._address = msg.sender;
entityMapping[msg.sender] = e;

and

entityMapping[msg.sender] = Entity(_data, msg.sender);

The second one wins as deployment was 229340 vs 246806. Adding and updating were also cheeper, however the difference wasn’t as big.

1 Like

pragma solidity 0.7.5;

contract MappingTest {

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

function addEntityMapping(uint _data) public returns(uint){
    entityMapping[msg.sender] = _data;
    return (entityMapping[msg.sender]);
}

function updateEntityMapping(uint _data)  public returns(uint){
    entityMapping[msg.sender] = _data;
    return entityMapping[msg.sender];
}

}

contract ArrayTest {

struct Entity{
    address _address;
    uint data;
}

Entity[] entityArray;

function addEntityArray(uint _data)  public returns(uint){
    entityArray.push(Entity((msg.sender), _data));
    return entityArray[(entityArray.length) - 1].data;
}

function updateEntityArray(uint _data)  public returns(uint){
    entityArray[(entityArray.length) - 1].data = _data;
    return entityArray[(entityArray.length) - 1].data;
}

}

array total gas cost with update: 386,212 gas
mapping total gas cost with update: 246,386 gas

1 Like

Array contract

  • Add first entity: 88.460 gas
  • Add entity after first: 71.360 gas
  • Edit fifth entity: 42.209 gas

Mapping contract

  • Add entity: 66.983 gas
  • Edit fifth entity: 29.222 gas

Conclusions:
It’s a difference of 12.987 gas which seems significant to me. Also there’s no difference between editing the first or last entry of a map, but editing the array does increase in cost the further down the array the entry is.

Code Mapping

contract mappingContract {

    struct Entity{
        uint data;
        address _address;
        bool isEntity;
    }
    
    mapping (address => Entity) public entityMap;
    
    function addEntity(address entityAddress, uint entityData) public {
        require(!entityMap[entityAddress].isEntity, "Duplicate!");
        entityMap[entityAddress] = Entity(entityData, entityAddress, true);
    }
    
    function updateEntity(address entityAddress, uint entityData) public {
        require(entityMap[entityAddress].isEntity);
        entityMap[entityAddress].data = entityData;
    }
}

Code array

contract arrayContract {

    struct Entity{
        uint data;
        address _address;
    }

    Entity[] public entityArr;

    function addEntity(address entityAddress, uint entityData) public {
        entityArr.push( Entity(entityData, entityAddress) );
    }

    function updateEntity(address entityAddress, uint entityData) public {
        for (uint i=0; i<entityArr.length; i++) {
            if (entityArr[i]._address == entityAddress) {
                entityArr[i] = Entity(entityData, entityAddress);
            }
        }
    }
}
1 Like

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

Mapping consumes 66693 gas and Array consumes 89418 gas. The array function consumes more gas and yes I suppose I would consider it a considerable difference as it is more than 20k gas between the two.

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 Mapping contract utilized 29649 gas while the Array contract utilized 31501 gas. In this fashion the array contract utilized more gas again although the difference between the two seemed to be negligible.

pragma solidity 0.7.5;
pragma abicoder v2;

contract StorageMapping {
    
    struct Entity{
    uint data;
    address _address;
    }
    
    mapping (address => Entity) public entityMap;

    function addEntity(address entityAddress, uint entityData) public {
        entityMap[msg.sender]._address = entityAddress;
        entityMap[msg.sender].data = entityData;
  }
  
  function updateEntity(address entityAddress, uint entityData) public {
        entityMap[msg.sender]._address = entityAddress;
        entityMap[msg.sender].data = entityData;
  }
   
    
}   





contract StorageArray {
   
   struct Entity{
    uint data;
    address _address;
    }
    
   Entity[] entityArray;  // array of Entity struct
 
function addEntity(uint entityData) public returns(Entity memory) {
    Entity memory newEntity;
    newEntity._address = msg.sender;
    newEntity.data = entityData;
    entityArray.push(newEntity);
    return entityArray[entityArray.length - 1];
}

function updateEntity(uint rowNumber, uint entityData) public {
    if(entityArray[rowNumber]._address != msg.sender) revert();
    entityArray[rowNumber].data = entityData;
  }
 
 
    
}
1 Like
ragma solidity ^0.8.0;

contract onlyMapping {
    
    struct Entity {
    uint data;
    address _address;

    }
    
    mapping (address => Entity) public entitystructs;

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

  }
}
pragma solidity ^0.8.0;

contract onlyArray {
    
    struct Entity {
    uint data;
    address _address;

    }
    
    Entity[] entitiarray;

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

    function updateEntity(uint _data) public {
            uint rowNumber;
            if(entitiarray[rowNumber]._address != msg.sender) revert();
            entitiarray[rowNumber].data = _data;
          
    }    
    
    function getEntityData () public view returns (uint){
        uint rowNumber;
        if(entitiarray[rowNumber]._address != msg.sender) revert();
        return entitiarray[rowNumber].data;
        
    }


    
}
1 Like

ARRAY CONTRACT

pragma solidity >0.5.0 <= 0.8.5;
pragma abicoder v2;

/*

The assignment is to create an array only storage design for the struct “entity”

It will have two functions:

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

*/

contract eArray{

struct entity{
    uint entityData;
    address _address;
}


entity[] public Entities;

function addEntity(address _address, uint entityData) public returns (bool success){
    require(_address == msg.sender);
    entity memory newEntity;
    newEntity.entityData = entityData;
    newEntity._address = _address;
    Entities.push(newEntity);
    return true;
}


function updateEntity(uint index, uint entityData) public returns (bool success){
    uint arrayIndex = index;
    require(msg.sender == Entities[arrayIndex]._address);
    Entities[arrayIndex].entityData = entityData;
    return true;
}

 /*
 
addEntity
Transaction cost: 72058
execution cost: 72058

updateEntity
Transaction cost: 31694
execution cost: 31694

*/

}

CONTRACT MAPPING

pragma solidity >0.5.0 <= 0.8.5;
pragma abicoder v2;

/*

The assignment is to create a mapping only storage design for the struct “entity”

It will have two functions:

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

*/

contract eMap{

struct entity{
    uint entityData;
    address _address;
}


mapping(address => entity) Entities;

function addEntity(address _address, uint entityData) public returns (bool success){
    require(_address == msg.sender);
    entity memory newEntity;
    newEntity.entityData = entityData;
    newEntity._address = _address;
    return true;
}


function updateEntity(address _address, uint entityData) public returns (bool success){
    require(_address == msg.sender);
    Entities[_address].entityData = entityData;
    return true;
}

 /*
 
addEntity
Transaction cost: 22595
execution cost: 22595

updateEntity
Transaction cost: 24736
execution cost: 24736

*/

}

QUESTIONS

  1. When executing the addEntity function, which design consumes the most gas (execution cost)? Is it a significant difference? Why/why not?.
    A1. The array contract is by far the more expensive of the two due to the array using expensive storage memory.

  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?

A2. Mapping consumes more gas when updating the 5th entry. I’m not entirely sure why.

1 Like

Array Contract

pragma solidity 0.8.7;
pragma abicoder v2;

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

Mapping Contract

pragma solidity 0.8.7;

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

Questions
Q1.
The array contract used much more gas 88k gas vs 66k gas for the mapping - about a third more gas which is significant for a single function.

Q2.
Mapping contract used 26.7k gas.
Array contract used 42k gas.
The array costs over 50% more gas vs the mapping. This is expected as the data storage isn’t ordered like a mappings data.

1 Like

The array approach requires more gas for the addEntity function. The difference is significant. This is due to an array being more complicated and doing more background checks than the mapping does.

The array solution costs more gas, due to the fact that one will have to search for the correct index to update, before updating the data variable. The mapping does not need to search for indexes.

1 Like

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

Mapping design consumes around 41701 and array 62675.

Gas is almost 33% more expensive in array than in mapping. I think this is because of more complicated code, E.G the “memory” used in array to save a new entry, while in mapping you just directly input your new values in the mapping, which is more simple.

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?

Execution cost for mapping was 27288 and for array 49319. Array here consumes almost two times more gas now. This is because of even more complicated code in array´s change function. It looks through the array to locate the value, while in mapping can found and modified directly.

Mapping code

pragma solidity 0.8.0;

contract mappingassignment {
    


struct Entity{
    uint data;
    address _address;
    uint listPointer;
}

mapping(address => Entity) Entities;


function addEntity(address _address, uint data) public{
 
    Entities[_address].data = data;       // simple because you can point to mapping directly
    Entities[_address]._address = _address;
 
}

function updateEntity(address _address, uint data) public {
   Entities[_address].data = data;

}

}

Array code

pragma solidity 0.8.0;

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

Entity[] public Entities;  


function addEntity(address _address, uint data) public {

    Entity memory newcustomentity;         // memory needs to be in array
    newcustomentity._address = _address;
    newcustomentity.data = data;
    Entities.push(newcustomentity);
}

function updateEntity(uint data) public {     // with array, updatingEntity requires a little complicated code
    uint arrLength = Entities.length;
    for(uint i = 0; i<arrLength; i++) {
    if (Entities[i]._address == msg.sender) { 
    Entities[i].data = data;
    }
    }

}
function listEntities() public view returns(uint listEnts) {
    return Entities.length;
}
}

Answers:

Execution cost for mapping was significantly lower than for array. The code is more complicated in array type of code. When updating the entity, the entire array has to be checked, while at mapping you are pointed directly to the specific address. It is only key to value storage, but on the other hand we are limited to store a lot of variables etc. with mapping-drawmax.
Also, updating the data for 5. Address consumes less gas with mapping.

For purpose I did not solve the assignment for msg.sender, but for any address as desired.

Codes:
-mapping:

pragma solidity 0.8.10;
pragma abicoder v2;

contract Compare {

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

-array:

pragma solidity 0.8.10;
pragma abicoder v2;

contract Compare {

      struct Entity {
        uint data;
        address _address;
      }
      
      Entity[] public Entities;
  
      function addEntity(address _address, uint data) public {
        Entity memory _toAdd;
        _toAdd._address=_address;
        _toAdd.data=data;
        Entities.push(_toAdd);
      }
  
     function updateEntity(address _address, uint data) public { 
         //check if address exists in the array (struct)
        uint Length = Entities.length;
        for(uint i = 0; i<Length; i++) {
        if (Entities[i]._address == _address) { 
        Entities[i].data = data;
        }
        }
    }

  function getEntities() public view returns(uint listOfEntities) {
    return Entities.length;
  }
}
1 Like

1.mapping solution significantly cheaper. I guess it is due to the fact that build up Array design costs significantly more, it needs to add data on the end of array a keep track of it than just use key value connection in mapping.

2.again mapping solution cheaper . adding as per above and updating is also cheaper with just mapping solution .Array costs more to update since gas is used for loop iterating through entire array to find correct address to update data. mapping does it via hash table.

1 Like

the addEntity function costs more in the array design and it costs less in the mapping design. its a significant difference because the array is a permanent storage data type.

Overall I learnt that the array design costs more than the mapping design.

1 Like

Tells a pretty clear story :slight_smile:

I was surprised by the array initial insert costing more gas. After that the inserts costs the same. Mapping was consistent all the way. Wonder why the difference between the array first insert and the mapping first insert. is it a difference in how those 2 types represented at the EVM level?

2 Likes
Mapping contract
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

contract mappingDesign {
    
  struct entityStruct {
    uint256 entityData;
    address entityAddress;
  }

  mapping(address => entityStruct) public entityMapping;

  function addEntity(uint256 _entityData) external {
    require(entityMapping[msg.sender].entityAddress != msg.sender, "Entity does already exist");
    
    entityMapping[msg.sender].entityAddress = msg.sender;
    entityMapping[msg.sender].entityData = _entityData;
  }

  function updateEntity(uint256 _entityData) external {
    require(entityMapping[msg.sender].entityAddress == msg.sender, "Entity does not exist yet");
    
    entityMapping[msg.sender].entityData = _entityData;
  }
}
Array contract
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

contract arrayDesign {
    
  struct entityStruct {
    uint256 entityData;
    address entityAddress;
  }

  entityStruct[] public entityArray;
  
  function addEntity(uint256 _entityData) external {
    entityStruct memory newEntity = entityStruct(_entityData, msg.sender);
    entityArray.push(newEntity);
  }

  function updateEntity(uint256 _index, uint256 _entityData) external {
    entityArray[_index].entityData = _entityData;
  }
}

1. 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 the most gas, the difference between mapping and array design is significant.

mapping design: 66326 gas
Array design: 88203 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?

The array design still consumes more gas when adding and updating entities.

mapping addEntity: 66338 gas
The mapping design for the 4 newly-added entities cost almost the same as the first call.

array addEntity: 71115 gas
For the array, compared to the first call of addEntity function, the 4 other calls were cheaper by around 17k gas

mapping updateEntity: 29006 gas
array updateEntity: 29156 gas
It cost almost the same to update the fifth address of mapping and array design.

1 Like
pragma solidity 0.8.0;

contract StorageSolutions1 {

    struct Entity{
    uint data;
    address _address;
    }
    mapping(address => Entity) public entityStructs;

    function addEntity(uint256 _entityData) external {
    require(entityStructs[msg.sender]._address != msg.sender, "Entity does already exist");
    
    entityStructs[msg.sender]._address = msg.sender;
    entityStructs[msg.sender].data = _entityData;
    }

    function updateEntity(uint256 _entityData) external {
    require(entityStructs[msg.sender]._address == msg.sender, "Entity does not exist yet");
    
    entityStructs[msg.sender].data = _entityData;
    }

}
pragma solidity 0.8.0;

contract StorageSolutions2 {

    struct Entity{
    uint data;
    address _address;
    }

    Entity[] public entityArray;

    function addEntity(uint256 _entityData) external {
    Entity memory newEntity = Entity(_entityData, msg.sender);
    entityArray.push(newEntity);
    }

    function updateEntity(uint256 _index, uint256 _entityData) external {
    entityArray[_index].data = _entityData;
    }

}

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?

Solution with mapping: deployment cost 375245, adding an Entity cost 66326gas, updating cost 29006

Solution with array: deployment cost 236462gas, adding an Entity cost 71103gas, updating cost 29156

As you can see the array contract is a lot cheaper in deplyment but costs more when using the functions. The mapping solution is probably more cstly to run because it has more code. But in terms of running the smart contract it is cheaper because you don´t have an array to loop trough every single time.

1 Like