CryptoZombies (2) — Advanced Solidity Concepts
Immutability of Contacts
The essential nature of Ethereum’s Smart Contracts is immutability. Once the contract is deployed, it can never be modified again. This is an intended feature, not a bug. It is a security measure to ensure that no one can tamper with the original contract, not even the creator himself. When you think of it, it’s blockchain after all. You can’t change the block.

What if we were using an external contract but somehow it had a bug and crashed? The hard-coded address to the external contract can’t be essentially changed. You can’t just change your code. So, you define a new function to set the address: setKittyContractAddress
.
Then, it leaves me with a question: who can set the address? How can use the function? Can only the creator do this? Or do all of the nodes approve this?
Ownable Contracts
One common practice that has emerged is to make contracts Ownable
… Cool.
msg.sender
is the person who deployed the contract.
Here’s a code for the ownable contract.
constructor() internal {
_owner = msg.sender;
emit OwnershipTransferred(address(0), _owner);
}modifier onlyOwner() {
require(isOwner());
_;
}function isOwner() public view returns (bool) {
return msg.sender == _owner;
}
constructor
is only executed when the contract is first deployed. So, basically, the first one to deploy the contract can be an owner.
Modifier
modifier
can be used to decorate other functions and in this exercise, we use it to make sure that only the owner can use a particular function. You can’t use it directly as a function, but attach it at the end of a function definition to change that function’s behaviour. When you execute it, the modifier runs first, and when it hits _;
it returns back to the original function.
Modifiers can also take in arguments like the following:
contract ZombieFactory is Ownable {}contract ZombieFeeding is ZombieFactory {
function setKittyContractAddress(address _address) external onlyOwner (16, msg.sender) {}
}
Gas
Ethereum is a big, slow, secure network of nodes. Each transaction should be verified by all of the nodes there. When you execute a function, every other node should verify its output by running it. But what if the function has an infinite loop or heavy workload? The Ethereum creators thus introduced the idea of gas to prevent such cases. The bigger and more your trucks cross the road, the more you should pay for it.
This brings attention to the necessity to optimise your code as much as possible unless you want your users to pay extra fees due to the code’s poor design.
Cluster the same types in a struct
There is one way to reduce the gas when declaring variables. In a struct, you can cluster variables of the same types together to do so and allocate the minimum storage for each variable.
struct Normal {
uint64 a;
uint b;
uint64 c;
}struct Mini {
uint 32 a;
uint 32 b;
uint c;
}
In this case, Mini
pays less gas than Normal
. It only works inside a struct.
Use memory instead of storage
There is another way to save gas. Avoid using storage
unless it’s necessary. Writing to storage
means saving value on the blockchain forever. It requires all the nodes to store the value, and the size would increase every transaction.
So, use memory
instead. Although the code should build an array every time the function is called, it’s way cheaper than storing a value inside storage
forever.
Use external view functions when accessing values.
view
functions don’t change anything on the blockchain. So, there’s no need for nodes to verify its computation. However, it isn’t the case for private
or internal
view
functions as other functions in the same contract create a transaction on Ethereum, which needs to be verified by nodes.
Returning all the zombies the owner has.
There are two options to achieve this.
The first one is to use storage
, which makes more sense in conventional programming languages.
mapping (address => uint[]) public ownerToZombies;function getZombiesByOwner(address _owner) external view returns (uint[] memory) {
return ownerToZombies[_owner];
}
But it has some problems in Solidity. owernerToZombies
is actually stored in storage
. This means that every time we write to the mapping, the users should pay gas. And let’s say that we traded out one of our zombies. If the number of zombies we had is N, removing the first zombie requires shifting all the next elements to the left, which means we write N-1 times.
The gas-wisely cheaper way to achieve this is just to use for
loop.
mapping (uint => address) public zombieToOwner;
mapping (address => uint) public ownerZombieCount;function getZombiesByOwner(address _owner) external view returns (uint[] memory) {
uint[] memory result = new uint[](ownerZombieCount[_owner]);
uint counter = 0;for (uint i = 0; i < zombies.length; i++) {
if (zombieToOwner[i] == _owner) {
result[counter] = i;
counter++;
}
}return ownerToZombies[_owner];
}
Yes, although this way also uses storage
, we actually write to it fewer times, saving our users more gas.