# Integer types; createPerson function alternatives; Gas; Optimization; View v Pure functions; Non-iterable nature of mappings (by @jon_m)

Hi guys
This topic is there to record the really interesting questions from @jon_m. And to keep them from getting lost in the solidity basics topics, as it can help a lot of us, I guess. I will therefore move his questions on this topic.

4 Likes

A post was merged into an existing topic: Solidity Basics

@filip

Iāve really enjoyed this first section of the Solidity course - great material and great explanations!

Having finished this section on Solidity basics, Iām left with the following questions:

Question 1

I was also wondering about this. In the video Iām sure you say thatā`uint`ā(unsigned integer) can be used with both positive and negative integers. However, the following Wikipedia page suggests that unsigned only refers to positive numbers and zero.

This makes sense to me as we prefix a minus sign to represent a negative number value, but we donāt need to prefix a positive sign to represent a positive number value e.g.
``````uint positiveNumber = 5;

uint negativeNumber = -5;    // This threw an error when I tried it
``````

Do you maybe mean that we can store as positive numbers what in reality are negative numbers, and then, as @marsrvr seems to suggest with decimal places, convert them into negative numbers with JavaScript in a front-end?

Question 2

In lesson 4 (Structs video) you showed us two alternatives for theā`createPerson`āfunction body:

``````struct Person {
uint id;
string name;
uint age;
uint height;
}

Person[] public people;

// Alternative 1
function createPerson(string memory name, uint age, uint height) public {
people.push(Person(people.length, name, age, height));
}

// Alternative 2
function createPerson(string memory name, uint age, uint height) public {
Person memory newPerson;
newPerson.id = people.length;
newPerson.name = name;
newPerson.age = age;
newPerson.height = height;
people.push(newPerson);
}
``````

In the following videos you used Alternative 2 as the basis for developing the code examples for mappings andā`if...else`ā control flow. I also attempted to write the equivalent using Alternative 1 - could you tell me if my alternative in the following code (2nd line in the function body) is correct?

``````struct Person {
string name;
uint age;
uint height;
}

function createPerson(string memory name, uint age, uint height) public {
people[creator] = Person(name, age, height);   // my alternative
}
``````

Question 3

In JavaScript, the following would be valid alternatives to theā`if...else`ācontrol flow demonstrated in lesson 8 (If & Else - Control Flow video). They also worked for me in Solidity, and I was wondering if you could confirm whether they are indeed valid alternatives:

``````// If & Else control flow demonstrated in the video
if (age >= 65) {
newPerson.senior = true;
}
else {
newPerson.senior = false;
}

// Alternative A - If & Else control flow without the curly brackets
if (age >= 65) newPerson.senior = true;
else newPerson.senior = false;

// Alternative B - using a ternary operator
age >= 65 ? newPerson.senior = true : newPerson.senior = false;

// Alternative C
newPerson.senior = age >= 65;
``````

As Solidity seems to have a lot of similarities with JavaScript, and because Remix lets you know if your code is correct or contains errors, Iāve found that by using a trial-and-error approach I seem to be able to work out whether certain code variations Iāve tried are valid or not (for example my control flow alternatives above). Is this a good way to experiment with Solidity if you already have a good grounding in JavaScript? It does avoid having to look everything up in documentation. Or is this trial-and-error approach (until the compiler gives you a green tick) dangerous to rely on, for reasons such as getting into bad practice? Should I always be using documentation instead? As there does seem to be a lot of Solidity syntax which is exactly the same as JavaScript, is there any kind of reliable summary available anywhere, which gives you a good quick reference as to what is the same and what is different? I would have thought a reference of this kind would certainly speed up learning Solidity for people who already know a lot of JavaScriptā¦

Question 4

In the quiz on mappings:

ā 3.āIs it possible to find all values entered into a mapping in Solidity?ā āAnswer:āNo

I putāYesābecause I thought that if it was a mapping which wasnāt based on user addresses, then if all the mappingās key values were known and the getter function allowed the user to input these key values one at a time, then essentially you could retrieve all of the values mapped to those key values ā not altogether, but one by one. Is that a valid interpretation of the question?

Is the answerāNoābecause the question refers to the type of mapping that we have in our example, where any user executing the contract would only be able to retrieve the value in the mapping associated with their own address? Or does it refer to the fact that only individual values can be retrieved each time the getter function is called, and never all values altogether? If not, what is the reasoning behind the correct answer?

4 Likes

Hi @jon_m

Q1:

