Assignment - Storage Design

Probably the creation of the value, can’t tell for sure, but in terms of compatibility, mappings are THE WAY to save gas when adding new values to the database, usually the arrays is the way to go in other languages, but in solidity which deal with gas fees, the array cost more than a mapping.

Also updating an array cost more, simply because you have to loop over the array to update the value, which is defined in more processing calculation for it than the mapping which is more direct (key => value).

Nice graph :nerd_face: its clear to understand the comparison between both

Carlos Z

1 Like

Taking this assignment as a reflection on the courses’ topic on gas consumption through usage of various storage types. I decided to just use the pure looping for updateEntity instead of update through providing index as a parameter or other strategy to reduce gas consumption.

The following code and result is for the one without any exception handling and with pure looping.

1.1 When executing the addEntity function, which design consumes the most gas (execution cost)?

Given below are the figures of gas consumption by two different contracts:

addEntity Gas cost using Mapping:
image

addEntity Gas cost using Array:
image

1.2. Is it a significant difference? Why/why not?

Average Gas for Mapping: 65000
Average Gas for Array: 70000
Difference in Gas: 7%

Here, the contract using an array consumes around 7% more gas compared to mapping.

2.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).

Given below are the figures of gas consumption by two different contracts:

updateEntity Gas cost using Mapping:
image

updateEntity Gas cost using Array:
image

2.2. Which solution consumes more gas and why?

Average Gas for Mapping: 26000
Average Gas for Array: 41000
Difference in Gas: 57%

Here, the contract using an array consumes significantly more gas (57%) compared to mapping.
Usage of array consumed more gas compared to mapping because the mapping has a direct link to the data in the storage. Whereas, the array had to consume more gas to perform loop through all its existing data until it found the correct data to update.

2 Likes

mapping–>
deploy: 234650
Add: 66500*5= 332500
modify5: 26619

array–>
deploy:242576
add: 71435*5= 357175
modify5: 41913

Well, like we can see from data yes, a consistent difference.

Like I said before, the array version consume very much more. In the updating part we need to use a for loop, and this is so expensive, because in this way we need to check every single component of the loop for find the right address and modify his data.

2 Likes

1. When executing the addEntity() function, which design consumes the most gas (execution cost)? Is it a significant difference?
The contract using the arrays consumed the most gas (the mapping contract consumed 66,054 vs the array contract consuming 88,190 gas). It is quite a bit more, which is interesting as I thought that appending to the end of an array would be less gas.

1.a. Why/why not?
I believe it’s because the mapping approach points directly to a specific location in the mapping (whether it exists or not) and doesn’t need to work with an enumerable object by appending to the end of it.

2. (after adding 5 Entities into storage using addEntity() using 5 different addresses and updating the 5th record for both contracts) Which solution consumes more gas and why?

The contract using the array consumes more gas because it needs to loop through the entire array until it finds the correct record to update; whereas the contract using the mapping takes the same amount of gas regardless of updating the first, 3rd, 5th, etc addresses because the address is the direct location of the data to update (no looping required).


See my code below:
pragma solidity 0.8.7;

// Only using mappings:
// Deploy gas:
// - transaction cost: 202459
// - execution cost:   202459
contract GasTest1 {
    struct Entity {
        uint256 _data;
        address _address;
    }
    mapping(address => Entity) entities;

    // addEntity creates a new entity for msg.sender by adding it to entities.
    // transaction cost: 66054
    // execution cost:   66054
    function addEntity(uint256 _data) public {
        entities[msg.sender] = Entity(_data, msg.sender);
    }

    // updateEntity updates the data in a saved entity for msg.sender.
    // 1 record:
    //   transaction cost: 28984
    //   execution cost:   28984
    // 5 records, updating 3rd:
    //   transaction cost: 28984
    //   execution cost:   28984
    // 5 records, updating last:
    //   transaction cost: 28984
    //   execution cost:   28984
    function updateEntity(uint256 _data) public {
        require(entities[msg.sender]._address == msg.sender);
        entities[msg.sender]._data = _data;
    }
}

