Solidity Error Handling Assignment


#1

Post your answers or questions below.


#2
pragma solidity 0.5.1;

contract DogContract2{

    struct Dog {
        string name;
        uint age;
    }

    mapping(address => Dog) ownerToDog;

    function addDog(string memory _name, uint _age) public {
        /* make sure there is not yet a dog assigned*/
        require(ownerToDog[msg.sender].age == 0, "There is already a dog");
        ownerToDog[msg.sender] = Dog(_name, _age);
        /* If the dog is accepted, make sure the right age is used*/
        assert(ownerToDog[msg.sender].age == _age);
    }

    function getDog() public view returns (string memory) {
        address owner = msg.sender;
        return ownerToDog[owner].name;
    }

}

#3

Good job @Capaburro, keep it up.


#4

This could be used to replace the “IF” statement. From what I have learned thus far, it would probably be best practice to do both a “REQUIRE” and an “ASSERT” (though I only use a require based on the quiz question).

function addDog(string memory _name, uint _age) public {
    require(ownerToDog[msg.sender].age == 0;
    
    ownerToDog[msg.sender] =  Dog(_name, _age);
    }

#5

@filip So I guessed correctly that “require” would do the job, but I am slightly confused on when to use and assert and when not to. My current understanding is that ‘require’ is for ‘input validation’ for the function parameters, and that ‘assert’ is more of an ‘output validation’ for the method’s return/output. If I need to validate parameters use ‘require’ and if I need to validate method outputs prior to returning use ‘assert’.

My confusion is WHEN to use ‘assert’, Is it completely discretionary or is there some kind of cost to using it in a function? For example, if I used assert in the addDog function would it make the function more expensive, clunky, less preferred over a duplicate function without assert, etc?

Cheers


#6

Yes you are basically correct. They could be used in other situations as well. But those would be the general cases.

As for the gas cost of having them in your functions, it’s a bit unclear as how much to operation actually costs. Require seems to very cheap at least. You could try the code and see if the gas spent changes.

So why should we use it if it costs us gas? Well, imo we shouldn’t think of the gas cost when using assert or require. They are necessities if you want a safe and secure contract. It’s the best way to make sure that your contract never ends up in a weird state or that any hackers could submit bad values to your contract. The gas cost shouldn’t be an issue when we talk about smart contract security.


#7

Very good point with regards to gas costs and security. I often fixate on efficiency, but I can’t let that get in the way of secure contracts - especially when that “efficiency gain” is negligible. Thanks Filip.


#8

Require statement should be used instead of assert so that the gas does not get wasted.


#9

I thought it also important to check that the user input was not 0

I don’t fully understand the working of the assert line in this case. I would have expected that if the address was already assigned a dog and then the user tries to input another dog with the same age, that it would be accepted as the ages would be ==

When trying this it seems that it does not overwrite the dog even with the same age but I cant see why.

pragma solidity 0.5.2;

contract DogContract {

struct Dog {
string name;
uint age;
}

mapping(address => Dog) ownerToDog;

function addDog(string memory _name, uint _age) public {
    require (ownerToDog[msg.sender].age == 0 && _age != 0);
    Dog memory currentDog = Dog(_name, _age);
    ownerToDog[msg.sender] = currentDog;
    assert(ownerToDog[msg.sender].age == _age);
}

function getDog() public view returns (string memory) {
    address owner = msg.sender;
    return ownerToDog[owner].name;
}

}


#10

Hi David,

If I understand your question correctly I think you have misunderstood what we check in the assert statement.

What we actually check is if the new dog age has actually been set. We compare the input towards the mapping we just set. If they are not identical, regardless of what input the user has inputed, then we have a very critical error.

Imagine I input a dog with age 10.

Then I set the mapping so that the age is 10.

Then I check if the mapping is 10 or not.

If it’s not 10. Then we have a huge problem somewhere.

This is what the assert statement checks.


#11

Thank you for clarifying :+1:


#12

My solution to the Error Handling.
It works but not quite sure is optimal.

pragma solidity ^0.5.1;

contract  DogContract{
    struct Dog{
        string name;
        uint age;
    }
 
    mapping(address => Dog ) ownerToDog;
    
    function addDog(string memory _name, uint _age) public {
        require(_age > 0); // to avoid user to input dogs with age 0, i.e puppies//
        require(ownerToDog[msg.sender].age==0); /* to make sure user inputs dog for first time */
        ownerToDog[msg.sender]=Dog(_name,_age);

    } 
    
    function getDogNameFromAddress() public view returns (string memory){
        address owner=msg.sender; 
        return ownerToDog[owner].name;
    }
}

#13

Very good! That works well. If you want to clean up the code you could write the same thing as

require(_age > 0 && ownerToDog[msg.sender].age==0)

&& means AND. So both conditions need to be true in order for it to pass.


#14
pragma solidity ^0.5.2;

contract DogCont {
struct Dog {
    string name;
    uint age;
}

mapping(address => Dog) ownerToDog;

function addDog(string memory _name, uint _age) public returns (uint){
    require(ownerToDog[msg.sender].age == 0); // the first element has to remain 
    Dog memory currentDog = Dog(_name, _age);
    ownerToDog[msg.sender] = currentDog; 
    assert(ownerToDog[msg.sender].age != 0); // a dog should be never 0 years old  
}

function getDog() /*getDog(uint _id)*/ public view returns (string memory){
    address owner = msg.sender;
    return ownerToDog[owner].name;
}

}


#15

Thanks! I forgot about &&.


#16

Thanks! I like the notification sent with the require function.


#17
pragma solidity 0.5.1;

contract DogContract{
    
    struct Dog{
        string name;
        uint age;
        
    }
    
    mapping (address => Dog) ownerToDog;
    
    function addDog (string memory _name, uint _age) public {
        
        require (ownerToDog[msg.sender].age == 0);
        Dog memory currentDog = Dog (_name, _age);
        ownerToDog[msg.sender] = currentDog;
        
        assert (ownerToDog[msg.sender].age == _age);
    }
    
    function getDogName ()public view returns (string memory){
        
        return ownerToDog[msg.sender].name;
    }
    
    
}

#18

Well i did this!


#19
pragma solidity 0.5.1;

contract DogContract{
   struct Dog{
       string name;
       uint age;
   }
   
   mapping(address => Dog) ownerToDog;
   
   function addDog(string memory _name, uint _age) public {
       require(ownerToDog[msg.sender].age == 0, "Dog contract taken");
       Dog memory currentDog = Dog(_name,_age);
       ownerToDog[msg.sender] = currentDog;
       

   }
   
   function getDog() public view returns (string memory) {
       address owner = msg.sender;
       return ownerToDog[owner].name;
   }
   
}

Question: Is there a way to check the name instead? It seems to make more sense to me if name is the one being checked. Thank you.


#20

pragma solidity ^0.5.2;

contract DogCont {
struct Dog {
string name;
uint age;
}

mapping(address => Dog) ownerToDog;

function addDog(string memory _name, uint _age) public returns (uint){
require(ownerToDog[msg.sender].age == 0); // to avoid user to input dogs with age 0, i.e puppies//
Dog memory currentDog = Dog(_name, _age);
ownerToDog[msg.sender] = currentDog;
assert(ownerToDog[msg.sender].age != 0); // a dog should be never 0 years old
}

function getDog() /getDog(uint _id)/ public view returns (string memory){
address owner = msg.sender;
return ownerToDog[owner].name;
}