Assignment - Storage Design

Hey @Hudson, I fixed my issues with using remix, I backed up all my files as you recommended.

Turns out, my problem was using an ‘https’ connection when I should’ve ‘http’ on my browser instead.

1 Like

When executing the addEntity function, which design consumes the most gas (execution cost)?
-The array design consumes the most gas in execution cost.
Is it a significant difference?
-It’s about a 50% increase in gas consumption to use an array over a mapping, so I’d say it’s pretty significant.
Why/why not?
-Arrays are larger data structures than mappings, even if they contain the same information arrays still have more meta-data that provides their special functionality. Mappings are very stripped down by comparison.

Add 5 Entities into storage using addEntity function and 5 different addresses, then update 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 solution. Again, arrays are more complex structures than mappings and have more functionality that needs to be processed. Also, depending on whether you want to include an index number as an input or not, the gas fee will either be the same for each entry in the array if we use an index input or it will get bigger with each entry if we have to use a loop to find the corresponding entry for msg.sender.

Here’s my code:

pragma solidity 0.7.5;
pragma abicoder v2;

contract StorageDesignMapping {

struct Entity {
    uint data;
    address _address;
}

mapping (address => Entity) public entityMapping;

function addEntity(uint _data) public {
    Entity memory newEntity = Entity(_data, msg.sender);
    entityMapping[msg.sender] = newEntity;
}
//execution cost = 41549
//transaction cost = 63013

function updateEntity(uint _data) public {
    entityMapping[msg.sender] = Entity(_data, msg.sender);
}
//execution cost = 7294
//transaction cost = 28758
}


contract StorageDesignArray {

struct Entity {
    uint data;
    address _address;
}

Entity[] public entities;

function addEntity(uint _data) public {
    Entity memory newEntity = Entity(_data, msg.sender);
    entities.push(newEntity);
}
//execution cost = 62385
//transaction cost = 83849

function updateEntity(uint _index, uint _data) public {
    entities[_index] = Entity(_data, msg.sender);
}
//execution cost = 8269
//transaction cost = 29861
}


Dude, brilliant deduction, well done.

This seems to be one of the ghosts that haunts Remix IDE. I was hoping that the Truffle setup videos would be earlier on in this 201 course, literally just to get away from editing in a browser. (Almost there now!)

Cheers friend :beers:

1 Like

I finally fixed my issue with deploying contracts in remix, and here’s my answer to the questions for this assignment:

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

  • Only array: transaction cost 87960 gas execution cost 66496 gas
  • Only mapping: transaction cost 63014 gas execution cost 41550 gas

From these results, using an Only array solution would cost 37% more gas (execution cost) than using an only mapping solution. 37% is very significant especially when handling larger amounts of 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?

  • Only Array: Updating the 5th address (execution cost) 8459 gas
  • Only mapping: Updating the 5th address (execution cost) 1517 gas
  • Adding entities in the only_array solution costs roughly around 30-37% gas for the execution cost as compared to the only_mapping solution. The percentage is in a range due to the difference in costs when adding an entity in an only_array contract.
2 Likes
pragma solidity 0.8.1;

//create a data storage contract using mapping.  Will be looking at 
//gas consumption using this method.

contract DataStorageUsingMapping
{
    struct Entity{
        uint data;
        address _address;
        
    }
    
    mapping(address => Entity) public entity;
    
    function addEntity(uint _data) public {
        //Entity memory temp = Entity(_data, msg.sender);
        //entity[msg.sender] = temp;
        entity[msg.sender].data = _data;
        entity[msg.sender]._address = msg.sender;
    }
    
    function updateEntity(uint _data) public {
        entity[msg.sender].data = _data;
    }
}
pragma solidity 0.8.1;

//create a data storage contract using an array.  Will be looking at 
//gas consumption using this method.