Playing with cast is not a good solution (it s a bit dangerous ) and solidity allow you to use regular integer, in this example you can see that you can use negative number. You can also see the effect when you are not respecting the type limit (i used int8 and uint8 as the limit is more human friendly )

``````pragma solidity >=0.4.22 <0.7.0;

contract Test {

uint8 test1 = 0;
int8 test2 = 0;

// Uint can be 0 to 255 otherwhise we overflow or underflow
function getUintLimit() public view returns(uint8, uint8){
// The minimum value is zero if we overlap we go back to the max number
uint8 underFlow = test1 - 1;
// The maximum value is 255 if we overlap we go back to the min number
uint8 overflow = (uint8)(test1 + 256); // I have to cast here because the compiler detect an overflow
return (underFlow, overflow);
}

// Limit -127 and 127
function getIntLimit() public view returns(int8, int8){
int8 LimitDownOk = test2 - 127; // OK
//int8 underFlow = (int8)(test2 - 128); // throw an error
int8 LimitUpOk = test2 + 127; // OK
//int8 overFlow = (int8)(test2 + 128); // throw an error
return (LimitDownOk, LimitUpOk);
}

}
``````

Q2:
Your alternative is correct, but keep in mind that an array and a map are not used for the same thing.
If i asked you how many peoples are stored in your contract, or what is the age of āBobā your alternative will not works. But to access a Person information your alternative is faster.

Q3:
The problem is clarity you can use all of them but if someone else is trying to read you code the first is the best, i use ternary often for simple functions. The second and the fourth are really not human readable IMO
Using solidity compiler is faster, but the documentation always have better explanation. I donāt know a good website which compare solidity and javascript syntax if you find one please share it with us on a new topic

Q4:
You can have a look at my explanation in this post:

This question is a bit tricky it depends a lot on your interpretation, i think the point here is, is it possible to iter on each elements of a mapping.

1 Like

Hey! Thank you so much for your detailed response

Iāve had a really good look and think about everything youāve explained and outlined, and Iād just like to confirm a few things and ask a few more questionsā¦

Q1

Thanks for introducing the concept of cast. I didnāt know what that was, but Iāve looked it up and now I do

So, basically, we are saying that
(i) `uint` only allows zero and positive integers up to a max based on the binary places included in the typeā e.g.ā `uint4` (0 to 15) , ā`uint8` (0 to 255) , ā`uint16` (0 to 65535)
(ii) `int` allows positive and negative integers from a min of minus(-) half the max based on the binary places included in the type, to a max of plus(+) half the max based on the binary places, and including zero (as the median of this range).
e.g.ā `int4` (-7 to 7) , ā`int8` (-127 to 127) , ā`int16` (-32767 to 32767)
Is that correct?

I understand the concept of underflow and overflow that you have demonstrated in your example code. Are you trying to demonstrate that this happens with `uint` but not with `int` (hence the errors thrown with `int` when trying to store integers above/below the max/min)? I have proved to myself that the underflow does indeed happen with `uint` and the following line of code stores the max (255 using `uint8`):

However, I donāt really understand the following line of code from your example, and why you refer to this as casting. If `test1` is already defined in a state variable as type `unit8`, and our new variable `overflow` is also defined as type `unit8`, why do we need to add the additional `(unit8)` to the assigned expression, and why the parentheses? Also, even with this additional `(unit8)`, my Remix compiler still throws an error hereā¦

Why does the compiler perform the underflow with `uint` (returning to the max) but not the overflow (i.e. why doesnāt it return to the min)?

Q2

I take your point about the differences between arrays and mappings, but @filip had already provided two alternatives for the array approach in the Structs video:

My alternative was purely meant as an alternative to @filipās code in the Mappings video, where he only provides one version, which is based on Alternative 2 for the array in the Structs video. Despite the differences you mention between using arrays and mappings, I think you are confirming that my alternative for the mapping is correct, arenāt you? Below, Iāve included @filipās version next to mine, which will hopefully make it clearer to see what I was hoping to achieveā¦

``````struct Person {
string name;
uint age;
uint height;
}

// Filip's version (based on the previous Alternative 2)
function createPerson(string memory name, uint age, uint height) public {

Person memory newPerson;
newPerson.name = name;
newPerson.age = age;
newPerson.height = height;

people[creator] = newPerson;
}

// My alternative (based on the previous Alternative 1)
function createPerson(string memory name, uint age, uint height) public {
people[creator] = Person(name, age, height);
}
``````

Q3