// Only using arrays:
// Deploy gas:
// - transaction cost: 229340
// - execution cost:   229340
contract GasTest2 {
    struct Entity {
        uint256 _data;
        address _address;
    }
    Entity[] entities;

    // addEntity creates a new entity for msg.sender by adding it to entities.
    // transaction cost: 88190 (now 71090)
    // execution cost:   88190 (now 71090)
    function addEntity(uint256 _data) public {
        entities.push(Entity(_data, msg.sender));
    }

    // updateEntity updates the data in a saved entity for msg.sender.
    // 1 record:
    //   transaction cost: 31341
    //   execution cost:   31341
    // 5 records, updating 3rd:
    //   transaction cost: 36655
    //   execution cost:   36655
    // 5 records, updating last:
    //   transaction cost: 41969
    //   execution cost:   41969
    function updateEntity(uint256 _data) public {
        for (uint256 i = 0; i < entities.length; i++) {
            if (entities[i]._address == msg.sender) {
                entities[i]._data = _data;
                break;
            }
        }
    }
}

// Using a combination of mapping and array:
// Deploy gas:
// - transaction cost: 246638
// - execution cost:   246638
contract GasTest3 {
    struct Entity {
        uint256 _data;
        uint256 _listPointer;
    }
    mapping(address => Entity) entities;
    address[] entityList;

    // addEntity creates a new entity for msg.sender by adding it to entities.
    // transaction cost: 90762 (now 93562)
    // execution cost:   90762 (now 93562)
    function addEntity(uint256 _data) public {
        entityList.push(msg.sender);
        entities[msg.sender] = Entity(_data, entityList.length - 1);
    }

    // updateEntity updates the data in a saved entity for msg.sender.
    // 1 record:
    //   transaction cost: 33264
    //   execution cost:   33264
    // 5 records, updating 3rd:
    //   transaction cost: 33264
    //   execution cost:   33264
    // 5 records, updating last:
    //   transaction cost: 33264
    //   execution cost:   33264
    function updateEntity(uint256 _data) public {
        require(entityList[entities[msg.sender]._listPointer] == msg.sender);
        entities[msg.sender]._data = _data;
    }
}

1 Like

Mapping Code:

pragma solidity 0.8.0;

contract Mappingtest {

    struct Entity {
        uint data;
        address _address;
    }

    mapping (address => Entity) Entities;

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

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

Array Code:

pragma solidity 0.8.0;

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

    Entity [] public Entities;

    function addEntity (uint data) public {
        Entity memory addedEntity;
        addedEntity.data = data;
        addedEntity._address = msg.sender;
        Entities.push(addedEntity);
    }

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

    }
}

Conclusion:
Mapping solution is cost efficient in comparison to the array.
Arrays consume more gas since it takes longer to go through the array to loop. However mapping entries have direct access to key value therefore updating 1st, 3rd, and 5th therefore not impacting the gas fees.

1 Like

Hi, this is my response to assignment storage.

