Assignment - Storage Design

Assignment Storage Design.

  1. When executing addEntity I found that the Array based test code took on average 1.3 times more execution gas than the Mapping version. Also on the first iteration of the Array code took 2x more gas.

Average execution gas for Array 28120 ( first iteration 43120)
Average execution gas for Mapping 22191 ( consistent over 5 iterations.)

I suggest that the overheads on Array gas costs are due to the use of a interim memory array and over head of indexing the array before storing in persistent storage.

  1. After the 5th iteration storing the data, updateEntity, caused a marginal increase in gas costs for the Array code.

Array gas cost 21714.
Mapping gas cost 20739.

As above suggest that the increase due to indexing array overhead. I repeated this test several times with the same results.

Obviously Mapping uses less gas from my test results and Array based uses more instructions which costs extra gas,

Any comments?

Thanks Mike

pragma solidity 0.8.0;

// Basic code for testing gas costs only .

contract gasMappingCost {

  struct EntityStruct {
    uint data;
    address _address;
      
  }

  mapping (address => EntityStruct) public entityStructs;

  function addEntity() public returns(bool success) {

    entityStructs[msg.sender].data = 0; // initialise data
    entityStructs[msg.sender]._address = msg.sender;
    return true;
  }
  
// msg.sender must match address 5 for test purposes when setting data.

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


/////////////////////////////////////////////////////////////////////////

pragma solidity 0.8.0;

// Basic code for testing gas costs only .

contract gasArrayCost{

  struct EntityStruct {
    uint data;
    address _address;      
  }
  
  EntityStruct[] public entityStructs;

  function addEntity() public  returns( bool success) {
    EntityStruct memory newEntity;
    newEntity._address = msg.sender;
    newEntity.data    = 0; // initialise data
    entityStructs.push(newEntity);
    return true;
  }
 
// Assume manual matching of array index to match address 5 [4] for test purposes when setting data
  
   function updateEntity(uint arrayIndex, uint _data) public returns(bool success) {
  
  entityStructs[arrayIndex].data=_data;
    
    return true;
  }
}
ype or paste code here
1 Like
arrayStorage.sol
pragma solidity 0.8.0;

contract arrayStorage {
    
    struct Entity {
        uint data;
        address _address;
    }
    
    Entity[] entityStructs;
    
    function addEntity(uint _data) public {
        Entity memory newEntity;
        newEntity.data = _data;
        newEntity._address = msg.sender;
        entityStructs.push(newEntity);
    }
    
    function updateEntity(uint _data) public {
        for(uint i = 0; i < entityStructs.length; i++) {
            if(entityStructs[i]._address == msg.sender) {
                entityStructs[i].data = _data;
            }
        }
    }
}
mappingStorage.sol
pragma solidity 0.8.0;

contract mappingStorage {

    struct Entity {
        uint data;
        address _address;
    }

    mapping(address => Entity) entitiyStruct;

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

    function updateEntity(uint _data) public {
        entitiyStruct[msg.sender].data = _data;
    }
}
  1. When executing the addEntity function, which design consumes the most gas (execution cost)? Is it a significant difference? Why/why not?

Array storage cost: 47386 gas
Mapping storage cost: 7254

Mapping is much less expensive compared to an array!

  1. 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: 20942
Mapping: 5515

Mapping is much less expensive because it doesn’t have to loop through the array!

1 Like
pragma solidity 0.8.4;
pragma abicoder v2;

// Only using array
contract World {
    
    struct Entity{
        uint data;
        address ownerAddress;
    }
    
    Entity[] entitiesList;
    
    function addEntity(uint _data) public {
        for (uint i = 0; i < entitiesList.length; i++) {
            if (entitiesList[i].ownerAddress == msg.sender) {
                revert();
            }
        }
        entitiesList.push(Entity(_data, msg.sender));
    }
    
    function updateEntity(uint _data) public {
        for (uint i = 0; i < entitiesList.length; i++) {
            if (entitiesList[i].ownerAddress == msg.sender) {
                entitiesList[i].data = _data;
                return;
            }
        }
        revert();
    }
}
pragma solidity 0.8.4;
pragma abicoder v2;

// Only using mapping
contract World {
    
    struct Entity{
        uint data;
        address ownerAddress;
    }
    
    mapping(address => Entity) entities;
    
    function addEntity(uint _data) public {
        require(entities[msg.sender].ownerAddress != msg.sender);
        entities[msg.sender] = Entity(_data, msg.sender);
    }
    
    function updateEntity(uint _data) public {
        require(entities[msg.sender].ownerAddress == msg.sender);
        entities[msg.sender].data = _data;
    }
}
  1. When executing the addEntity function, which design consumes the most gas (execution cost)?
    With array -> 63126 gas
    With mapping -> 42418 gas

  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?
    With array -> 63126 + 50883 + 53640 + 56397 + 59154 + 19965 = 303165
    With mapping -> 42418 + 42418 + 42418 + 42418 + 42418 + 6480 = 218570

  3. Is it a significant difference? Why/why not?
    The design with array consumes more gas than a mapping design. It consumes 1,4-1,5 times more gas. The difference is big, because in the array design function needs to go through every single item in array to search if it is in the array or not.

1 Like

when executing the addEntity function, the initial array call seems to take an extra 30% (62024 gas). After creation, the rest of the calls to add take the same amount of gas (47024 gas). Comparing to the mapping, each addEntity call uses the same amount of gas (41188 gas), however its slightly lower to add using the mapping. The difference shows that its more efficient to use the mapping in this use case.

I think the question may not be worded correctly. Looking at the updateEntity() times for all of the calls is much more revealing than just looking at updating the 5th. The lookup to find the index in the array uses additional gas for each loop execution (9696, 12330, 14964, 17598, 20232) vs mapping using an identical amount for each (7019)

2 Likes

My Contracts

Array
pragma solidity 0.8.1;

contract GasCompare_Array {
    
    struct Entity{
        uint data;
        address _address;
    }
    
    Entity[] private entities;
    
    function addEntity(uint _data) external returns(bool) {
        entities.push(Entity({data: _data, _address: msg.sender}));
        return true;
    }
    
    function updateEntity(uint _data) external returns(bool) {
        for (uint i = 0; i < entities.length; i++) {
            if (entities[i]._address == msg.sender) {
                entities[i].data = _data;
                break;
            }
        }
        return true;
    }
    
}
Mapping
pragma solidity 0.8.1;

contract GasCompare_Mapping {
    
    mapping(address => uint) private entities;
    
    function addEntity(uint _data) external returns(bool) {
        entities[msg.sender] = _data;
        return true;
    }
    
    function updateEntity(uint _data) external returns(bool) {
        entities[msg.sender] = _data;
        return true;
    }
    
}

Results

Operation Array Tx Cost Array Exec. Cost Mapping Tx Cost Mapping Exec. Cost
addEntity (1) 83949 62485 42153 20689
addEntity (2) 68949 47485 42153 20689
addEntity (3) 68949 47485 42153 20689
addEntity (4) 68949 47485 42153 20689
addEntity (5) 68949 47485 42153 20689
updateEntity 41639 20175 27175 5711

Answers

Adding an entity to the array consumes more gas because the array is a slightly more complex entity in itself. It involves the storage of its length (hence the first push to the array being more expensive because it stores the first non-zero length value) and some boundary checks.

Updating the last entity is much more costly for the array since it has to loop through the entire array in order to find the correct address. The access to the data is direct in the mapping scenario.

2 Likes
  1. The execution cost for addEntity(), is:
    63090 wei for the array contract.
    42371 wei for the mapping contract.

The price difference is relatively significant as the array contract addEntity function costs 50% more gas than in the mapping solution.

  1. The execution cost for the updateEntity() after 5 new entries, is:
    19381 wei for the array contract.
    6428 wei for the mapping contract.
    The array solution consumes much more gas because it has iterate through the entire array.

OnlyArray.sol

pragma solidity 0.7.5;

contract OnlyArray{

    struct Entity{
        address _address;
        uint data;
    }
    
    Entity[] public  Entities;
    
    function addEntity (  uint entityData) public returns (uint rowNumber){
        Entity memory newEntity = Entity(msg.sender, entityData);
        Entities.push(newEntity);
        return Entities.length -1;
    }
   function updateEntity ( uint entityData) public returns (bool success){
        for (uint i=0; i< Entities.length; i++){
            if (Entities[i]._address == msg.sender){
                Entities[i].data = entityData;
                return true;
            }
        }
        return false; 
   }
}

OnlyMapping.sol

pragma solidity 0.7.5;

contract OnlyMapping {

    struct Entity{
        uint data;
        bool isEntity;
    }
    
    mapping ( address => Entity ) public  Entities;
    
    function addEntity (  uint entityData) public returns (bool success){
        if (isEntity(msg.sender)) revert();
        Entities[msg.sender].data = entityData;
        Entities[msg.sender].isEntity = true;
        return true;
    }
   function updateEntity ( uint entityData) public returns (bool success){
        if (!isEntity(msg.sender)) revert();
        Entities[msg.sender].data = entityData;
        return true; 
   }
   function isEntity(address entityAddress) public view returns (bool exists){ 
           return Entities[entityAddress].isEntity;
   }
    
}
1 Like
pragma solidity 0.8.0;

contract mappingWithStruct {

    struct Entity{
    address _address;
    uint data;
  }

  mapping (address => Entity) public entitys;

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

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

pragma solidity 0.8.0;

contract simpleList {

  struct Entity{
    address _address;
    uint data;
  }

  Entity[] public entitys;

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

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

When executing the addEntity function, which design consumes the most gas (execution cost)? Is it a significant difference? Why/why not?
Array(62208) consumes more gas than Mapping(41272). The mapping uses about 2/3 of the gas used by the array which is a significant difference by any standards. However I noticed this is only the case for the first execution of add entity. After the first execution the Array cost only (47208) where as the mapping remains at (41272). Still the mapping is about 13% cheaper for gas usage.

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 consumes far more gas and the amount of gas grows linearly with the amount of data stored in in the array while the gas cost for the mapping is constant.

1 Like

Using Array:

pragma solidity 0.8.0;

//SPDX-License-Identifier: UNLICENSED

// only array

contract bank_array {
    struct Entity {
        uint data;
        address _address;
    }
    
    Entity[] public entityStructs;
    
    function addEntity(uint entityData) public returns(Entity memory){
        Entity memory newEntity;
        newEntity._address = msg.sender;
        newEntity.data = entityData;
        entityStructs.push(newEntity);
        return entityStructs[entityStructs.length - 1];
    }
    
    function updateEntity(uint entityData) public {
       if(entityStructs.length == 0) revert();    
       for (uint i = 0; i < entityStructs.length; i++){
           if (msg.sender == entityStructs[i]._address){
              entityStructs[i].data = entityData;
              break;
           }
       }

    }
}

Using Mapping:

pragma solidity 0.8.0;

//SPDX-License-Identifier: UNLICENSED

// only mapping

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

  1. When executing addEntity() function, execution costs are as follow:
    Using Array: 66518 gas
    Using Mapping: 41476 gas

  2. Added 5 entities with different addresses and then updated the 5th entity, the execution costs are as follow:
    Using Array: 20821 gas
    Using Mapping: 5716 gas

The results show that Array consumes more gas than mapping as Array member is more resource intensive to add/create and also there needs to be iterations through the Array to locate the intended address to update.

2 Likes

Using mapping

pragma solidity 0.7.5;
pragma abicoder v2;
//SPDX-License-Identifier: UNLICENSED

contract onlyMapping{
    
    struct Entity{
    uint data;
    address _address;
    }
    
    mapping (address => Entity) public entityStruct;
    
    function addEntity(uint _data, address _address1) public returns(bool success){
        entityStruct[_address1].data=_data;
        entityStruct[_address1]._address=_address1;
        return true;
    }
    
    function updateEntity(uint _dta, address addr)public returns (bool success){
        entityStruct[addr].data=_dta;
        return true;
    }
}

and using array

pragma solidity 0.7.5;
pragma abicoder v2;
//SPDX-License-Identifier: UNLICENSED

contract onlyArray{
    
    struct Entity{
    uint data;
    address _address;
    }
    
    Entity[] public entityList;
    
    function addEntity(uint _data, address _address1) public returns(Entity memory ){
        Entity memory newEntity;
        newEntity.data=_data;
        newEntity._address=_address1;
        entityList.push(newEntity);
        return entityList[entityList.length - 1];
    }

    function updateEntity(uint dta, address addr) public {
        for (uint i=0;i<entityList.length;i++){
            if (entityList[i]._address==addr){
                entityList[i].data=dta;
            }
        }
    }

}

Mapping consumes 41903gas to add and 5918 to update first / 5918 for the fifth update.
Array consumes 66581gas to add and 9992 to update first / 22795 for the fifth update.

So mapping is cheaper because less steps and data is needed to complete the execution, arrays have to go throuh loops and store more information and more gas is used.

2 Likes

Not sure if I understood the assignment correctly as the contracts are very similar in design and in execution costs. But here they are:

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

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

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

    function addEntity(uint _data) public returns (uint) {
        key++;
        entities[key] = Entity(_data, msg.sender);
        return key;
    }
    
    function updateEntity(uint key, uint _data) public {
        entities[key].data = _data;
        entities[key]._address = msg.sender;
    }   
}

The transaction and execution costs are as follows:
ArrayStorage.addEntity: 84455 + 62991 = 147446
MappingStorage.addEntity: 85231 + 63767 = 148998

ArrayStorage.updateEntity: 34587 + 12931 = 47518
MappingStorage.updateEntity: 32961 + 11305 = 44266

The execution costs are very similar, with the MappingStorage slightly cheaper for updating but slightly more expensive for adding. Since both solutions use direct access using the key, the costs should be similar. But I guess I’m missing something here.

1 Like

Here is my code for StorageDesignArray

pragma solidity 0.8.0;

contract StorageDesignArray {
    
    // Struct
    struct Entity {
        uint data;
        address _address;
    }
    
    // Array
    Entity[] entityList;
    
    // Functions
    // Creates a new entity for msg.sender and adds it to the array.
    function addEntity(uint _data) public {
        Entity memory newEntity;
        newEntity.data = _data;
        newEntity._address = msg.sender;
        entityList.push( newEntity );
    }
    
    // Updates the data in a saved entity for msg.sender
    function updateEntity(uint _data) public {
      //loop through entityList to match address to msg.sender
       for(uint i = 0; i < entityList.length; i++) {
           if(entityList[i]._address == msg.sender) continue;
        //update data 
        entityList[i].data = _data;
       }
    }
}

Here are my StorageDesignArray gas cost

Deploy:
StorageDesignArrayDeploy
addEntity index 0:
StorageDesignArrayAddEntity _Index_0
addEntity index 1:
StorageDesignArrayAddEntity_Index_1
addEntity index 4:
StorageDesignArrayAddEntity_Index_4
updateEntity index 4:
StorageDesignArrayUpdateEntity_Index_4

Note:
addEntity[0] higher gas cost as starting index.
addEntity[1] -> addEntity[4] all had same gas cost:
transaction cost -17.89% to index 0
execution cost -24.04% to index 0

Here is my code for StorageDesignMapping

pragma solidity 0.8.0;

contract StorageDesignMapping {
    
    struct Entity {
        uint data;
        address _address;
    }
    
    mapping (address => Entity) public entityList;
    
    // Creates a new entity for msg.sender and adds it to the array.
    function addEntity(uint _data) public {
        entityList[msg.sender] = Entity(_data, msg.sender);
    }
    
    // Updates the data in a saved entity for msg.sender
    function updateEntity(uint _data) public {
        // checks that entityList address is a match to msg.sender
        require(entityList[msg.sender]._address == msg.sender);
        // update data of msg.sender
        entityList[msg.sender].data = _data;
    }
 }   

Here are my StorageDesignMapping gas cost

Deploy:
StorageDesignMappingDeploy
addEntity index 0:
StorageDesignMappingAddEntity_Index_0
addEntity index 1:
StorageDesignMappingAddEntity_Index_1
addEntity index 4
StorageDesignMappingAddEntity_Index_4
updateEntity index 4:
StorageDesignMappingUpdateEntity_Index_4

Note:
addEntity[0] -> addEntity[4] all had same gas cost:

Summary:
StorageDesignMapping cost about 10% more to deploy than StorageDesignArray, but the costs are significantly less for function calls.

Gas Cost Comparison StorageDesignArray to StorageDesignMapping figures.

Deploy:
transaction: mapping cost 10.46% > array cost
execution: mapping cost 10.31% > array cost

addEntity() to index 0
transaction: mapping cost -24.97% array cost
execution: mapping cost -33.56% array cost

addEntity() to index 1 -> 4
transaction: mapping cost -8.62% array cost
execution: mapping cost -12.53% array cost

updateEntity() to index 4
transaction: mapping cost -53.51% array cost
execution: mapping cost -83.19% array cost

2 Likes

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

Adding the first entity into a mapping/array does not match the gas cost of oncoming ones. Then is stabilizes at values:

  • Array 47286 gas
  • Mapping 41450 gas

Which is not really a ground breaking difference. Both are O(1) complexity.

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 difference is expected to be huge here. Because we need to loop through the whole array. The results correspond to this idea:

  • Array 20917 gas
  • Mapping 5515 gas

If you add even more elements the cost of the array still grows because update has a O(n) complexity in an array. In the mapping the cost reaming the same. Update has a O(1) complexity in a mapping.

2 Likes

Mapping Contract:
Deployment Cost: 130777 gas
AddEntity: Execution Cost: 41354 gas (constant)
UpdateEntity: Execution Cost for last item: 5437 gas (constant)

Array Contract:
Deployment Cost: 191432 gas
AddEntity: Execution Cost: 63091 gas for the first one, 48091 gas for the following
UpdateEntity: Execution Cost for last item: 19403 gas (16769 for the fourth, 14135 for the third, 11501 for the second, and 8867 for the first)

When executing the addEntity function, which design consumes the most gas (execution cost)? Is it a significant difference? Why/why not?
The Array Contract was more expensive in terms of gas consumption (63k for the first item, 48k for the following ones, against a constant 41k gas fee in the Mapping Contract).
If we don’t consider the first entry of the Array Contract, those costs are constant, so, even if there is a 16% difference in gas cost between both of them, it is not significant for me, as those costs don’t depend on the amount of stored entities.

Add 5 Entities into storage using the addEntity function and 5 different addresses. Then update the data of the fifth address you used. Do this for both contracts and take note of the gas consumption (execution cost). Which solution consumes more gas and why?
The execution cost for updating an entity on the Array Contract is almost 4 times the one from the Mapping Contract (19403 vs 5437 gas).
This difference may be explained because a Mapping is a key=>value structure, so it looks up for the key, and then it replaces the proper value. For this reason, the update cost is constant (ie: O(1)). Meanwhile, we should iterate over the array structure in order to find the proper address, then it replaces the value. As the lookup is done through a loop cycle, its cost depends on the size of the array (2634 gas for each additional item). This linear relationship between size and cost is significant, as it grows depending on the amount of users we have, which is something that we really don’t want.
In this case it is even worst, as every new user will have its transaction cost increased compared with the previous one, which will naturally discourage our dapp adoption.

3 Likes

Array Contract Code:

pragma solidity 0.7.5;

contract ArrayContract
{
    struct Entity{
        uint _data;
        address _address;
    }
    Entity[] entityList;
    
    function addEntity(uint data) public returns (uint)
    {
        Entity memory newEntity;
        newEntity._address = msg.sender;
        newEntity._data = data;
        entityList.push(newEntity);
        return entityList.length - 1;
    }
    function updateEntity(uint data) public returns (bool)
    {
        for(uint i = 0; i < entityList.length; i++)
        {
            if(entityList[i]._address == msg.sender)
            {
                entityList[i]._data = data;
                return true;
            }
        }
        return false;
    }
    function getEntityData() public view returns (uint)
    {
        for(uint i = 0; i < entityList.length; i++)
        {
            if(entityList[i]._address == msg.sender)
            {
                return entityList[i]._data;
            }
        }
        return 0;
    }
}

Mapping Contract Code:

pragma solidity 0.7.5;

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

Array contract:

pragma solidity 0.7.5;

contract Cost{

struct Entity{
    uint data;
    address _address;
}

Entity[] entities;

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

function updateEntity(uint _index, uint newdata) public returns (bool){
    entities[_index].data = newdata;
    return true;
}
}

Mapping contract:

pragma solidity 0.7.5;

contract Cost{

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 _index, uint _data) public returns (bool){
    entities[msg.sender].data = _data;
    return true;
}
}
  1. when executing the addEntity() function we can see that the array contract consumes 62265gas fee. On the other hand, the mapping contract consumes 41276gas fee.
    The mapping contract consumes almost 1/3 less than the array as we need to iterate through the array in order to add any entities which means more gas.

  2. when adding 5 entities and updating the fifth element we still see that the array contract consumes more gas but with not significant difference. Array contract gas consumption was 6224 and the consumption of the mapping contract was 5444.

Does this sound correct?

2 Likes

Hi!
When using mappings, there’s no need for the Struct, it just duplicates the address.
Since most of the other posts I saw mapped to the struct anyway, and you can read the result here in the forum I decided to simply map address to data.

I used public functions, assuming I’d want to call the functions from within the contract later on.

Here are my results:

Using arrays:
Five adds and one update cost 249,761 gas
One add cost 47,531 gas

Using mappings:
Five adds and one update cost 109,370 gas
One add cost 20,732 gas

The mappings use a lot less storage in my version, and update uses little compute resources:
Mappings save 56% gas!!

Code (no error handling!): But I discovered I can have 2 contracts in one file :slight_smile:

pragma solidity 0.8.0;

contract ArrayTest{
    
    struct Entity{
        uint data;
        address _address;
    }
    
    Entity[] entities;
    
    function addEntity(uint _data, address _address) public{
        entities.push(Entity(_data, _address));
    }
    
    function updateEntity(uint _data, address _address) public returns (bool){
        for(uint i=0; i<entities.length; i++){
            if(entities[i]._address == _address) { 
                entities[i].data = _data; 
                return true; 
            }
        }
        return false;
    }
} 

contract MappingTest{
    
    mapping(address => uint) entities;
    
    function addEntity(uint _data, address _address) public{
        entities[_address] = _data;
    }
    
    function updateEntity(uint _data, address _address) public{
        entities[_address] = _data; 
    }
}   
2 Likes
StorageDesign.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract StorageDesignMapping {
    
    struct Entity {
        uint data;
        address _address;
    }
    
    mapping(address => Entity) public entityStructs;
    
    function mappingAddEntity(address entityAddress, uint entityData) public returns(bool success) {
        entityStructs[entityAddress].data = entityData;
        entityStructs[entityAddress]._address = entityAddress;
        return true;
    }
    
    function mappingUpdateEntity(address entityAddress, uint entityData) public returns(bool success) {
        entityStructs[entityAddress].data = entityData;
        return true;
    }
}

contract StorageDesignArray {
    
    struct Entity {
        uint data;
        address _address;
    }
    
    Entity[] public entityStructs;
    
    function arrayAddEntity(address entityAddress, uint entityData) public returns(bool success) {
        Entity memory newEntity;
        newEntity._address = entityAddress;
        newEntity.data = entityData;
        entityStructs.push(newEntity);
        return true;
    }
    
    function arrayUpdateEntity(address entityAddress, uint entityData) public returns(bool success) {
        uint entityId = 0;
        while(entityStructs[entityId]._address != entityAddress){
             entityId++;   
        }
        entityStructs[entityId].data = entityData;
        return true;
    }
}

(NOTE: the code isn’t production-ready, and has no in-built debugging tools)

mappingAddEntity transaction log

mappingAddEntity

arrayAddEntity transaction log

arrayAddEntity

The addArrayEntity function costs about 40% more gas to execute than the mappingAddEntity function. This is probably because an array of structs costs takes up more persistent storage than just structs plus some links (i.e. the mapping) between them.

mappingUpdateEntity transaction log

mappingUpdateEntity

arrayUpdateEntity transaction log

arrayUpdateEntity

The arrayUpdateEntity function costs about 57% more gas to execute than the mappingUpdateEntity function. This is because the arrayUpdateEntity function has to iterate through the array to find the correct address, whilst the mappingUpdateEntity function is based on key-value pairs, so you only have to find the correct key.

A summary is neatly presented here:

Big-O notation for data structures

big-o-notation
Source

1 Like

Mapping:

pragma solidity >=0.7.0 <0.9.0;

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

mapping(address => Entity) entities;

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

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

}