Thanks for confirming my alternatives are correct. I take your point about clarity, but I guess that is something purely subjective, and people can have different opinions about what is clearer and what isnāt. Would there be differences in the cost of gas between the alternatives, making this a possible factor in deciding which to use?

Will do!

Q4ā

Hi @jon_m

Q1:
Yeah itās exact you get it
Regarding my example i just rush a bit righting it. You can have overflow or underflow in any type, none are safe. I shouldnāt had use an other variable you can try this code it ll bug in both case:

``````    // Uint can be 0 to 255 otherwhise we overflow or underflow
function getUintLimit() public view returns(uint8, uint8){
// The minimum value is zero if we overlap we go back to the max number
uint8 underFlow = 0;
underFlow -= 1;
// The maximum value is 255 if we overlap we go back to the min number
uint8 overflow = 0;
overflow = (uint8)(overflow + 256); // I have to cast here because the compiler detect an overflow
return (underFlow, overflow);
}

// Limit -127 and 127
function getIntLimit() public view returns(int8, int8){
int8 LimitDownOk = 0;
int16 testU = 128;
int16 testD = 129;
LimitDownOk  = (int8)(LimitDownOk - testD);

int8 LimitUpOk = 0;
LimitUpOk  = (int8)(LimitUpOk + testU);

return (LimitDownOk, LimitUpOk);
}
``````

I had to cast or use a variable of an other type, because the compiler is detecting that 256 couldnāt be an uint8 same for a number up to 128 it canāt be an int8. So itās just a trick to be able to show a bad pratice

Q2:
For this use case the result is the same, actually faster with a mapping.Your alternative works and itās more compact.

Q3:
It could be, actually the gas cost is base on how many opcodes are used. For an if else statement itās gonna be the same opcodes because this is a simple operation i guess.
But you can try to compile the same contract with a different statement and look at the opcode generated.
If you click on the Debug button in remix you can follow the cost of each operation in gas.

Iāve finally worked this throughā¦

Q1

Having experimented some more with this, I can now see that sometimes the compiler blocks an underflow or overflow by throwing an error, but that by doing some casting or adding additional variables of a different type, we can āforceā the underflow and overflow to occur with bothā`uint`āandā`int` ,āand that this isā¦

In other words, we should always keep within the restrictions in terms of the range of integers available with a given integer type (signed or unsigned), because the compiler will not always prevent an overflow (a loop from the max limit, back to the min limit) or an underflow (a loop from the min limit back to the max limit).

Is that correct?

By the way, theā`view`āin your example functions throws the following warning in my Remix compiler:

`Warning: Function state mutability can be restricted to pure`

The functions still work when deployed withā`view`ā(ignoring the warnings) but the warnings disappear when I changeā`view`āforā`pure` .āWhat exactly isā`pure`āand why does the compiler recommend it be used instead ofā`view` ?

Also, having experimented a bit, Iāve also found that the following statements in your code:

``````overflow = (uint8)(overflow + 256);
LimitDownOk = (int8)(LimitDownOk - testD);
LimitUpOk = (int8)(LimitUpOk + testU);
``````

ā¦can be reduced toā¦

``````overflow += uint8(256);
LimitDownOk -= int8(testD);
LimitUpOk += int8(testU);
``````

What do you think?

Iāve also spotted that, withā`int`ātypes, the negative integer limit is always 1 more than the positive integer limit, to compensate for the fact that zero is included as the median of the full rangeāi.e.

ā`int4` (-8 to 7) ,ā`int8` (-128 to 127) ,ā`int16` (-32768 to 32767)

ā¦and notā¦

Q2 resolved

Q3

Thanks ā Iāve been able to do this in the Debugger area, and Iāve found some differences in the gas cost between the alternative control flow statements weāve considered.
When I total the individual gas costs for all the operations related to each alternative control flow statement, the differences between these totals are also equal to the differences between the total execution costs shown in the transaction details.

``````// Ternary operator (highest gas cost = 80)
// total execution cost of tx = 63895 gas
age >= 65 ? newPerson.senior = true : newPerson.senior = false;

// if...else statement (gas cost = 67)
// total execution cost of tx = 63882 gas
// -13 gas difference
if (age >= 65) {
newPerson.senior = true;
}
else {
newPerson.senior = false;
}

// Boolean expression assigned directly (lowest gas cost = 52)
// total execution cost of tx = 63867 gas
// -28 gas difference
newPerson.senior = age >= 65;
``````

Yes the compiler will not prevent your code to underflow or overflow, if you take the ethereum security course you ll learn a lot about it.