pragma solidity ^0.8.7;
contract storageArray{
    struct Entity{
        uint data;
        address _address;
    }

    Entity[] entityList;
    event newUpdate(uint _index);
    event newEntity(uint _index);

    function addEntity(uint _data) public returns(uint){
        // add a new entity to entityList array
        (bool exist,uint index) = exist(msg.sender);
        if(exist)revert();
        entityList.push(Entity(_data,msg.sender));
        emit newEntity(entityList.length);
        return entityList.length;
    }

    function updateEntity(uint _data) public returns(uint){
        (bool exist,uint index) = exist(msg.sender);
        if(!exist) revert();
        entityList[index].data = _data;
        emit newUpdate(index);
        return index;
    }

    function exist(address _addr) private returns(bool exist ,uint position){
        if(entityList.length == 0){
            exist = false;
        }
        for(uint i = 0;i < entityList.length; i++){
            if(entityList[i]._address == _addr){
              exist = true;
              position = i;
            }
        }
        return(exist,position);
    }
    
    function getEntity() public  returns(Entity memory){
         (bool exist,uint index) = exist(msg.sender);
          if(!exist)revert();
          return entityList[index];
    }
}
pragma solidity ^0.8.7;
contract StorageMapping {
       struct Entity{
        uint data;
        address _address;
    }
    mapping(address => Entity) entities;
    event newUpdate(address _addr);
    event newEntity(address _addr);

    function addEntity(uint _data) public {
        if(exist(msg.sender))revert();
        entities[msg.sender] = Entity(_data,msg.sender);
        emit newEntity(msg.sender);
    }

    function updateEntity(uint _data) public{
        if(!exist(msg.sender))revert();
        entities[msg.sender].data = _data;
        emit newUpdate(msg.sender);
    }

    function exist(address _addr) private returns(bool){
        if(entities[msg.sender]._address == address(0)){
            return false;
        }else{
            return true;
        }
    }

    function getEntity() public view returns(Entity memory){
        return  entities[msg.sender];
    }
}

1- the array storage addEntity() consume more gas than the addEntity() function of the mapping storage. it is significant difference 18696 gas for the same address and same data value. I think it depends on my exist() function which check for duplicates.
2- the array storage consume more gas, because of the iteration.

1 Like

Hello everybody,

I got a quick question,
I’m right now at “Storage Design Patterns” section, and I saw something that I just did not understand.
In the video “Simple Array Mapping”, there is something in the function newEntity code that I don’t understand why it’s there.

  function newEntity(address entityAddress, uint entityData) public returns(EntityStruct memory) {
    EntityStruct memory newEntity;
    newEntity.entityAddress = entityAddress;
    newEntity.entityData    = entityData;
    entityStructs.push(newEntity);
    return entityStructs[entityStructs.length - 1];
  }

Why is -1 in the return line?
Can someone explain it to me?

Thanks!!

Keyword .lenght will return the number of elements of the variable, in case of arrays:

Example:
if you have an array of 3 elements, array = ['one', 'two', 'three'], the lenght will be 3.
But the index does not start at 1, ‘one’ is at the zero position on the array (array[0]), so the last element ‘three’, its position is not 3, is 2, so if you want to get the index position of the last element, you can just call the length of the array, remove 1 from the result (3 -1).

Hope it helps.

Carlos Z

You didn’t specify some very important constraints that will make a huge difference in gas usage.

  1. What does updateEntity mean? Can I just overwrite an already existing Entity with new data? Or do I have to add data to a possibly already existing data?
  2. Are duplicate Entities allowed?
  3. If I addEntity and it already exists, should I update it? Don’t do anything? or overwrite it?
  4. If I updateEntity but that Entity doesn’t exist, should I add it? Or do nothing? If I can add it implicitly, then no addEntity is even needed.
1 Like

The array (list) method increased in cost as the number of entities grew. This was exactly as expected. Lesson: don’t use arrays for things requiring fast write access. Read access was twice as fast with the mapping solution and I’d expect that to stay constant with the map, but grow linearly with the array.

pragma solidity >=0.8.7;

// SPDX-License-Identifier: asdf

// You don't really need an addEntity function, only an updateEntity.
// These two conditions were not specified one way or the other in the spec
// 1) don't allow duplicates. You'll have to do an expensive check for the existence of an entry even with addEntity
// 2) if updateEntity updates a non-existent entity, add it. 

contract MapGas {
  // costs
    // add entities: 
    // 1 66054
    // 2 66054     
    // 3 66054
    // 4 66054
    // 5 66054
    //  update 5th: 26926

    struct Entity {
        uint data;
        address _address;
    }

    mapping (address => Entity) Entities;

 
    function addEntity (uint _data) external {
                Entities[msg.sender] = Entity(_data, msg.sender); // Entity doesn't need _address member really
    }

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

}