contract DataStorageUsingArray
{
     struct Entity{
        uint data;
        address _address;
        
    }
    
    Entity[] public entity;
    
    function addEntity(uint _data) public {
        Entity memory temp = Entity(_data, msg.sender);
        entity.push(temp);
    }
    
    function updateEntity(uint _data) public {
        
        uint index;
        
        //iterate through the array to find the index of the array to update
        for(uint i = 0; i < entity.length; i++)
        {
            if(msg.sender == entity[i]._address)
                index = i;
        }
        
        entity[index].data = _data;
    }
}
  1. When executing the addEntity function, which design consumes the most gas (execution cost)?
    The array method consumes the most gas.
  2. Is it a significant difference? Why/why not?
    The difference is significant because an array is being created (62,299 vs 41,454).
  3. 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 method consumes more gas. It requires more gas to create an array than to map an address to a struct. Also updating a variable in an array requires more gas because it has to iterate through the array using a for loop (20,963 vs 5,515).
1 Like

Hello everyone,

The code for both contracts is at the bottom of this post.

Execution cost measurements:

Contract Function Execution cost (gas)
Array addEntity Average ~57661 (64157, 51909, 54661, 57413, 60165)
Array updateEntity (5th entry) 20828
Mapping addEntity 42443
Mapping updateEntity (5th entry) 6503

In the case of both Array and Mapping, addEntity is expensive compared to updateEntity because it involves creating a new element in storage, whilst updateEntity only modifies existing data in storage.

updateEntity is 3.2 times cheaper with the mapping than with the array because the mapping optimises random access. In the case of the array it is necessary to iterate over each element to find the desired entry.

Matt

pragma solidity 0.8.0;


contract MagicArray{

    struct Entity{
            uint data;
            address _address;
    }
    

    Entity[] entities; 
    
    function addEntity(uint data) public returns (uint length){
    
        
        for(uint i = 0; i < entities.length; ++i) 
        {
            require(entities[i]._address != msg.sender, "entity already exists");

        }

        entities.push(Entity(data, msg.sender));    
        
        return entities.length;
    }
    
    function updateEntity(uint data) public {

        uint i;
        for(i = 0; i < entities.length && entities[i]._address != msg.sender ; ++i) 
        {
        }
        
        require(i < entities.length, "Entity does not exist");
        
        entities[i].data = data; 
    }
    
    function getData() public view returns (uint data, uint index){
        uint i;
        for(i = 0; i < entities.length && entities[i]._address != msg.sender ; ++i) 
        {
        }
        
        require(i < entities.length, "Entity does not exist");
        
        return (entities[i].data, i); 
        
    }
    
}
pragma solidity 0.8.0;


contract MagicMapping{

    struct Entity{
            uint data;
            bool exists;
    }
    
    mapping( address => Entity) entities;
    
    function addEntity(uint data) public {
        
        require(entities[msg.sender].exists == false, "This entity already exists");
        
        entities[msg.sender].data = data;
        entities[msg.sender].exists = true;
    }
    
    function updateEntity(uint data) public {
        require(entities[msg.sender].exists == true, "This entity does not exist");
        
        entities[msg.sender].data = data;        
    }
    
    function getData() public view returns (uint data){
        
       require(entities[msg.sender].exists == true, "This entity does not exist");
        
       return entities[msg.sender].data;        
         
    }
    
}
1 Like

StorageMapping.sol

pragma solidity 0.7.5;
contract Storage1 {
    
struct Entity{
    uint data;
    address _address;
}

mapping (address => Entity) entities;

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

StorageArray.sol

pragma solidity 0.7.5;
contract Storage2 {
    
struct Entity{
    uint data;
    address _address;
}

    Entity[] public entities; 
    
    
    function addEntity(uint entityData) public returns (bool success){
        Entity memory newEntity;
        newEntity.data = entityData;
        newEntity._address = msg.sender;
        entities.push(newEntity);
        return true;
    }
    
    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;

    }
}

Obviously the array solution consumes more gas. The difference is even significant on the update operation, as on the array we need to iterate the whole array until we find the entry we want to update. On contrast, on the mapping solution, we can use the address as a key to find the entry we want, so we do need to iterate through it.

1 Like

