lukeleeai
Published in

lukeleeai

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.

Fluree PBC

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 viewfunctions 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.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store