contract ListGas {
   // costs
    // add entities: 
    // 1 90625
    // 2 76075      
    // 3 78625
    // 4 81175
    // 5 83725
    //  update 5th: 43940

    struct Entity {
        uint data;
        address _address;
    }

    Entity[] Entities;

    uint MAX_INT = 2**256-1;  // used to indicate "not found"

    function FindEntity(address _address) private view returns(uint) {
        uint last = Entities.length;// avoid calculating Entities.length at each iteration
        for (uint i = 0; i < last; i++) {
            if (Entities[i]._address == _address) {
                    return i;
            }
        }
        return MAX_INT; // use of int256 solely for the purpose of allowing this to indicate "not found"
    }

    // costs:
    // add first entity: 28531, 28531
    function addEntity (uint data) external {
        uint index = FindEntity(msg.sender);
        if (index == MAX_INT) {
                Entities.push(Entity(data, msg.sender));
        }
    }

    function updateEntity(uint _data) external {
        uint index = FindEntity(msg.sender);
        if (index != MAX_INT) {
                Entities[index].data += _data;
       }
    }

}

1 Like

Hello Everybody!

Hope everyone is starting their new year with their right foot.

I wanted to make both contracts as simple as possible.
The complexer a function, the more gas it burns.

This is my Array Solution

pragma solidity 0.8.0;

contract ArraySolution{

    struct Entity{
        uint data;
        address _address;
    }

    Entity[] public Entitys;

    function addEntity(uint data) public{
        Entity memory newEntity;
        newEntity.data = data;
        newEntity._address = msg.sender;
        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;
            }
        }
    }
}

This is my Mapping Solution

pragma solidity 0.8.0;