Here’s the code (results below the code):

pragma solidity 0.7.5;
pragma abicoder v2;

contract mappingStorageDesign {
    
    struct Entity {
        uint _data;
        address _address;
    }
    
    mapping(address => Entity) entities;
    
    function addEntity (uint _data) public {
        Entity memory newEntity;
        newEntity._data = _data;
        newEntity._address = msg.sender;
        entities[msg.sender] = newEntity;
    }
    
    function updateEntity (uint _data) public {
        entities[msg.sender]._data = _data;
    }
    
    //Simple getter to check if the add/update function works
    function getEntity () public view returns (Entity memory){
        return entities[msg.sender];
    }
    
}

contract arrayStorageDesign {
    
        struct Entity {
            uint _data;
            address _address;
        }
    
    Entity[] entities;
    
    function addEntity(uint _data) public {
        Entity memory newEntity;
        newEntity._data = _data;
        newEntity._address = msg.sender;
        entities.push(newEntity);
    }
    
    function updateEntity(uint _data) public {
        for (uint i=0; i < entities.length; i++) {
            if (entities[i]._address == msg.sender) {
                entities[i]._data = _data;
            }
        }
    }
    
    //Simple getter to check if the add/update function works
    function getEntity (uint _index) public view returns (Entity memory) {
        return entities[_index];
    }
}

Results:

addEntity():
Mapping:
transaction cost	63014 gas
execution cost	41550 gas

Array:
transaction cost	68850 gas
execution cost	47386 gas

No significant difference. It costs about the same to add data to a mapping as to an array. Same magnitude of complexity.

updateEntity():
Mapping:
transaction cost	26979 gas
execution cost	5515 gas

Array:
transaction cost	41791 gas
execution cost	20327 gas

The array solution consumes more gas since we must iterate the array to update the entity (linear complexity), while with the mapping we have direct access to data (constant complexity).

1 Like
pragma solidity 0.8.3;


contract StorageAssignmentMapping {

    struct Entity{
        uint data;
        address _address;
    }
    
    mapping (address => Entity) dataStorage;
    
    function addEntity(uint _data) public {
        dataStorage[msg.sender].data = _data;
        dataStorage[msg.sender]._address = msg.sender;
    }
    
    function updateEntity(uint _data) public {
        dataStorage[msg.sender].data = _data;
    }
    
    function readEntity() public view returns (uint, address) {
        return (dataStorage[msg.sender].data, dataStorage[msg.sender]._address);
    }

}



contract StorageAssignmentArray {

    struct Entity{
        uint data;
        address _address;
    }
    
    Entity[] dataStorage;

    function addEntity(uint _data) public {
        dataStorage.push(Entity(_data, msg.sender));
    }
    
    function updateEntity(uint _id, uint _data) public {
        dataStorage[_id].data = _data;
    }
    
    function readEntity() public view returns (Entity[] memory) {
        return (dataStorage);
    }

}
Gas Costs mapping array % more expensive
Add 41476 62308 50.23%
1 additional 41476 47308 14.06%
1 additional 41476 47308 14.06%
1 additional 41476 47308 14.06%
1 additional 41476 47308 14.06%
1 additional 41476 47308 14.06%
modify 5th 5537 6512 17.61%

Looks like Array is all round more expensive than Mapping for this use case as items need to be stored to ‘storage’ in order to be recalled, where as mapping storage requirements are not as costly.
Initial initializing of the array or mapping are more expensive operations than subsequent modifications to the same mapping or array index.

1 Like
pragma solidity 0.8.0;

contract StorageDeisgn1{
    
    struct Entity{
      uint data;
      address _address;
    }
    
    mapping (address => Entity) entityList;
    
    function addEntity(uint _data) public {
        entityList[msg.sender] = Entity(_data, msg.sender);
    } 
    
    function updateEntity(uint _data) public {
        entityList[msg.sender].data = _data;
    }
}
pragma solidity 0.8.0;

