soildity的在线ide remixhttps://remix.ethereum.org/

soildity学习网站 CryptoZombies#1 Solidity Tutorial & Ethereum Blockchain Programming Course | CryptoZombies

基于CryptoZombies的soildity学习笔记(友链):ZombieBlock | uloveRock?

Solidity: Beginner to Intermediate Smart Contracts

Lesson 1:创建一个合约

  1. 合约

Solidity 的代码都包裹在合约里面. 一份合约就是以太应币应用的基本模块, 所有的变量和函数都属于一份合约, 它是你所有应用的起点.

Solidity 源码都必须使用 “version pragma” — 标明 Solidity 编译器的版本.

1
2
3
4
5
pragma solidity ^0.4.19;//编译器版本标识
// 合约
contract helloworld {

}
  1. 变量类型

uint 无符号数据类型, 指其值不能是负数,对于有符号的整数存在名为 int 的数据类型。 uint 实际上是uint256代名词, 一个256位的无符号整数。另有 uint8 uint16 uint32等类型。

可使用如uint8(变量)进行数据类型转换

  1. 数学运算

与其他语言相同,并且支持乘方操作:

1
uint x = 5 ** 2; // equal to 5^2 = 25
  1. 结构体

sol可以创建结构体

1
2
3
4
5
6
7
8
9
10
11
struct Person {
uint age;
string name;
}

// 创建一个结构体数组
Person[] public people;//public 可公共访问
// 创建一个新person
Person satoshi = Person(172, "Sam");
// 将新创建的person添加进people数组:
people.push(satoshi);
  1. 数组

sol有动态数组和静态数组两种数组。

1
2
3
4
5
6
7
8
// 固定长度为2的静态数组:
uint[2] fixedArray;
// 固定长度为5的string类型的静态数组:
string[5] stringArray;
// 动态数组,长度不固定,可以动态添加元素:
uint[] dynamicArray;
// 结构体类型数组
structName[] structArray;

array.push() 在数组的 尾部 加入新元素 ,所以元素在数组中的顺序就是我们添加的顺序

  1. 函数

在 Solidity 中函数定义的句法如下:

1
2
3
4
function functionName1(uint _number) public view returns (string){
}
function _functionName2(uint _number) private returns (string,int){
}

函数属性:
**public/private:**默认为public公共函数 , 可改为private私有函数.私有函数的名字需要用(_)起始;

返回值:函数的定义里可包含返回值的数据类型(如本例中 string)。可以有多个返回值.

**修饰符view/pure:**view只读函数,pure不访问数据的函数

  1. eccak256

Ethereum 内部有一个散列函数keccak256,它用了SHA3版本。一个散列函数基本上就是把一个字符串转换为一个256位的16进制数字。

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

事件 是合约和区块链通讯的一种机制。可以添加事件供前端监听

1
2
3
4
5
6
7
8
9
// 这里建立事件
event IntegersAdded(uint x, uint y, uint result);

function add(uint _x, uint _y) public {
uint result = _x + _y;
//触发事件,通知app
IntegersAdded(_x, _y, result);
return result;
}
  1. web3.js

以太坊有一个 JavaScript 库,名为Web3.js

js示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 下面是调用合约的方式:
var abi = /* abi是由编译器生成的 */
var ZombieFactoryContract = web3.eth.contract(abi)
var contractAddress = /* 发布之后在以太坊上生成的合约地址 */
var ZombieFactory = ZombieFactoryContract.at(contractAddress)
// `ZombieFactory` 能访问公共的函数以及事件

// 某个监听文本输入的监听器:
$("#ourButton").click(function(e) {
var name = $("#nameInput").val()
//调用合约的 `createRandomZombie` 函数:
ZombieFactory.createRandomZombie(name)
})

// 监听 `NewZombie` 事件, 并且更新UI
var event = ZombieFactory.NewZombie(function(error, result) {
if (error) return
generateZombie(result.zombieId, result.name, result.dna)
})

// 获取 Zombie 的 dna, 更新图像
function generateZombie(id, name, dna) {
let dnaStr = String(dna)
// 如果dna少于16位,在它前面用0补上
while (dnaStr.length < 16)
dnaStr = "0" + dnaStr

let zombieDetails = {
// 前两位数构成头部.我们可能有7种头部, 所以 % 7
// 得到的数在0-6,再加上1,数的范围变成1-7
// 通过这样计算:
headChoice: dnaStr.substring(0, 2) % 7 + 1
// 我们得到的图片名称从head1.png 到 head7.png

// 接下来的两位数构成眼睛, 眼睛变化就对11取模:
eyeChoice: dnaStr.substring(2, 4) % 11 + 1,
// 再接下来的两位数构成衣服,衣服变化就对6取模:
shirtChoice: dnaStr.substring(4, 6) % 6 + 1,
//最后6位控制颜色. 用css选择器: hue-rotate来更新
// 360度:
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
}

第一节课内容总结:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
pragma solidity ^0.4.19;

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 _name, uint _dna) private {
uint id = zombies.push(Zombie(_name, _dna)) - 1;
NewZombie(id, _name, _dna);
}