addEntity cost : 66,260 gas
total cost (after 5 addEntity, and 1 updateEntity function) : 358,221 gas

Array:

pragma solidity >=0.7.0 <0.9.0;

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

Entity[] entities;


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

function updateEntity(uint data, address _address) public pure returns(bool success) {
    Entity memory updatedEntitiy;
    updatedEntitiy._address = _address;
    updatedEntitiy.data = data;
    return true;
    
}


}




addEntity cost : 73,113 gas
total cost (after 5 addEntity, and 1 updateEntity function) : 388,092 gas total

After all, the difference between the gas costs are not significantly high for the contracts.
addfunction difference %10
total cost difference %8

1 Like
pragma solidity 0.8.4;

contract storageDesignAssignmentMapping {
    
    struct Entity {
        uint data;
        address _address;
    }
    
    mapping (address => Entity) public entityStruct;
    
    
    function addEntity(uint entityData) public returns (bool success) {
        entityStruct[msg.sender] = Entity(entityData, msg.sender);
        return true;
    }
    
    function updateEntity(uint entityData) public returns (bool success) {
        entityStruct[msg.sender].data = entityData;
        return true;
    }
}
pragma solidity 0.8.4;

contract storageDesignAssignmentArray {
    
    struct Entity {
        uint data;
        address _address;
    }
    
    Entity[] public entity;
    
    
    function addEntity(uint data) public returns (bool success) {
        Entity memory newEntity;
        newEntity._address = msg.sender;
        newEntity.data = data;
        entity.push(newEntity);
        return true;
    }
    
    function updateEntity(uint entityData) public returns (bool success) {
        uint arrayLength = entity.length;
        for (uint i=0; i<arrayLength; i++) {
            if (entity[i]._address == msg.sender)
            entity[i].data = entityData;
        }
        return true;
    }
}

