Home

Ethereum Beginner Course Notes

Contracts

Solidity's code is encapsulated in contracts. A contract is the fundamental building block of Ethereum applications — all variables and functions belong to a contract, and this will be the starting point of all your projects.

An empty contract named HelloWorld would look like this:

pragma solidity >=0.5.0 <0.6.0; contract HelloWorld { }

State Variables & Ints

State variables are permanently stored in contract storage. This means they're written to the Ethereum blockchain. Think of them like writing to a DB.

contract Example { // This will be stored permanently in the blockchain uint myUnsignedInteger = 100; }

Structs

Sometimes you need a more complex data type. For this, Solidity provides structs:

struct Person { uint age; string name; }

Structs allow you to create more complicated data types that have multiple properties.

Arrays

When you want a collection of something, you can use an array. There are two types of arrays in Solidity: fixed arrays and dynamic arrays.

You can also create an array of structs. Using the previous chapter's Person struct:

// Array with a fixed length of 2 elements: uint[2] fixedArray; // another fixed Array, can contain 5 strings: string[5] stringArray; // a dynamic Array - has no fixed size, can keep growing: uint[] dynamicArray; Person[] people; // dynamic Array, we can keep adding to it

Public Arrays

You can declare an array as public, and Solidity will automatically create a getter method for it. The syntax looks like:

Person[] public people;

Other contracts would then be able to read from, but not write to, this array. So this is a useful pattern for storing public data in your contract.

pragma solidity >=0.5.0 <0.6.0; contract ZombieFactory { uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; struct Zombie { string name; uint dna; } Zombie[] public zombies; }

Function Declarations

function eatHamburgers(string memory _name, uint _amount) public { }

This is a function named eatHamburgers that takes 2 parameters: a string and a uint. For now the body of the function is empty. Note that we're specifying the function visibility as public. We're also providing instructions about where the _name variable should be stored in memory. This is required for all reference types such as arrays, structs, mappings, and strings.

Working with structs and arrays

This code outines how to add a new zombie to the factory:

pragma solidity >=0.5.0 <0.6.0; contract ZombieFactory { uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; struct Zombie { string name; uint dna; } Zombie[] public zombies; function createZombie(string memory _name, uint _dna) public { zombies.push(Zombie(_name, _dna)); } }

Private/Public Functions

In Solidity, functions are public by default. This means anyone (or any other contract) can call your contract's function and execute its code.

Obviously this isn't always desirable, and can make your contract vulnerable to attacks. Thus it's good practice to mark your functions as private by default, and then only make public the functions you want to expose to the world.

Let's look at how to declare a private function:

uint[] numbers; function _addToArray(uint _number) private { numbers.push(_number); }

This means only other functions within our contract will be able to call this function and add to the numbers array.

As you can see, we use the keyword private after the function name. And as with function parameters, it's convention to start private function names with an underscore (_).

Return values

An example:

string greeting = "What's up dog"; function sayHello() public returns (string memory) { return greeting; }

View functions

The example in the return function above doesn't change the state, so we can declare it as a view function:

string greeting = "What's up dog"; function sayHello() public view returns (string memory) { return greeting; }

Pure functions

Pure functions are functions that you are not even accessing in the app:

function _multiply(uint a, uint b) private pure returns (uint) { return a * b; }

Note: It may be hard to remember when to mark functions as pure/view. Luckily the Solidity compiler is good about issuing warnings to let you know when you should use one of these modifiers.

Keccak256 and Typecasting

Ethereum has the hash function keccak256 built in, which is a version of SHA3. A hash function basically maps an input into a random 256-bit hexadecimal number. A slight change in the input will cause a large change in the hash.

It's useful for many purposes in Ethereum, but for right now we're just going to use it for pseudo-random number generation.

Also important, keccak256 expects a single parameter of type bytes. This means that we have to "pack" any parameters before calling keccak256:

//6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5 keccak256(abi.encodePacked("aaaab")); //b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9 keccak256(abi.encodePacked("aaaac"));

Note: Secure random-number generation in blockchain is a very difficult problem. Our method here is insecure, but since security isn't top priority for our Zombie DNA, it will be good enough for our purposes.

Typecasting

An example of uint to uint8 typecasting:

uint8 a = 5; uint b = 6; // throws an error because a * b returns a uint, not uint8: uint8 c = a * b; // we have to typecast b as a uint8 to make it work: uint8 c = a * uint8(b);

An example of typecasting and Keccak256 working together:

pragma solidity ^0.4.25; contract ZombieFactory { uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; struct Zombie { string name; uint dna; } Zombie[] public zombies; function _createZombie(string memory _name, uint _dna) private { zombies.push(Zombie(_name, _dna)); } function _generateRandomDna(string memory _str) private view returns (uint) { uint rand = uint(keccak256(abi.encodePacked(_str))); return rand % dnaModulus; } }

Putting it all together

We are done with the random zombie generator, we just need to create the zombie with a public function:

pragma solidity >=0.5.0 <0.6.0; contract ZombieFactory { uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; struct Zombie { string name; uint dna; } Zombie[] public zombies; function _createZombie(string memory _name, uint _dna) private { zombies.push(Zombie(_name, _dna)); } function _generateRandomDna(string memory _str) private view returns (uint) { uint rand = uint(keccak256(abi.encodePacked(_str))); return rand % dnaModulus; } function createRandomZombie(string memory _name) public { uint randDna = _generateRandomDna(_name); _createZombie(_name, randDna); } }

Events

Events are a way for your contract to communicate that something happened on the blockchain to your app front-end, which can be 'listening' for certain events and take action when they happen.

// declare the event event IntegersAdded(uint x, uint y, uint result); function add(uint _x, uint _y) public returns (uint) { uint result = _x + _y; // fire an event to let the app know the function was called: emit IntegersAdded(_x, _y, result); return result; }

Your app front-end could then listen for the event. A javascript implementation would look something like:

YourContract.IntegersAdded(function (error, result) { // do something with result })

For our new Zombie, we emitted the data with the following:

pragma solidity >=0.5.0 <0.6.0; contract ZombieFactory { event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; struct Zombie { string name; uint dna; } Zombie[] public zombies; function _createZombie(string memory _name, uint _dna) private { uint id = zombies.push(Zombie(_name, _dna)) - 1; emit NewZombie(id, _name, _dna); } function _generateRandomDna(string memory _str) private view returns (uint) { uint rand = uint(keccak256(abi.encodePacked(_str))); return rand % dnaModulus; } function createRandomZombie(string memory _name) public { uint randDna = _generateRandomDna(_name); _createZombie(_name, randDna); } }

Web3.js

Our Solidity contract is complete! Now we need to write a javascript frontend that interacts with the contract.

Ethereum has a Javascript library called Web3.js.

In a later lesson, we'll go over in depth how to deploy a contract and set up Web3.js. But for now let's just look at some sample code for how Web3.js would interact with our deployed contract.

// Here's how we would access our contract: var abi = /* abi generated by the compiler */ var ZombieFactoryContract = web3.eth.contract(abi) var contractAddress = /* our contract address on Ethereum after deploying */ var ZombieFactory = ZombieFactoryContract.at(contractAddress) // `ZombieFactory` has access to our contract's public functions and events // some sort of event listener to take the text input: $("#ourButton").click(function(e) { var name = $("#nameInput").val() // Call our contract's `createRandomZombie` function: ZombieFactory.createRandomZombie(name) }) // Listen for the `NewZombie` event, and update the UI var event = ZombieFactory.NewZombie(function(error, result) { if (error) return generateZombie(result.zombieId, result.name, result.dna) }) // take the Zombie dna, and update our image function generateZombie(id, name, dna) { let dnaStr = String(dna) // pad DNA with leading zeroes if it's less than 16 characters while (dnaStr.length < 16) dnaStr = "0" + dnaStr let zombieDetails = { // first 2 digits make up the head. We have 7 possible heads, so % 7 // to get a number 0 - 6, then add 1 to make it 1 - 7. Then we have 7 // image files named "head1.png" through "head7.png" we load based on // this number: headChoice: dnaStr.substring(0, 2) % 7 + 1, // 2nd 2 digits make up the eyes, 11 variations: eyeChoice: dnaStr.substring(2, 4) % 11 + 1, // 6 variations of shirts: shirtChoice: dnaStr.substring(4, 6) % 6 + 1, // last 6 digits control color. Updated using CSS filter: hue-rotate // which has 360 degrees: skinColorChoice: parseInt(dnaStr.substring(6, 8) / 100 * 360), eyeColorChoice: parseInt(dnaStr.substring(8, 10) / 100 * 360), clothesColorChoice: parseInt(dnaStr.substring(10, 12) / 100 * 360), zombieName: name, zombieDescription: "A Level 1 CryptoZombie", } return zombieDetails }