contract StorageDeisgn2{
    
    struct Entity{
      uint data;
      address _address;
    }
    
    Entity[] entityList;
    
    function addEntity(uint _data) public{
        entityList.push(Entity(_data, msg.sender));
    } 
    
    function updateEntity(uint index, uint _data) public{
        entityList[index].data = _data;
    }
}
  1. The contract that contains the array consumes the most gas. The difference is quite significant because when using an array you store more data in storage than using a mapping thus costing more in the process.

2.The solution with the array cost more gas because of the needed variable of index to change its data in the struct this adds more memory space to be used which results in more gas consumption

1 Like

Hello!

Here are my contracts for this assignment:

This is for the mapping:

pragma solidity 0.8.0;

contract EntitiyMapping {
    
    struct Entity {
        uint data;
        address _address;
    }
    
    mapping(uint => Entity) public entityStructs;
    
    function addEntity(uint addressId, address newAddress, uint data) public {
       entityStructs[addressId]._address = newAddress;
       entityStructs[addressId].data = data;
       
    }
    function updateEntity(uint addressId, address newAddress, uint data) public returns (address, uint) {
        entityStructs[addressId]._address = newAddress;
        entityStructs[addressId].data = data;
        return (newAddress, data);
    }
}

And this is for the Array one:

pragma solidity 0.8.0;
pragma abicoder v2;
contract EntityArray {
    
    struct Entity {
        uint data;
        address _address;
    }
    
    Entity[] public entityStructs;
    
    function addEntity(uint data, address _address) public returns (Entity[] memory){
       Entity memory newEntity;
       newEntity._address = _address;
       newEntity.data = data;
       entityStructs.push(newEntity);
       return entityStructs;
    }
    function updateEntity(address _address) public returns (Entity[] memory){
        Entity storage updateEntity = entityStructs[entityStructs.length -1];
        updateEntity._address = _address;
        return entityStructs;

    }
}

And here is the list of execution costs for both contracts:

For Mapping:

Add Entity 1: 41869
Add Entity 2: 41869
Add Entity 3: 41869
Add Entity 4: 41869
Add Entity 5: 41869
Update cost: 8006

And for the Array:

Add Entity 1: 66313
Add Entity 2: 53659
Add Entity 3: 56006
Add Entity 4: 58352
Add Entity 5: 60699
Update Entity cost: 21277

This means that the array createEntity cost is averagely 1.5x more than the mapping one. I am not sure about the why, but is it because that array elements are stored in storage while mapping is not?
Execution cost is also higher for the array one for me, which is around 2.65x more. I only have the idea of explaining this on the same matter as arrays are stored in storage while mappings are not but if there is better explanation, please reply and I will learn it!

1 Like

Mapping

pragma solidity 0.8.3;

contract Contract{
    struct Entity{
        uint data;
        address adr;
    }
    
    mapping(address => Entity) entities;
    
    function addEntity(uint _data) public {
        if(entities[msg.sender].adr == msg.sender) revert();
        entities[msg.sender].data = _data;
        entities[msg.sender].adr = msg.sender;
    }
    
    function updateEntity(uint _data) public {
        if(entities[msg.sender].adr != msg.sender) revert();
        entities[msg.sender].data = _data;
    }
    
    function getEntityData() public view returns(uint) {
        if(entities[msg.sender].adr != msg.sender) revert();
        return entities[msg.sender].data;
    }
}

addEntity: 42422 gas
updateEntity: 6502 gas

Array

pragma solidity 0.8.3;

contract Contract{
    struct Entity{
        uint data;
        address adr;
    }
    
    Entity[] entities;
    
    function addEntity(uint _data) public {
        uint i=0;
        for(;i<entities.length;i++){
            if(entities[i].adr == msg.sender) break;
        }
        require(i==entities.length);
        Entity memory newEntity;
        newEntity.data = _data;
        newEntity.adr = msg.sender;
        entities.push(newEntity);
    }
    
    function updateEntity(uint _data) public {
        uint i=0;
        for(;i<entities.length;i++){
            if(entities[i].adr == msg.sender) break;
        }
        require(i<entities.length);
        entities[i].data = _data;
    }
    
    function getEntityData() public view returns(uint) {
        uint i=0;
        for(;i<entities.length;i++){
            if(entities[i].adr == msg.sender) break;
        }
        require(i<entities.length);
        return entities[i].data;
    }
}

