Table of contents
I must say that I thoroughly enjoyed my first lesson. I'm going to take this time to review and go over what I have learned with you. However, if you haven't finished lesson 1 please do so before reading this post. There are too many spoilers below that might negatively impact your learning journey.
Key Terms/Syntax
Before we start, let's look at some essential vocabulary.
Here's an explanation of each of the key Solidity terms from the lesson:
Contract: A contract in Solidity is a fundamental building block of Ethereum smart contracts. It represents a collection of data (state variables) and functions that can interact with the Ethereum blockchain. Contracts define the behavior and rules of decentralized applications (DApps) and are written in the Solidity programming language.
Function: A function in Solidity is a block of code that performs a specific task or set of tasks. Functions can be called externally (from outside the contract) or internally (from within the contract). They can read and modify the contract's state, and they can also return values.
Variable: Variables in Solidity are used to store data. They can hold different types of data such as integers, strings, addresses, and more. Variables are used to maintain the state of a contract and to store values that can be used in computations and logic.
Struct: A struct (short for structure) in Solidity is a custom data type that allows you to define a composite data structure consisting of multiple fields with different data types. Structs are useful for creating complex data structures that represent real-world objects or entities.
Array: An array in Solidity is a data structure that can hold multiple elements of the same data type. Arrays can be dynamic (length can change during execution) or fixed-size. They are often used to store collections of data, such as a list of addresses or a sequence of numbers.
Compile: Compilation is the process of converting human-readable Solidity code into machine-readable bytecode that can be executed on the Ethereum Virtual Machine (EVM). Solidity code is compiled using the Solidity compiler. The resulting bytecode is deployed to the blockchain, and users can interact with the compiled smart contract by sending transactions.
These terms are foundational to understanding and working with Solidity and smart contracts on the Ethereum blockchain. As we continue to learn and develop in the blockchain space, we'll encounter these concepts frequently and see how they come together to create functional and secure decentralized applications.
Code Breakdown
Okay, now we are ready to rock! Let's break down the Solidity code step by step to understand its structure and functionality. The code below defines a smart contract named ZombieFactory
, which is used to create and manage zombie entities on the Ethereum blockchain.
pragma solidity >=0.5.0 <0.6.0;
contract ZombieFactory {
The
pragma
statement specifies the version of the Solidity compiler that should be used to compile the code. In this case, it allows versions greater than or equal to0.5.0
but less than0.6.0
.The
contract ZombieFactory { ... }
block is the main body of the smart contract. It contains the contract's state variables, functions, and other definitions.
event NewZombie(uint zombieId, string name, uint dna);
The
event
declaration defines an event namedNewZombie
. Events are used to log information about important occurrences within the smart contract. This event logs the creation of a new zombie and includes thezombieId
,name
, anddna
attributes.You'll see that this event will be triggered a bit later in our code.
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
These lines declare two
uint
variables,dnaDigits
anddnaModulus
. These variables are used for generating random DNA values for zombies.dnaDigits
specifies the length of the DNA sequence, anddnaModulus
is a divisor used to keep the DNA within a specific range. The value is calculated as10
raised to the power ofdnaDigits
(which is16
).In Solidity, the double-asterisk (
**
) is used as an exponentiation operator. It raises a number to a specified power. For example: (2**2 = 4), or (3**3 = 27).
struct Zombie {
string name;
uint dna;
}
This defines a
struct
namedZombie
to represent a zombie entity. It contains two attributes:name
(a string) anddna
(an unsigned integer representing the zombie's genetic code).These attributes will be used throughout the contract in the functions to build our zombies. To me, it seems similar to raw materials in a factory.
Zombie[] public zombies;
This declares a public array of
Zombie
structs namedzombies
. Public arrays in Solidity automatically create a getter function, allowing external code to access the array's elements.I consider this to be similar to a list. In this case, new zombies are added to our list (array) when created.
It's important to note that this is located outside of a function to work properly within the contract.
function _createZombie(string memory _name, uint _dna) private {
uint id = zombies.push(Zombie(_name, _dna)) - 1;
emit NewZombie(id, _name, _dna);
}
This is a private function
_createZombie
that takes a_name
(a string) and_dna
(an unsigned integer) as parameters. It creates a newZombie
struct using the provided attributes and pushes it onto thezombies
array. It also emits theNewZombie
event to log the creation of the new zombie.A private function in Solidity is a function that can only be accessed and called from within the same contract where it's defined. It's not accessible from external contracts or transactions.
The attribute 'memory' is used to temporarily store data. When you pass a parameter with the
memory
keyword, you're indicating that the function will read and manipulate the data in temporary memory without modifying the data on the blockchain itself.So basically this function creates a zombie and places it in our array.
In the second of the code above, we create a variable 'uint id' to be used in the next line as a parameter in emit NewZombie(). The -1 tells the computer to store it at the end of the list.
When you want to record a specific occurrence or action in your contract, you use the
emit
keyword followed by the name of the event and the required arguments. Emitting an event is a way to announce that a certain action has taken place within the contract.
function _generateRandomDna(string memory _str) private view returns (uint) {
uint rand = uint(keccak256(abi.encodePacked(_str)));
return rand % dnaModulus;
}
This is another private function
_generateRandomDna
that takes a_str
(a string) as input. It generates a random number based on the provided string using thekeccak256
hash function. The result is then modulo-ed bydnaModulus
to ensure the generated DNA value falls within the desired range.In simple terms, you can think of keccak256 as a magic blender for data. Let's break it down:
Input Data: Imagine you have any kind of data, like a message, a number, a document, or even a picture. Keccak256 takes this data as input.
Blender: Think of Keccak256 as a super-powerful blender. You put your data into the blender, and it processes the data in a way that produces a unique fixed-size "fingerprint" for that input.
Output Hash: The output of the blender is a long string of characters, usually shown as a series of numbers and letters. This is called a "hash." The special thing about Keccak256 is that even a tiny change in the input data will completely change the hash.
Unpredictable: Changing just a single character of the input data results in a completely different hash. It's almost impossible to predict what the hash will look like based on the original data.
Fixed Size: No matter how large or small your input data is, the hash that Keccak256 produces is always the same length. This makes it easy to compare hashes and verify data.
One-Way Function: Once you blend your data with Keccak256, you can't reverse the process to get back your original data. It's like turning an apple into applesauce—you can't turn the sauce back into an apple.
Security and Integrity: Keccak256 is used in blockchain to ensure the integrity of data. If even a tiny part of the original data changes, the hash will change dramatically, alerting you to any tampering.
Another key attribute of this function is the abi. In summary,
abi.encodePacked
is used to format the input data as raw binary, which is then hashed using Keccak256 to generate a hash value. This hash value is stored as an unsigned integer (uint
) and is further constrained to a specific range using the modulus operation. The resulting value is the random DNA value used within the contract.The code in this function was a lot for me to unpack, so I'll break it down a little further the best I can:
Here's a breakdown of the key elements:
abi.encodePacked(_str)
:The
abi.encodePacked
function is used to tightly pack the input data_str
without any padding. It converts the input data into its raw binary representation.This is important because the Keccak256 hash function operates on binary data, and using
abi.encodePacked
ensures that the data is treated as raw binary without any extra formatting.
keccak256(...)
:keccak256
is the Keccak256 hash function, a cryptographic hashing algorithm used to create a unique hash value from its input.It takes the packed binary data generated by
abi.encodePacked(_str)
and produces a hash value that appears random but is deterministic for the same input.
uint(rand)
:After generating the hash using
keccak256
, the result is stored in a variable namedrand
. It's important to note that the output of thekeccak256
function is a hash, which is typically a byte array.By using
uint(rand)
, the hash is converted into an unsigned integer type. This is likely done to ensure compatibility with thednaModulus
calculation and to provide a number within a certain range.
rand % dnaModulus
:The
%
operator calculates the remainder of the division betweenrand
anddnaModulus
. This effectively limits the range of the generated DNA value to be between 0 anddnaModulus - 1
.This step ensures that the DNA value fits within the desired range based on the number of digits specified by
dnaDigits
.
function createRandomZombie(string memory _name) public {
uint randDna = _generateRandomDna(_name);
_createZombie(_name, randDna);
}
- This public function
createRandomZombie
takes a_name
parameter and generates a random DNA value using_generateRandomDna
. It then uses the_createZombie
function to create a new zombie with the generated DNA and the provided name.
Overall, this smart contract allows users to create zombies with random DNA values by calling the createRandomZombie
function. The contract ensures that DNA values are generated securely and logged through events for transparency. The contract's state variables and private functions work together to manage the creation of zombie entities on the Ethereum blockchain.
Review
Okay, before we go, let's look at what our code is doing step by step.
Contract Declaration: The code starts by declaring a Solidity contract named
ZombieFactory
.Event Declaration: An event named
NewZombie
is declared using theevent
keyword. This event will be emitted whenever a new zombie is created, and it will store information about the zombie's ID, name, and DNA.Constants: Two constants,
dnaDigits
anddnaModulus
, are defined.dnaDigits
represents the number of digits in a DNA sequence, anddnaModulus
is calculated as 10 raised to the power ofdnaDigits
. These constants are used to manage the DNA generation process.Struct Declaration: A struct named
Zombie
is defined. This struct has two fields:name
(a string) anddna
(an unsigned integer).Array Declaration: An array of
Zombie
structs namedzombies
is declared as public. This array will store all the zombie entities created by the contract.Function: _createZombie: This is a private function that creates a new zombie. It takes two parameters:
_name
(the zombie's name) and_dna
(the zombie's DNA). Inside this function:A new zombie is created using the provided
_name
and_dna
.The new zombie is added to the
zombies
array using thepush
function, and the index of the newly added zombie is stored in theid
variable.The
NewZombie
event is emitted, passing theid
,_name
, and_dna
as parameters.
Function: _generateRandomDna: Another private function is defined to generate random DNA based on a given input string
_str
. Inside this function:The
_str
is hashed using thekeccak256
hash function.The resulting hash is converted to an unsigned integer and stored in the
rand
variable.The random DNA is calculated as
rand
modulodnaModulus
, ensuring it fits within the desired range.
Function: createRandomZombie: This is a public function intended to be called by users to create a new random zombie. It takes
_name
(the zombie's name) as a parameter. Inside this function:The
_generateRandomDna
function is called with_name
to generate a random DNA for the zombie.The
_createZombie
function is called with_name
andrandDna
to create the new zombie entity and emit theNewZombie
event
That's it for this review. I will be looking over this again before I move on to lesson 2. I think it will help me as I move into more diffucult parts of our code. I hope this was helpful. For those of you who are more advanced than me, constructive criticism is welcomed as long as it is supportive and can help me improve my skills.