function _generateRandomDna(string _str) private view returns (uint) {
uint rand = uint(keccak256(_str));
return rand % dnaModulus;
}

function createRandomZombie(string _name) public {
uint randDna = _generateRandomDna(_name);
_createZombie(_name, randDna);
}
}

Lesson 2:使用多个合约

  1. Address(地址)和Mapping(映射)

以太坊区块链由 _ account _ (账户)组成,你可以把它想象成银行账户。一个帐户的余额是 以太 (在以太坊区块链上使用的币种),你可以和其他帐户之间支付和接受以太币,就像你的银行帐户可以电汇资金到其他银行帐户一样.

每个帐户都有一个Address“地址”,你可以把它想象成银行账号,这是账户唯一的标识符.我们可以指定地址作为用户的id.

mapping(映射)本质上是存储和查找数据所用的键值对:

1
2
3
4
//对于金融应用程序,将用户的余额保存在一个 uint类型的变量中.即address在此映射上指向一个uint
mapping (address => uint) public accountBalance;
//或者可以用来通过userId 存储/查找的用户名,即uint在此映射上指向一个address
mapping (uint => string) userIdToName;

在 Solidity 中,有一些全局变量可以被所有函数调用。 其中一个就是 msg.sender,它指的是当前调用者(或智能合约)的 address

在 Solidity 中,功能执行始终需要从外部调用者开始。一个合约只会在区块链上什么也不做,除非有人调用其中的函数。所以 msg.sender总是存在的。

通过msg.sender可便于更新mapping数据:

1
2
accountBalance[msg.sender] = uint;
userIdToName[uint] = msg.sender;

使用 msg.sender 很安全,因为它具有以太坊区块链的安全保障 —— 除非窃取与以太坊地址相关联的私钥,否则是没有办法修改其他人的数据的。

  1. require

require可以使函数在执行过程中,当不满足某些条件时抛出错误,并停止执行:

1
2
3
4
5
6
7
function sayHiToVitalik(string _name) public returns (string) {
// 比较 _name 是否等于 "Vitalik". 如果不成立,抛出异常并终止程序
// (敲黑板: Solidity 并不支持原生的字符串比较, 我们只能通过比较两字符串的 keccak256 哈希值来进行判断)
require(keccak256(_name) == keccak256("Vitalik"));
// 如果返回 true, 运行如下语句
return "Hi!";
}

编写函数时,用require作验证条件是很有必要的.

  1. inheritance(继承)

合约之间是可以继承的.

1
2
3
4
5
6
7
8
9
10
11
contract Doge {
function catchphrase() public returns (string) {
return "So Wow CryptoDoge";
}
}
// 继承自Doge,可以调用catchphrase()
contract BabyDoge is Doge {
function anotherCatchphrase() public returns (string) {
return "Such Moon BabyDoge";
}
}
  1. Import(引入)

solidity通过import引入其他文件:

1
import "./ep1.sol"
  1. 存储变量

在 Solidity 中,有两个地方可以存储变量。 storage是永久存储在区块链中的变量。 Memory存储临时变量,当外部函数对某合约调用完成时,内存型变量即被移除。

大多数时候你都用不到这些关键字,默认情况下 Solidity 会自动处理它们。 状态变量(在函数之外声明的变量)默认为“存储”形式,并永久写入区块链;而在函数内部声明的变量是“内存”型的,它们函数调用结束后消失。但在有些情况(处理函数内的结构体和数组)需要手动声明类型.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
contract SandwichFactory {
struct Sandwich {
string name;
string status;
}
Sandwich[] sandwiches;

function eatSandwich(uint _index) public {
// Sandwich mySandwich = sandwiches[_index];
// ^ 看上去很直接,不过 Solidity 将会给出警告
// 告诉你应该明确在这里定义 `storage` 或者 `memory`。
// 所以你应该明确定义 `storage`:
Sandwich storage mySandwich = sandwiches[_index];
// ...这样 `mySandwich` 是指向 `sandwiches[_index]`的指针
// 在存储里,另外...
mySandwich.status = "Eaten!";
// ...这将永久把 `sandwiches[_index]` 变为区块链上的存储

// 如果你只想要一个副本,可以使用`memory`:
Sandwich memory anotherSandwich = sandwiches[_index + 1];
// ...这样 `anotherSandwich` 就仅仅是一个内存里的副本了
// 另外
anotherSandwich.status = "Eaten!";
// ...将仅仅修改临时变量,对 `sandwiches[_index + 1]` 没有任何影响
// 不过你可以这样做:
sandwiches[_index + 1] = anotherSandwich;
// ...如果你想把副本的改动保存回区块链存储
}
}
  1. 函数可见性internalexternal