addEntity: 64057 gas --> 51814 gas --> 54571gas --> 57328 gas --> 60085 gas
updateEntity: 20818 gas

Discussion

The execution cost for addEntity is not significantly different for mapping and array. However, array consumes more gas because it has to loop through all the array to check for duplicates. Next, for the updateEntity, the execution cost is quite significant. The reason is the same for the previous function, where array has to loop though all the array first. We can also see that updateEntity is cheaper than addEntity. This is because adding an element to an array or a mapping uses more nodes' storage.
1 Like

Array contract:

pragma solidity 0.8.0;

contract StorageDesignArray {
    
    struct Entity{
        uint data;
        address _address;
    }
    
    Entity[] public entityArray;
    
    function addEntity(uint _data) public returns(bool done){
        Entity memory newEntity;
        newEntity._address = msg.sender;
        newEntity.data = _data;
        entityArray.push(newEntity);
        return true;
    }
    
    function updateEntity(uint _id, uint _data) public returns(bool done){
        entityArray[_id].data = _data;
        return true;
    }
    
}

Mapping contract:

pragma solidity 0.8.0;

contract StorageDesignMapping {
    
    struct Entity{
        uint data;
        address _address;
    }
    
    mapping(address => Entity) public entityMapping;
    
    function addEntity(uint _data) public returns(bool done){
        Entity memory newEntity;
        newEntity._address = msg.sender;
        newEntity.data = _data;
        entityMapping[msg.sender] = newEntity;
        return true;
    }
    
    function updateEntity(address _inputAddress, uint _data) public returns(bool done){
        entityMapping[_inputAddress].data = _data;
        return true;
    }
    
    
    
}

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

When executing addEntity on the contract which is using the mapping then it consumes 41771 gas whereas the contract using arrays consumes 62585 gas which is a whole lot more. The difference is a lot…
This is because of how Solidity is storing arrays and mappings. How much processing it requires to do it.

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 solution consumes more gas. I believe that the array solution is consuming more gas because it requires a lot more to go through an array than an mapping. The difference is that a mapping requires less effort than an array to process.
I am not exactly sure about the detail really but it has to be something in how an array and mapping are indexed in Solidity.

2 Likes

Mapping only:

pragma solidity 0.7.5;

contract StorageMapping{

    struct Entity{
        address entityAddress;
        uint data;
    }
    
    mapping (address => Entity) public entities;
    
    function addEntity(uint _data) public{
        require(entities[msg.sender].entityAddress == 0x0000000000000000000000000000000000000000, "Already registered");
        entities[msg.sender] = Entity({data:_data, entityAddress:msg.sender});
    }
    
    function updateEntity(uint _data) public {
        require(entities[msg.sender].entityAddress == msg.sender, "Not found in registry");
        
        entities[msg.sender].data = _data;
    
    }
}

Array only:

pragma solidity 0.7.5;

contract StorageArray{

    struct Entity{
        address entityAddress;
        uint data;
    }
    
    Entity[] public entities;
    
    function addEntity(uint _data) public{
        for(uint i = 0; i<entities.length; i++){
            require(msg.sender != entities[i].entityAddress, "Already registered");
        }
        entities.push(Entity({data:_data, entityAddress:msg.sender}));
    }
    
    function updateEntity(uint _data) public {
        for(uint i = 0; i<entities.length; i++){
            if (entities[i].entityAddress == msg.sender){
                entities[i].data = _data;
                break;
            }
        }        
    }
}

Mapping:
addEntity execution cost:

  1. 42260
  2. 42260
  3. 42260
  4. 42260
  5. 42260

updateEntity cost (5th address):
6324

Array:
addEntity execution cost:

  1. 63126
  2. 50760
  3. 53394
  4. 56028
  5. 58662