A view function doesnāt allow you to modify the storage state but you can still read the storage. A pure function doesnāt allow you to modify and read the storage, only local variable will be used.
Strongly typing your function helps to avoid making mistake.

Yes this is better

``````overflow += uint8(256);
LimitDownOk -= int8(testD);
LimitUpOk += int8(testU);
``````

But for an example i try to keep it simple to show the point

`int4 (-8 to 7) , int8 (-128 to 127) , int16 (-32768 to 32767)`

Correct

``````
// Ternary operator (highest gas cost = 80)
// total execution cost of tx = 63895 gas

// if...else statement (gas cost = 67)
// total execution cost of tx = 63882 gas
// -13 gas difference

// Boolean expression assigned directly (lowest gas cost = 52)
// total execution cost of tx = 63867 gas
// -28 gas difference
``````

Woo really nice, iām really surprise that ternary cost more gas thank to run this tests.
Btw did you check the ` Enable optimization` box before compiling ?

Thank a lot for all your observations @jon_m you did a lot of great research.
Btw if you have time (i know you already spend a lot of time writing this post) can you create a new topic on the forum, and share with us all your observations. I m sure it ll help a lot of people and this topic is more about the basic, i think you got far further than the basic

1 Like

Copied from our private conversation where we had continued this discussion:

My tests were without this enabled. Iāve now re-run them with it enabled, and Iāll post my findings in the new topic when itās set up. What exactly does Enable optimization do? What does it optimize? I couldnāt find anything about this in the documentationā¦

Iām still not sure of all the terminology:
Does storage state refer to values stored in state variables ā variables defined at the beginning of the contract and outside of the functions?
Are local variables ones that are defined within functions (like in the overflow/underflow test functions we have used)?
If the answer is yes in both cases, is the compiler suggesting we change `view` to `pure` because our test functions only need to read variables that are defined and modified within these same functions?

What do you mean by strongly typing ? Does it mean making a function `pure` instead of just `view` when only local variables (and not state variables) need to be accessed?

yeah i ll try to do that today

The optimization will make the compiler able to find duplicated op code or/and remove useless code, their is a good example here.

Yes as you can see here:

``````    int8 test2 = 0;

function getIntLimit() public pure returns(int8, int8){
int8 LimitDownOk = 0;
LimitDownOk -= 127; // Pure

int8 notPure = test2 + 127;

return (LimitDownOk, notPure);
}
``````

TypeError: Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires āviewā.
int8 notPure = test2 + 127;
^ā^

Yes.
I mean it will helps you to avoid human mistake for example you are designing your contractās function and you create a function which is just applying a mathematical operation and not modifying the state of your contract.

• This function shouldnāt modify or read the state, when an other developer (or yourself) add code to this function if they are not following your function declaration a warning or an error will be throw.

It will help other developers to know what is the real purpose of this function for example:

``````    //function isNbrOdd(uint8 isOdd) public view returns(bool){
function isNbrOdd(uint8 isOdd) public pure returns(bool){
if (isOdd % 2 == 0)
return true;
return false;
}
``````

Is pure but you can also compile it as a view function (with a warning).

But if someone try to read the state in this function it ll throw an error:

``````   bool test = true;
function isNbrOdd(uint8 isOdd) public pure returns(bool){
if (isOdd % 2 == 0)
return test;
return false;
}
``````

You will only be able to compile it as a view function.

When you have a 200 lines of code function to review if you had use the right type when declaring it this kind of issue will not happened.
But this function can be declare as a view function if you decide that in a future implementation it ll be possible to read the state from it (and ignore the warning).

1 Like

Results with optimization ON

There is no change in the order from highest ā lowest gas cost. In fact the difference between the costs is actually greater; so, relatively speaking, the ternary operator has an even higher gas cost than the other two control flow options:

Ternary operator:
Still the highest gas cost = 75 (only 5 less than with optimization OFF)
`age >= 65 ? newPerson.senior = true : newPerson.senior = false;`

ifā¦else statement
Gas cost = 54 (13 less than with optimization OFF)
-21 gas difference compared to ternary operator (additional -8 than with optimization OFF)

``````if (age >= 65) {
newPerson.senior = true;
}
else {
newPerson.senior = false;
}
``````

Boolean expression assigned directly:
Still the lowest gas cost = 39 (also 13 less than with optimization OFF)
-36 gas difference compared to ternary operator (also an additional -8 than with optimization OFF)
`newPerson.senior = age >= 65;`

3 Likes