合约继承时,是不能继承访问父代的私有函数(private)的.

publicprivate 属性之外,Solidity 还使用了另外两个描述函数可见性的修饰词:internal(内部) 和 external(外部)。两个修饰词的用法与publicprivate相同.

internalprivate 类似,不过, 合约可以继承父合约的内部函数(internal);

externalpublic 类似,不过,这些函数只能在合约之外调用 - 它们不能被合约内的其他函数调用。

  1. 与其他合约交互

如果我们的合约需要和区块链上的其他的合约会话,则需先定义一个 interface (接口)。

如果有这么一个合约:

1
2
3
4
5
6
7
8
9
10
contract LuckyNumber {
mapping(address => uint) numbers;

function setNum(uint _num) public {
numbers[msg.sender] = _num;
}
function getNum(address _myAddress) public view returns (uint) {
return numbers[_myAddress];
}
}

如果我们想要和此合约对话,调用其中的getNum函数,我们需要定义并使用一个接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//合约外定义一个接口
//这个过程虽然看起来像在定义一个合约,但实际上是声明一个没有定义函数体的函数.
//由于只声明了函数,此时接口是不能指向合约的
contract NumberInterface {
function getNum(address _myAddress) public view returns (uint);
}

//使用接口:
contract MyContract {
address NumberInterfaceAddress = 0xab38...;
// ^ 这是LuckyNumber合约在以太坊上的地址
NumberInterface numberContract = NumberInterface(NumberInterfaceAddress);
// 现在变量 `numberContract` 指向LuckyNumber的地址

function someFunction() public {
// 现在我们可以调用在那个合约中声明的 getNum函数:
uint num = numberContract.getNum(msg.sender);
// ...在这儿使用 `num`变量做些什么
}
}
  1. 多个返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function multipleReturns() internal returns(uint a, uint b, uint c) {
return (1, 2, 3);
}

function processMultipleReturns() external {
uint a;
uint b;
uint c;
// 这样来做批量赋值:
(a, b, c) = multipleReturns();
}

// 或者如果我们只想返回其中一个变量:
function getLastReturnValue() external {
uint c;
// 可以对其他字段留空:
(,,c) = multipleReturns();
}
  1. if语句
1
2
3
4
5
6
7
function eatBLT(string sandwich) public {
// Solidity 并不支持原生的字符串比较, 我们只能通过比较两字符串的 keccak256 哈希值来进行判断
// 看清楚了,当我们比较字符串的时候,需要比较他们的 keccak256 哈希码
if (keccak256(sandwich) == keccak256("BLT")) {
eat();
}
}

总结:

前端js实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var abi = /* abi generated by the compiler */
var ZombieFeedingContract = web3.eth.contract(abi)
var contractAddress = /* our contract address on Ethereum after deploying */
var ZombieFeeding = ZombieFeedingContract.at(contractAddress)

// 假设我们有我们的僵尸ID和要攻击的猫咪ID
let zombieId = 1;
let kittyId = 1;

// 要拿到猫咪的DNA,我们需要调用它的API。这些数据保存在它们的服务器上而不是区块链上。
// 如果一切都在区块链上,我们就不用担心它们的服务器挂了,或者它们修改了API,
// 或者因为不喜欢我们的僵尸游戏而封杀了我们
let apiUrl = "https://api.cryptokitties.co/kitties/" + kittyId
$.get(apiUrl, function(data) {
let imgUrl = data.image_url
// 一些显示图片的代码
})

// 当用户点击一只猫咪的时候:
$(".kittyImage").click(function(e) {
// 调用我们合约的 `feedOnKitty` 函数
ZombieFeeding.feedOnKitty(zombieId, kittyId)
})

// 侦听来自我们合约的新僵尸事件好来处理
ZombieFactory.NewZombie(function(error, result) {
if (error) return
// 这个函数用来显示僵尸:
generateZombie(result.zombieId, result.name, result.dna)
})

Lesson 3:高级Solidity理论