updateEntity cost (5th address):
19495

Array design consumes more gas when executing addEntity function as well as updateEntity function. With array we need to iterate the whole array until we find the entity we want to update. As the array grows the execution cost should be higher.

addEntity cost for mapping stayed consistent. With mapping we can use the address as a key to find the entity. There is no iteration involved.

Code:

Mapping

pragma solidity 0.8.0;

contract EntityMapping {
	
		struct Entity {
		    uint data;
		    bool isEntity;
		}

		mapping (address => Entity) entities;

		function addEntity(uint _data) public {
			Entity memory newEntity;
			newEntity.data = _data;
			newEntity.isEntity = true;
			entities[msg.sender] = newEntity;

		}

		function updateEntity(uint _data) public {
			require(entities[msg.sender].isEntity == true);
			entities[msg.sender].data = _data;
		}

}

Array:

pragma solidity 0.8.0;

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

		Entity[] entities;

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

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

The Gas costs for each were as such:

Mapping:
addEntity:
execution cost 41551 gas

updateEntity (1st):
execution cost 6481 gas

updateEntity (6th):
execution cost 6481 gas

Array:
addEntity:
execution cost 62386 gas

updateEntity (1st):
execution cost 8934 gas

updateEntity (6th)
execution cost 22704 gas

The costs for adding and updating using arrays is more expensive because the underlying operations cost more gas to execute. The difference in cost for adding the 6th element in the array is due to the executing the loop calculation to find the msg.sender address, which is not necessary for the mapping.

In conclusion arrays requiring loops (or algorithms) to search should be avoided for large data sets if possible as this would consume quite a bit of unnecessary gas.

1 Like

solution with an array:

pragma solidity 0.8.0;

// Define the contract
contract Arraycontract{
    
    // Define the struct.
    struct Entity{
        uint data;
        address _address;
    }
    
    // Define entity array
    Entity[] entityStructs;
    
    // Make function to add entity's
    function addEntity(address _address, uint data ) public returns(bool success) {
        // Declare empty entity.
        Entity memory newEntity;
        // set address 
        newEntity._address  = _address;
        // set balance of address
        newEntity.data      = data;
        // push all the data to the struct
        entityStructs.push  (newEntity);
        return true;
    }
    
    // Make function to update entity's
    function updateEntity(uint _index, uint data) public returns(bool succes){
        // Assign the provided data
        entityStructs[_index].data   = data;
        return true;
    }
}

solution with a mapping:

pragma solidity 0.8.0;

// Define contract 
contract simpleMapping{
    
    // Define struct
    struct Entity{
    uint data;
    address _address;
    }
    
    // Define mapping entities   
    mapping (address => Entity) entityStruct;
    
    // Create function to add data of an address
    function addEntity (uint data) public returns(bool succes) {
        // Declaire empty entity
        Entity memory newEntity;
        // Set balance of address
        newEntity.data = data;
        // Set address (sender is address)
        newEntity._address = msg.sender;
        // Create the struct.
        entityStruct[msg.sender] = newEntity;
        return true;
    }
    
    // Create function to update an entity
    function updatEntity (address _address, uint data) public returns(bool succes){
        // Assign approved data.
        entityStruct[_address].data = data;
        return true;
    }

}

1. When executing the addEntity function, which design consumes the most gas (execution cost)?
Array: 62.830.
Mapping: 41.749.

2. Is it a significant difference? Why/why not?
This is quite a big difference with the mapping operation you save 21.081 on gas costs. you save about 30% per action you wanna do.

3. Add 5 Entities into storage using the addEntity function and 5 different addresses. update data and do on both contracts. Wich solution consumes more gas and why?
With the array solution it costs in total: 239.150 to add 5 different addresses to the storage.
And it costs: 6.670 gas to update an array.

With the mapping solution it costs in total: 208.745 gas to add 5 different addresses to the storage this is a difference of 30.405 in gas costs this is a significant difference.
And to update an array with the mapping solution it costs: 5.940 gas to update the array wich is a difference of: 730 in gas.

