Storage Design Patterns

Here you can ask questions related to this specific course section.

3 Likes

Am I understanding this right? Mapping addresses => struct is not ideal because a struct is initialized for every address call that happens so we can’t tell if it’s a real entry or just an entry with default values. Is that just the nature of the data structure? Why wouldn’t the code just read an error?

1 Like

Hey @wilsonlee123, hope you are ok.

Is not about if its ideal or not to use a mapping from address to struct, is completely valid.
Is just to keep in mind that some variables will be initialized by default with a value, booleans for example are always start by default at “false”.

So in case of mappings, is just to show you some few points to take into consideration, all mapping from address will start with default values, until you change the state of the values.

If you have any more questions, please let us know so we can help you! :slight_smile:

Carlos Z.

4 Likes

Thanks, Carlos!

To dig into this a bit more, why does that happen? Is it just how the data structure is?

Hey @wilsonlee123

this is common to all programming languages that asks for a variable type.
Basically only javascript does not initialise variables by default.

Cheers,
Dani

2 Likes
  function isEntity(address entityAddress) public view returns(bool isIndeed) {
    if(entityList.length == 0) return false;
    return (entityList[entityStructs[entityAddress].listPointer] == entityAddress);
  }

  function newEntity(address entityAddress, uint entityData) public returns(bool success) {
    if(isEntity(entityAddress)) revert();
    entityStructs[entityAddress].entityData = entityData;
    entityList.push(entityAddress);
    entityStructs[entityAddress].listPointer = entityList.length - 1;
    return true;
  }

I am a bit confused with some of Filips code here.

In the isEntity function, does the following code checks the index of the array or does it check to see how many total entries are in the array? if(entityList.length == 0) return false;

if the answer is the latter than the following code in newEntity function entityStructs[entityAddress].listPointer = entityList.length - 1; this sets the listPointer to match the index correct? Essentially the first entry in the entityList would be 1?.

Hey @RCV

In the isEntity function, does the following code checks the index of the array or does it check to see how many total entries are in the array? if(entityList.length == 0) return false;

This line if(entityList.length == 0) return false; is checking the length of the entityList array.
If the length is 0, it means that the array is empty so there is no need to check further and the function can immediately return false.

entityStructs[entityAddress].listPointer = entityList.length - 1; this sets the listPointer to match the index correct? Essentially the first entry in the entityList would be 1?.

This sets the pointer to the index of the array that contains the user address.

Example with a newly deployed contract:

entityStructs[entityAddress].listPointer = entityList.length - 1;

entityList.length = 1;
1 = 1 = 0

0 is the index.

Cheers,
Dani

Hi there.
In the contract of simple-mapping the code of filip is this way:

