Solidity: Beginner to Intermediate Smart Contracts

image

Solidity: Beginner to Intermediate Smart Contracts

Solidity:从初学者到中级智能合约
Solidity 是一种面向合约的编程语言,专门用于在 以太坊(Ethereum)等区块链平台上编写智能合约(Smart Contracts)。它是一种类似 JavaScript、Python 和 C++ 的高级语言,主要用于定义去中心化应用(DApps)的逻辑,确保在区块链上执行透明、安全、不可篡改的交易和合约。

Making the Zombie Factory

Chapter1: lesson overview

在这个课程中,我们要去建造一个创造僵尸军队的僵尸工厂。

  • 工厂会有一个存放僵尸数据的数据库
  • 工厂有一个创造僵尸的函数
  • 每一个僵尸都有一个随机的独一无二的外表

How Zombie DNA Works

image

僵尸的外表是基于僵尸的DNA的,僵尸的DNA是一个16位的整数 belike:

8356281049284737

就和真的DNA一样,DNA的某些位置是和僵尸的外表对应的。比如【前两位对应僵尸的头的类型】【第二个两位对应僵尸的眼睛类型】

在这个tutorial中,头的类型为了便于调整只有7种,实际上两位数可以表示100种(0~99)。

例如:上面的例子中前两位是83​,映射到7种就是83 % 7 + 1 = 7​。所以僵尸会有第七个头的类型。

Chapter2:Contracts

Solidity 的代码封装在 合约(Contract) 中。合约是 以太坊应用程序(Ethereum Applications) 的基本构建块,所有的 变量(Variables)函数(Functions) 都属于某个合约。这将是你所有项目的起点。

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.

一个名为HelloWorld​的空合约示例如下:

1
2
3
contract HelloWorld {

}

Version Pragma

所有的solidity源代码都会以”version pragma”开头,这是对solidity版本的一个声明。在这一套课程中,我们会使用pragma solidiy >=0.5.0 <0.6.0;

放一起就是每次写一个代码之前必须在前面加的:

1
2
3
4
pragma solidity >=0.5.0 <0.6.0;
contract HelloWorld {

}

在我们的僵尸工厂中,我们的Contract.sol​为:

1
2
3
4
pragma solidity >=0.5.0 <0.6.0;
contract ZombieFactory {

}

Chapter3:State Variables & Integers

状态变量(State Variables)是 永久存储在合约存储(Contract Storage) 中的数据。
这意味着它们被 写入以太坊区块链,类似于向数据库(DB)写入数据。

例如:

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

在这个示例合约中,我们创建了一个uint​变量myUnsignedInteger​并且设置为了100

Unsigned Integers:uint

这个数据类型是一个无符号整型,只能存非负数。

在Solidity中,uint​也是一个uint256​一个256bit的无符号整数(意思是能存$0\sim2^{256}-1$的数字对于int256​来说就是$-2^{256-1}\sim2^{256-1}-1$)同样的还有uint8​,uint16​等等。

Chapter4:Math Operations

Solidity中支持数学计算:

  • 加法x + y
  • 减法x - y
  • 乘法x * y
  • 除法x / y
  • 取模x % y

同样还支持指数运算符:

1
uint x = 5 ** 2; // equal to 5^2 = 25

Chapter5:Structs

甚至和C++一样,Solidity提供了结构体的使用

1
2
3
4
struct Person{
uint age;
string name;
}

Chapter6:Arrays

在Solidity中,有两种数组:fixed arraysdynamic arrays

1
2
3
4
5
6
// 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;

甚至可以创建一个结构体数组:

1
Person[] people; // dynamic Array, we can keep adding to it

Public Arrays

可以创建一个public​数组,并且Solidity会自动创建一个getter​方法。

1
Person[] public people;

这样别的合约就只能读取内容,不能写入内容。

Chapter7:Fuction Declarations

在Solidity中,函数的声明长得像:

1
2
3
function eatHambuger(string memory _name, uint _amount) public{

}

这是一个有两个参数(uint​和string​ 的函数)

这里string​是引用类型,必须指定是memory​或者storage​,memory​不会存入区块链,storage​会长期存储并消耗gas

在 Solidity 中,函数参数的传递方式有两种:

  • 按值传递(By Value) 👉 创建副本,函数内的修改不会影响原变量。
  • 按引用传递(By Reference) 👉 直接引用原变量,修改会影响原始数据。

Chapter8:Working With Structs and Arrays

Creating New Structs

首先创建一个结构体Person

1
2
3
4
5
6
struct Person {
uint age;
string name;
}

Person[] public people;

现在我们要创建一个新的Person​,并且将它加入到people​数组中:

1
2
Person satoshi = Person(172, "Satoshi");
people.push(satoshi)

这里的push​是将元素放到数组的最后面。

Chapter9:Private/Public Functions

在Solidity中,函数默认是public​,这意味着所有人或者所有合约都能够调用这个函数。

但是这会让我们的合约容易被攻击, 所以出现了private​函数:

1
2
3
4
uint[] numbers;
function _addToArray(uint _number) private{
numbers.push(_number)
}

这里只有在我们自己合约中的函数才能够调用这个函数。一般来说private​函数的开头总是有一个下划线_

Chapter10:More on Functions

Return Values

是函数返回的一个值(字面意思)

1
2
3
4
string greeting = "What's up";
function sayHello() public returns (string memory){
return greeting;
}

在Solidity中,函数的声明也需要包含返回值的类型(在上面为string​)

Function modifiers

上面的函数并没有改变任何状态,换言之就是没有写入或者是改变什么。

这里我们声明一个view​函数,只viewing​数据,但是不会修改:

1
function sayHello() public view returns (string memory){}

在 Solidity 中,纯函数(Pure Functions) 指的是 不读取或修改区块链上的任何数据 的函数。
这意味着它们只能执行数学计算或处理传入的参数,不会访问 状态变量 或 合约存储。

Chapter 11:Keccak256 and Typecasting

Ethereum中有一个内置函数keccak256​ (SHA3)能够将输入映射为一个256位的随机十六进制数。一个小小的改变都会导致输出的结果发生翻天覆地的变化:

1
2
3
4
//6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5
keccak256(abi.encodePacked("aaaab"));
//b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9
keccak256(abi.encodePacked("aaaac"));

Typecasting

有时候我们需要再数据类型中转换:

1
2
3
4
5
6
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);

就像上面的代码一样,我们只需要在运算过程中进行转换即可。

Chapter 12:Putting It Together

这一章是对前面内容的一个小总结,目标是创建一个public​函数,通过输入僵尸的名字来创建一个随机的僵尸DNA。这里直接贴出:

  1. Create a public​ function named createRandomZombie​. It will take one parameter named _name​ (a string​ with the data location set to memory​). (Note: Declare this function publicjust as you declared previous functions private)
  2. The first line of the function should run the _generateRandomDna​ function on _name​, and store it in a uint​ named randDna​.
  3. The second line should run the _createZombie​ function and pass it _name​ and randDna​.
  4. The solution should be 4 lines of code (including the closing }​ of the function).
1
2
3
4
function createRandomZombie(string memory _name) public {
uint randDna = _generateRandomDna(_name);
_createZombie(_name, randDna);
}

Chapter 13:Events

事件(Events) 是智能合约与外部应用(如前端界面)之间通信的机制。合约通过触发事件,将特定信息记录在区块链的交易日志中,外部应用可以监听这些事件并在发生时采取相应的操作。

1
2
3
4
5
6
7
8
9
// 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;
}

外部应用会监听这个事件,对于JavaScript来说:

1
2
3
YourContract.IntegersAdded(function(error, result) {
// do something with the result
})