The array solution spents more gas because it has more process to perform. Because it has to loop through the whole array to find an address and then to update it. While with a mapping you can call a certain address and update it almost directly.

1 Like

Hi, this is my solution:

pragma solidity 0.8.0;  

contract assignmentMapping {
    
    struct Entity{
        uint data;
        address _address;
    }
    
    mapping(address => Entity) public entities;
    
    function addEntity(uint _data) public {
        // require(entities[msg.sender]._address == address(0), "Entity already exists");
        entities[msg.sender].data = _data;
        entities[msg.sender]._address = msg.sender;
    }
    
    function updateEntity(uint _data) public {
        // require(entities[msg.sender]._address != address(0), "Entity does not exist");
        entities[msg.sender].data = _data;
    }
}

contract assignmentArray {
    
    struct Entity{
        uint data;
        address _address;
    }
    
    Entity[] public entities;
    
    // mapping(address => bool) public isEntity;
    
    function addEntity(uint _data) public {
        // require(isEntity[msg.sender] == false, "Entity alrady exists");
        entities.push(Entity(_data, msg.sender));
        // isEntity[msg.sender] = true;
    }
    
    function updateEntity(uint _data) public {
        // require(isEntity[msg.sender] == true, "Entity does not exist");
        for(uint i=0;i<entities.length;i++) {
            if (entities[i]._address == msg.sender) {
                entities[i].data = _data;
                break;
            }
        }
    }
    
}
  1. Mapping addEntity() execution cost = 41476
    Array addEntity() execution cost = 62286, every following addEntity() = 47286

In this case the difference is overall small. We can see that first add into array costs 62286 (which is quite big cost difference), but every following add costs only 47286, which is cost similar to mapping (41476). The interesting question though is why array addEntity costs more on the first entry?

  1. Mapping updateEntity() cost = 5537
    Array updateEntity() cost = 19987

Array design costs much more (almost 4x), because it has to loop through all the array until it finds the entry with given address. On contrary mapping finds the specific value for address right away. However we can optimise looping through array with break statement, which stops after the address is found and can save us some cost, but it is still more expensive than mapping.

1 Like

My code for the assignment.
I did include an isEntity function since I would imagine one would want to check if the entity exists so you don’t get duplicate entries or try to update a non-existant entity.

pragma solidity 0.7.5;

contract storageDesignMapping{
    
    struct Entity{
        uint data;
        address _address;
        bool isEntity;
    }

    mapping (address=> Entity) entityList;
    
    function isEntity(address _entityAddress) private view returns (bool success){
        return entityList[_entityAddress].isEntity;
    }
    
    //addEntity(). Creates a new entity for msg.sender and adds it to the mapping/array.
    function addEntity(uint _entityData) public returns (bool success){
        require (!isEntity(msg.sender),"Duplicate: this entity exists already.");
        entityList[msg.sender].data=_entityData;
        entityList[msg.sender]._address=msg.sender;
        entityList[msg.sender].isEntity=true;
        return true;
    }
    
    //updateEntity(). Updates the data in a saved entity for msg.sender
    function updateEntity(uint _entityData) public returns (bool success){
        require (isEntity(msg.sender),"Not found: this entity does not exist.");
        entityList[msg.sender].data= _entityData;
        return true;
    }
}
pragma solidity 0.7.5;

contract storageDesignArray{
    
    struct Entity{
        uint data;
        address _address;
    }
    
    Entity[] private entityList;
    
    function isEntity(address _entityAddress) private view returns (bool success){
        if (entityList.length==0) return false;
        for(uint i = 0; i<entityList.length ; i++){
            if (entityList[i]._address== _entityAddress){
                return true;
            }
        }
        return false;
    }
    
    //addEntity(). Creates a new entity for msg.sender and adds it to the mapping/array.
    function addEntity(uint _entityData) public returns(bool success){
        require (!isEntity(msg.sender),"Duplicate: this entity exists already.");
        Entity memory newEntity;
        newEntity.data = _entityData;
        newEntity._address=msg.sender;
        entityList.push(newEntity);
        return true;
    }
    //updateEntity(). Updates the data in a saved entity for msg.sender
    function updateEntity(uint _entityData) public returns(bool success){
        require (isEntity(msg.sender), "Not found: this entity does not exist.");
        for (uint i=0; i<entityList.length; i++){
            if (entityList[i]._address==msg.sender){
                entityList[i].data=_entityData;
                return true;
            }
        }
    }
}