contract MappingSolution{

    struct Entity{
        uint data;
        address _address;
    }

    mapping(address => Entity) public Entitys;

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

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

Questions:

  1. The array solution consumes more gas. It’s not a big difference, but there is a difference.
    Mapping execution cost = 66080 gas
    Array execution cost = 71212
    I tested it on the sixth index, so that could be the reason behind the gas consumption

  2. As everyone would guess, the Array solution consumed more gas than Mapping.
    The reason behind this is because the array solution has to take more steps before it can edit the data.
    Mapping execution cost = 26741 gas
    Array execution cost = 42268 gas
    As you can see the difference is almost twice as expensive as the mapping.

Every solution has it’s Pros and Cons.

Just as Fillip said.

1 Like
Contracts code (click to expand)
pragma solidity ^0.8.11;

struct Entity{
    uint data;
    address _address;
}

contract storage_mapping{

    mapping(address => Entity) public entities;

    function addEntity(uint _entityData) public returns(bool success){
        require(entities[msg.sender]._address!=msg.sender, "This address is already present in the smart contract");
        entities[msg.sender]._address = msg.sender;
        entities[msg.sender].data = _entityData;
        success = true;
    }

    function updateEntity(uint _entityData) public returns(bool success){
        require(entities[msg.sender]._address==msg.sender, "This address does not have any data yet, please use addEntity first");
        entities[msg.sender].data = _entityData;
        success = true;
    }


}

contract storage_array{

    Entity[] public entities;

    function findEntity(Entity[] storage list, address entityAddress) view internal returns(int index){  // Returns ID or -1 if not found
        if(list.length == 0) return -1;
        for(int id=0; id<int(list.length); ++id){
            if(entityAddress == list[uint(id)]._address) return id;
        }
        return -1;
    }

    function addEntity(uint _entityData) public returns(bool success){
        require(findEntity(entities, msg.sender)==-1, "This address is already present in the smart contract");
        Entity memory newEntity;
        newEntity._address = msg.sender;
        newEntity.data = _entityData;
        entities.push(newEntity);
        success = true;
    }

    function updateEntity(uint _entityData) public returns(bool success){
        int index = findEntity(entities, msg.sender);
        require(index>=0, "This address does not have any data yet, please use addEntity first");
        entities[uint(index)].data = _entityData;
        success = true;
    }

}
Execution costs (click to expand)

addEntity
Iteration 1: with array storage 68792, with mapping storage 46638
Iteration 2: with array storage 54475, with mapping storage 46638
Iteration 3: with array storage 57128, with mapping storage 46638
Iteration 4: with array storage 59781, with mapping storage 46638
Iteration 5: with array storage 62434, with mapping storage 46638

updateEntity (on the 5th entity)
With array storage 39586, with mapping storage 26396

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

  • The array solution consumes more gas.
  • In addition, as new entities are added the gas consumed by such solution keeps growing (as array lookups require a higher amount of iterations if the array is larger).
  • Instead, the gas used by the mapping solution remains the same regardless the amount of entities.
  • Consequently, while the difference is negligible for the first few entities, it becomes significant when the number of entities grows a lot.

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?

  • Once again, the solution which consumed more gas was the array storage. The reason is already explained above.
1 Like

Array Contract

AddEntity - 88489, 71389, 71389, 71389, 71389, Update - 29336

Mapping Contract

AddEntity - 66375, 66375, 66375, 66375, 66375, Update - 27490

  1. Array requires more gas for both functions. Because it’s expensive to lookup arrays.
1 Like

Array is more expensive on both addEntity and updateEntity.
That’s because when pushing it has to allocate new memory in the storage (addEntity), and then it will have more elements to iterate (updateEntity)

Hi, here’s my answers.

Array-Based Design

// SPDX-License-Identifier: GPL 3.0

pragma solidity 0.7.5;

contract ArrayBasedDesign {
    struct Entity {
        uint data;
        address entityAddress;
    }
    Entity[] entities;

    function addEntity() public {
        entities.push(Entity({
            data: 0,
            entityAddress: msg.sender
        }));
    }

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

Mapping-Based Design

// SPDX-License-Identifier: GPL 3.0

pragma solidity 0.7.5;

contract MappingBasedDesign {
    struct Entity {
        uint data;  
        address entityAddress;
    }
    mapping(address => Entity) entities;

    function addEntity() public {
        entities[msg.sender] = Entity({
            data: 0,
            entityAddress: msg.sender
        });
    }

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

Execution cost comparison table

Question #1

When executing the addEntity function, which design consumes the most gas (execution cost)?

The array design consumes the most gas when executing the addEntity (67888 gas; while the mapping design consumes 45752 gas).

Is it a significant difference?

I don’t know the threshold at which the difference of gas consumption starts to become significant.
I take a wild guess!
Because:

So, 22136 gas could be a significant difference.

Why?

I believe that the reason is the array design requires CREATE operations to create a new entity and append the entity to the array.

On the other hand, in the mapping design, when we add a new entity to the mapping, what goes under the hood is that every address is mapped to a default entity with data=0, entityAddress=0x0, so when the addEntity is executed, the storage value entityAddress is set to non-zero address from 0x0 and the storage value’s zeroness data remains unchanged.

No such things as new entities being created or get pushed to the mapping (CREATE operations), only changing value from one state to the other or remaining unchanged (SSTORE operations).

The result is a significant difference of gas consumption between the two designs.

Question #2

Add 5 Entities into storage using the addEntity function and 5 different addresses. Then update the data of the fifth address you used. Do this for both contracts and take note of the gas consumption (execution cost).

Which solution consumes more gas?

The array design consumes more gas to update the data of the fifth address.

Why?

In the array design, the updateEntity function needs to run a test on every element of the array until it finds out the right entity for the msg.sender, then the function can possibly update the data of that entity.
Updating the 5th entity’s data consumes more gas (58599 gas) than the 2nd one’s (50997 gas), because finding the 2nd entity only takes 2 tests.

In the mapping design, without running a single test, the updateEntity can identify the entity instantly and update its data.
Regardless of the order of an entity being added to the mapping, the amount of gas consumption is the same for updating every entity (43819 gas).

So, because of running multiple tests to identify the 5th entity before updating its data, the array design consumes more gas than the mapping design.

1 Like

addEntity() consumed 89600 gas when the value 12 entered in Array Storage, but it consumed 66736 gas in Mapping Storage with the same value. It is a significant difference, especially in mapping storage, it costs lesser gas than the array method. Array method definitely consumes more energy on data saving and requires large function declaration.

The updating costs more expensive in array than mapping, cos’ we have to loop through the array until the address value matches with msg.sender. Loops consumes more energy than getting value from an indexed mapping list.

Contracts:

pragma solidity 0.8.0;

contract EntitiesByArray {

	struct Entity {
		uint data;
		address _address;
	}

	Entity[] public entityArray;

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

	function updateEntity(uint newData) public returns(bool success) {
		for (uint i=0; i<entityArray.length; i++) {
			if (entityArray[i]._address == msg.sender) {
				entityArray[i].data = newData;
				break;
			}
		}
		return true;
	}

	/*function showTheList() public view returns(Entity[] memory) {
		return entityArray;
	}*/

/* CONSOLE:
addEntity():
0x5B3...dC4, value: 12, gas: 89600
0xAb8...cb2, value: 12, gas: 72500
0x4B2...2db, value: 12, gas: 72500
0x787...baB, value: 12, gas: 72500
0x617...7f2, value: 12, gas: 72500

updateEntity():
0x617...7f2, value: 24, gas: 42179
*/

}

contract EntitiesByMapping {

	struct Entity {
		uint data;
		bool isEntity;
	}

	mapping( address => Entity ) public EntityMap;

	function addEntity(uint passData) public returns(uint receipt) {
		if ( isEntity() ) revert();
		EntityMap[msg.sender].data = passData;
		EntityMap[msg.sender].isEntity = true;
		return EntityMap[msg.sender].data;
	}

	function updateEntity(uint theData) public returns(uint result) {
		if ( !isEntity() ) revert();
		EntityMap[msg.sender].data = theData;
		return EntityMap[msg.sender].data;
	}

	function isEntity() public view returns(bool isIndeed) {
		return EntityMap[msg.sender].isEntity;
	}


/* CONSOLE:
addEntity():
0x5B3...dC4, value: 12, gas: 66736
0xAb8...cb2, value: 12, gas: 66736
0x4B2...2db, value: 12, gas: 66736
0x787...baB, value: 12, gas: 66736
0x617...7f2, value: 12, gas: 66736

updateEntity():
0x617...7f2, value: 24, gas: 29393
*/

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

Yes, the array contract uses 71691 gas and the mapping contract uses 43809 gas. The array contract uses 28152 more gas than the mapping contract uses.This is because an array must use an iterative strategy to loop through all of the elements and find the one it is looking for, where as a mapping uses a key to find its value almost instantly.

  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?

Yes, the array contract uses much more gas. To update the 5th address data, my array contract used 42490 gas, while my mapping only used 26933 gas. That is a 15557 difference in gas usage! See my answer to question number 1 for my explanation of why this occurs.

Array Contract

pragma solidity 0.8.0;

contract EntityArray {

    struct EntityStruct {
        uint data;
        address _address;
    }

    EntityStruct[] entityArray;

    function addEntity(uint _data) public returns (uint rowNumber) {
        EntityStruct memory newEntity;
        newEntity.data = _data;
        newEntity._address = msg.sender;
        entityArray.push(newEntity);
       return entityArray.length -1;
    }

    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;
    }

    function getEntityCount() public view returns (uint entityCount) {
        return entityArray.length;
    }

    function getEntity() public view returns (uint entityData) {
        for (uint i=0; i<entityArray.length; i++) {
            if(entityArray[i]._address == msg.sender) {
                return entityArray[i].data;
            } 
        }
        
    }
}

Mapping Contract

pragma solidity 0.8.0;

contract EntityMapping {

    struct EntityStruct {
        uint data;
        address _address;
    }

    mapping(address => EntityStruct) entities; 

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

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

    function getEntity() public view returns (uint) {
        return entities[msg.sender].data;
    }
}
1 Like

Why couldn’t we just use Linked Lists? Its easy to delete elements in the beggining and the middle. Also really nice and clean in memory.

  1. When executing the addEntity function, which design consumes the most gas (execution cost)? Is it a significant difference? Why/why not?
    Array execution cost: 89314 gas
    Mapping execution cost: 67575 gas
    The array cosumes gas more than Mapping storage design.

  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?
    Array consumes gas more than mapping storage design because during update entity,it need to loop find the address that already exist and update data while mapping only check key:value.Use time on logic shorter than array.

Mapping

pragma solidity 0.8.0;

contract assignmentStorageDesignMapping {
    
    struct Entity {
        uint data;
        address _address;
        bool exist;
    }

    mapping(address => Entity) public existents;

    function addEntity(uint data, address entityAddress) public returns (bool success){
        require(existents[entityAddress].exist == false, "This address already exists");
        Entity memory newEntity = Entity(data, entityAddress, true);
        existents[entityAddress] = newEntity;
        return true;
    }
    
    function updateEntity(uint data, address entityAddress) public returns (bool success){
        require(existents[entityAddress].exist == true, "Not found address to update");
        existents[entityAddress].data = data;
        return true;
    }

}

Array

pragma solidity 0.8.0;

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

    Entity[] existents;
    
    function _hasAnEntity(address candidate) internal view returns(bool) {
        for (uint i=0; i< existents.length; i++) {
            if (existents[i]._address == candidate) return true;
        }
        return false;
    }

    function _getEntryIndex(address candidate) internal view returns(uint) {
        for (uint i=0; i<existents.length; i++) {
            if (existents[i]._address == candidate) return i;
        }
        revert ("Not found index on this address");
    }

    function getEntity() public view returns(Entity [] memory){
        return existents;
    }

    function addEntity(uint data, address entityAddress) public returns (bool success){
        require(!_hasAnEntity(entityAddress), "An address already exists!");
        Entity memory newEntity;
        newEntity.data = data;
        newEntity._address = entityAddress;
        existents.push(newEntity);
        return true;
    }
    
    function updateEntity(uint data, address entityAddress) public returns (bool success){
        require(_hasAnEntity(entityAddress), "Not found address to update!");
        uint index = _getEntryIndex(entityAddress);
        existents[index].data = data;
        return true;
    }
    
}
1 Like

Hello!
This was my solution for the assignment.

mapping_test.sol

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

contract mappingStorageList {

    struct Entity {
    uint data;
    address _address;
    }

    mapping (address => Entity) public entities;

    //Creates a new entity for msg.sender and adds it to entities map
    function addEntity(uint _data) public returns (bool success){
        require(entities[msg.sender]._address != msg.sender, "Entity already exists");
        entities[msg.sender]._address = msg.sender;
        entities[msg.sender].data = _data;
        return true;

    }

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

}

array_test.sol

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

contract arrayStorageList {

    struct Entity {
        uint data;
        address _address;
    }

    Entity[] public entityArray;

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

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


}
  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 consumed 88489 wei when the addEntity function ran and the mapping design used 66550 wei. The array design used about 25% more gas to execute.
  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?
  • The mapping solution still kept the gas cost down around the 20-25% mark. It looks like the loop required to make the array solution work is a less efficient option than the mapping solution overall. Key-value pairs seem to be the way to go in this specific scenario.
1 Like