addEntity gas cost with array = 88489 for the first addEntity then 71389 for the rest
addEntity gas cost with mapping = 66253

Quite a difference between them.

updateEntity gas cost with Array = 41670
updateEntity gas cost with Mapping = 26921

The array consumes more gas because it has to loop through the array to find the address mapping

1 Like

Storage design

  • Array
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.7.5;

contract GasArray{
    
    
    struct Entity{
        uint _data;
        address _address;
    }
    
    Entity[] entities;
    
    
    function addEntity(uint data) public{
        Entity memory entity;
        entity._data = data;
        entity._address = msg.sender;
        entities.push(entity);
    }
    
    function updateEntity(uint data) public returns(bool updated){
        bool flag;
         for(uint i = 0; i < entities.length; i++){
             if(entities[i]._address==msg.sender){
            entities[i]._data = data;
            flag = true;
        }
    }
        return flag;
        
    }
}
  • Mapping
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.7.5;

contract GasMapping{
    
    
    struct Entity{
        uint _data;
        address _address;
    }
    
    mapping(address => Entity) entities;
    
    function addEntity(uint data) public returns(bool added){
        entities[msg.sender]= Entity(data, msg.sender);
        return true;
    }
    
    function updateEntity(uint data) public returns(bool updated){
        require(entities[msg.sender]._address == msg.sender);
        entities[msg.sender]._data = data;
        return true;
    }
}
  1. When executing the addEntity function, I got the following results:
  • Array
    gasArray1
  • Mapping
    gasMappingCreate1

It is a significant difference and it is do to computationally heavier array, that uses more space for saving the structs and connecting them.

  1. Added 5 Entities into storage using the addEntity function and 5 different addresses and updated the data of the fifth address.
  • Array
    gasArrayUpdate5
  • Mapping
    gasMappingUpdate5

The execution cost of the array is even more noticeable when changing an element’s data, growing linearly with the number of elements added to the array, because of the iterable quality of arrays, making mappings less expensive.

1 Like