When executing the addEntity function, which design consumes the most gas (execution cost)? Is it a significant difference? Why/why not? Add 5 Entities into storage using the addEntity function and 5 different addresses
mapping execution costs 44210 gas every time
array execution costs 63190 , 51655, 54290, 56925, 59560

Array costs way more from the start. I take it that the first entry in the array costs more since it has to create an actual array. The next actions start out lower since the array now exists.
With array it keeps going up since I added an isEntity function that loops through the array. I consider this function as quite elemental since you wouldn’t want duplicate entries into any database/array. The cost will keep increasing since it takes more actions to go through an ever expanding array.

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?
I’ve also used the isEntity function for the update so it wouldn’t be possible to update a non-existant entity.
mapping update costs 6456
array update of the first element in tthe array costs 8173, update of the fifth element costs 29249

It has to loop through the entire array in isEntity and then another loop to updateEntity so the further down the array the more actions will have to be performed. and the cost will go up.

Arrays cost much more since they always have to loop through. Not very advisable for large collections of data.

1 Like

I didn’t get consistent results. In the end it looked like adding new entity to mapping was most affordable. Updating didn’t have huge differences and it was not consistent.

pragma solidity 0.8.1;

contract Mapping{
    
    struct Entity{
        uint data;
        address _address;
    }
    
    mapping(address => Entity) store;
    
    function addEntity() public{  // 2788 gas
        store[msg.sender] = Entity(0, msg.sender);
    }
    
    function updateEntity(uint _data) public{ // 20515
        store[msg.sender].data = _data;
        
    }
}

contract Array{
    
    struct Entity{
        uint data;
        address _address;
    }
    
    Entity[] store;
    
    function addEntity() public{ // 27824
        store.push(Entity(0, msg.sender));
    }
    
    function updateEntity(uint _data) public{ // 19965
        for(uint i = 0; i < store.length; i++) {
            if(store[i]._address == msg.sender){
                store[i].data = _data;
                break;
            }
        }
    }
}
1 Like

I tried two different array implementations, one that checks for existence and one that doesn’t.

In both cases it’s cheaper to add and update with a mapping than an array. I’m not sure why pushing to an array is more expensive than inserting into a mapping, but the update is more expensive for arrays because it requires a linear lookup rather than a constant time map lookup.

EntityArray (checks for existence in addEntity):

addEntity
1: tx cost: 84843, exec cost: 63379
2: tx cost: 73501, exec cost: 52037
3: tx cost: 77159, exec cost: 55695
4: tx cost: 80817, exec cost: 59353
5: tx cost: 84475, exec cost: 63011

Increases by 3658 each time.

updateEntity

5: tx cost: 40813, exec cost: 19349

EntityArray2 (doesn’t check for existence in addEntity):

addEntity
1: tx cost: 83962, exec cost: 62498
2: tx cost: 68962, exec cost: 47498
3: tx cost: 68962, exec cost: 47498
4: tx cost: 68962, exec cost: 47498
5: tx cost: 68962, exec cost: 47498

updateEntity

5: tx cost: 40813, exec cost: 19349

EntityMapping

addEntity
1: tx cost: 63113, exec cost: 41649
2: tx cost: 63113, exec cost: 41649
3: tx cost: 63113, exec cost: 41649
4: tx cost: 63113, exec cost: 41649
5: tx cost: 63113, exec cost: 41649

updateEntity

5: tx cost: 27181, exec cost: 5717

pragma solidity 0.8.0;

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

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

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