pragma solidity 0.8.0;
contract mappingWithStruct {

  struct EntityStruct {
    uint entityData;
    bool isEntity;
  }

  mapping (address => EntityStruct) public entityStructs;

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

  function deleteEntity(address entityAddress) public returns(bool success) {
    if(!isEntity(entityAddress)) revert();
    entityStructs[entityAddress].isEntity = false;
    return true;
  }

  function updateEntity(address entityAddress, uint entityData) public returns(bool success) {
    if(!isEntity(entityAddress)) revert();
    entityStructs[entityAddress].entityData = entityData;
    return true;
  }
}
© 2021 GitHub, Inc.```


Why do we need the isEntity function? Im a bit confused
 also the function deleteEntity does not delete data, just put the flag to false.
Am I missing something out?

I have rewrite the code on my point of view, what do u think about it?

pragma solidity 0.8.0;

contract mappingWithStruct {

  struct EntityStruct {
    uint entityData;
    bool isEntity;
  }

  mapping (address => EntityStruct) public entityStructs;

  function newEntity(address entityAddress, uint entityData) public returns(bool success) {
    //if(isEntity(entityAddress)) revert(); 
    require(entityStructs[entityAddress].isEntity == false);
    entityStructs[entityAddress].entityData = entityData;
    entityStructs[entityAddress].isEntity = true;
    return true;
  }

  function deleteEntity(address entityAddress) public returns(bool success) {
    //if(!isEntity(entityAddress)) revert();
    require(entityStructs[entityAddress].isEntity == true);
    entityStructs[entityAddress].isEntity = false;
    return true;
  }

  function updateEntity(address entityAddress, uint entityData) public returns(bool success) {
    //if(!isEntity(entityAddress)) revert();
    require(entityStructs[entityAddress].isEntity == true);
    entityStructs[entityAddress].entityData = entityData;
    return true;
  }
}

My code does the same as Filips.

1 Like

Hey @zero0_cero

isEntity() checks if there is data in the struct EntityStruct for the given address.
deleteEntity just set isEntity to false so that when you call isEntity() you will get false and your functions will revert.

Regards,
Dani

1 Like

Ok so Im trying to get a better grasp on this assignment so I wrote this code based on the example philip used and i included a few comments on some of the concepts I dont understand. If anyone could take the time togive some advice I would really appreciate it

pragma solidity 0.8.0;
contract deletingFromArray{
    
    struct Rstruct{
        uint data;
        uint listpointer;
    }
    mapping (address => Rstruct) public Rstructs;
    address[] public Rlist;
    
    
    function isR(address Raddress) public view returns (bool isIndeed) {     
        if (Rlist.length==0) return false;
        **return (Rlist[Rstructs[Raddress].listpointer]==Raddress);**             // I understand that we reach into the data types inside the struct
    }                                                                         // but how was that set to the local variable Raddress?
    
    function getRCount() public view returns**(uint so_this_can_b_anything)**{    //Why can this be named anything? Is this calldata?
        return Rlist.length;
    }
    
    function newR(address Raddress, uint data) public returns (bool succes){  //Error check? I guesse because bools are cheper than require?
        if (isR(Raddress)) revert ();                                         //So we call the function we declared earlier here
        Rstructs [Raddress].data= data;
        Rlist.push(Raddress);
        Rstructs[Raddress].listpointer=Rlist.length -1;                      
        return true;
    }
    
    function updateR (address Raddress, uint data) public returns(bool succes){
        if (!isR(Raddress)) revert();
        Rstructs[Raddress].data=data;
        return true;
    }
    
    function deleteEntity(address Raddress) public returns (bool succes){
        if(!isR(Raddress)) revert();
        uint row2delete= Rstructs[Raddress].listpointer;
        address toMove= Rlist[Rlist.length-1];
        Rlist[row2delete]=toMove;
        Rstructs[toMove].listpointer=row2delete;
        Rlist.pop;
        return true;
    }     
}
1 Like

Hey everyone,

I pretty much understood everything in the “Final Solution” part except for this code

delete entityStructs[entityAddress]; 

It’s not part of the removal of the address in the array, right? But does it set that address’ data to its Initial values?

If the address was deleted in the first place, why do we have to set its data to its Initial values? Or what is the reason behind doing this? I couldn’t quite get it.

Thanks a lot, everyone!

Hey, @dan-i thank you for this knowledge. Does this mean that if data is set to its initial values (bool is false) it would not take space in storage? therefore no gas fees?

Also, I cannot deploy the contract it says

creation of mappedWithUnorderedIndexAndDelete errored: Cannot convert undefined or null to object

Thank you for the clarification.

Hi @CryptoXyz

Using the delete method just sets the variable / string to its initial value:

A uint will be set to 0;
A boolean to false;
An address to 0x0 etc


Deploy this contract I wrote for you and check:

Call:

  • set()
  • get()
  • delete()
  • get()

you will see the results yourself.

pragma solidity 0.8.0;

contract test {
    
    struct testStruct {
        uint a;
        bool b;
        address c;
    }
    
    mapping (address => testStruct) public mappingOfTests;
    
    function set () public {
        mappingOfTests[msg.sender] = testStruct (10,true,msg.sender);
    }
    
    function get () public view returns (testStruct memory) {
        return mappingOfTests[msg.sender];
    }
    
    function deleteData () public {
        delete mappingOfTests[msg.sender];
    }
}

1 Like

Hey @dan-i,

Thank you so much for the effort of creating this contract to further answer my questions.

Hoping for your success!

Can you explain what the first line of code that incudes revert() does:

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

If the function isEntity(entityAddress) returns true the transaction is reverted.

1 Like

Of course. Calling the function isEntity. Thank you.

1 Like

MODIFIED DELETE FUNCTION

Hey guys so i modified Filips delete function in the final mapping storage solution program to make it a little easier to read (its not technically better its just shortened) and it also consumes less gas. I just used his function which was actually similar to my deleteUser function in my multistig wallet sol.

//FILIPS SOLUTION
//this function allows us to delete entities.
  function deleteEntity(address entityAddress) public returns(bool success) {
    if(!isEntity(entityAddress)) revert();
    uint rowToDelete = entityStructs[entityAddress].listPointer; // = 1
    address keyToMove   = entityList[entityList.length-1]; //save address4
    entityList[rowToDelete] = keyToMove;
    entityStructs[keyToMove].listPointer = rowToDelete; //= 2
    entityList.pop();
    delete entityStructs[entityAddress];
    return true;
  }
  
  //UPDATED SOL
  function removeEntity(address _entityAddress) public {
        //require that the entity we wish to delet exists
        if (!isEntity(_entityAddress)) revert();
        uint entity_ID = entityStructs[_entityAddress].listPointer;  //assign ID for deletion to entity instance ID
        entityList[entity_ID] = entityList[entityList.length - 1];
        
        entityList.pop();
        delete entityStructs[_entityAddress];
        
    }

the solution is very much the same in terms of style but i think its a little easier to follow. So instead of making a ‘rowToDelete’ and ‘KeyToMove’ variables i just create an entity ID which is assigmed to the ID of our entity instance (remember that this is already a prediefined attribute when we create an entity instance).

The to reiterate what Filip says, we cannot delete internal elements in a list we can only ‘pop’ off the last index. So we assign our entity_ID to the last index of the array but here its done in one line and is easier to read i think.

Lastly we do not need the last operation in filips code given by this line

entityStructs[keyToMove].listPointer = rowToDelete; //= 2

we can just simply pop straight away because we know that the Entity address that we want to delete is now stored in the last index. An finally just delete the struct as normal. From testing this shortened version seems to work just fine.

This solution is not technically better as the approach is the same but it seems to have a reduction in gas cost because there is less operations but the the percentage difference is only around 20% from the few tests i did. its just easier to read in my opinion for anyone who was struggling to follow Filips sol.

1 Like

ARRAY STORAGE WITH DELETE FUNCTIONALITY

I was playing around with the simplearray solution from the first video to see if it could be modified to handle deletion. I havent went deep into testing or refining this solution i probably should write a few other functions to help me test but here it is anyway. It is deletion by index so may be this is not desirable but i wanted to play around with the array storage method some more

There is a lot of comments they are for my own sake the delete function is at the bottom

pragma solidity 0.8.0;

contract simpleList {

    //define simple struct
  struct EntityStruct {
    address entityAddress;
    uint entityData;
  }

    //create array to store data of struct instances (Log Array)
  EntityStruct[] entityStructs;
  mapping (address => bool) knownEntity;
  
  //function to check for known entityStructs
  function isEntity(address _entityAddress) public view returns(bool) {
      
      return knownEntity[_entityAddress];
  }

  //create function to create struct instance and push it to the struct log array
  function newEntity(address entityAddress, uint entityData) public returns(EntityStruct memory) {
    //defines a new instance of our entity struct
    if(isEntity(entityAddress)) revert();
    
    EntityStruct memory newEntity;
    
    //defines the attributes of the struct
    newEntity.entityAddress = entityAddress;
    newEntity.entityData    = entityData;
    
    //pushes this new entity instance to our Struct log array
    entityStructs.push(newEntity);
    knownEntity[entityAddress] = true;
    
    
    //returns the new entity (-1 because array indexing starts at 0)
    return entityStructs[entityStructs.length - 1];
  }
  
  //updates entity data
  function updateEnti(uint _id, address _entityAddress, uint _entitydata) public {
      
      if(!isEntity(_entityAddress)) revert();
      if(entityStructs[_id].entityAddress != _entityAddress) revert();
      
     entityStructs[_id].entityData = _entitydata;
      
      
  }
  
  //getter function that returs a certain id that the user inputs

  //function that returns the size of our struct log array
  function getEntityCount() public view returns(uint entityCount) {
    return entityStructs.length;
  }
  
  //function to return our entoty struct log
  function getEntityLog() public view returns (EntityStruct[] memory) {
      
      return entityStructs;
  }
  
  //function to delete entty instance
  function removeEntity(uint _id) public
    {
        //reuire that the entity log array is greater or equal than 1
        require(entityStructs.length >= 1);
        
         //incase we want to remove a user that is not at the end of the simpleList
         //take the id of that entity and push it to the end of the list
         entityStructs[_id] = entityStructs[entityStructs.length - 1];
         address entityAddress = entityStructs[_id].entityAddress;
         knownEntity[entityAddress] = false;
         //then pop that user
         entityStructs.pop();
        //owners;
    }
}

Filip & Co. I am hoping you can clarify on a question from the Simple Array Mapping lesson.
This is the 2nd lesson in the Storage Design Patterns.

In this lesson you explained the simple_array.sol file has some drawbacks one of those being that you cannot input your address to easily retrieve the entity you want as it does not contain a mapping. Instead you would have to create a loop function to iterate through all the addresses to find the entity and values you are looking for. You did however show us that we can return an ID associated with each new entity that is returned to u (return entityStructs[entityStructs.length - 1]; Since we are getting a unique ID back isn’t it very easy to take that ID and get the address and other values in our entity?

Correct me if I am wrong but you would create a getFunction or something that would take this ID that has been returned to us, input it into our function and it will return the struct with the address and other info? So we don’t even need to set up a loop just as long as we have the ID because the ID returned to us can be put into our function to pull up the address? Is that correct or am I am I missing something?