1.搭建僵尸工厂
Solidity 定义的函数的属性默认为公共
。 这就意味着任何一方 (或其它合约) 都可以调用你合约里的函数。
这样的合约易于受到攻击。 所以将自己的函数定义为私有
是一个好的编程习惯,只有当你需要外部世界调用它时才将它设置为公共
。
两种新的修饰符
view:意味着函数只能够读取数据不能够修改数据;
pure:不读取应用里的状态,运行仅仅依靠输入的参数。
*private修饰的函数有一定命名规则
*returns 不是return,函数中返回用return
散列函数keccak256
一个散列函数基本上就是把一个字符串转换为一个256位的16进制数字。字符串的一个微小变化会引起散列数据极大变化。
事件
事件 是合约和区块链通讯的一种机制。你的前端应用“监听”某些事件,并做出反应。
前端写代码调用合约
// 下面是调用合约的方式: 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 }
答案:
pragma solidity ^0.4.19;
contract ZombieFactory {
// 这里建立事件
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
struct Zombie {
string name;
uint dna;
}
Zombie[] public zombies;
function _createZombie(string _name, uint _dna) private {
zombies.push(Zombie(_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);
}
}
二、僵尸攻击人类
映射

地址
20字节(一个以太坊地址的长度),地址类型也有成员,地址是所有合约的基础
https://learnblockchain.cn/2017/12/12/solidity2/
Msg.sender
它指的是当前调用者(或智能合约)的 address
。
require
类似asset 用来判断前置条件是否被满足
**Solidity 并不支持原生的字符串比较, 我们只能通过比较 // 两字符串的 keccak256 哈希值来进行判断
继承
类似于java中的extends,在solidity中是is
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;
mapping (uint => address) public zombieToOwner;
mapping (address => uint) ownerZombieCount;
function _createZombie(string _name, uint _dna) private {
uint id = zombies.push(Zombie(_name, _dna)) - 1;
zombieToOwner[id] = msg.sender;
ownerZombieCount[msg.sender]++;
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 {
require(ownerZombieCount[msg.sender] == 0);
uint randDna = _generateRandomDna(_name);
_createZombie(_name, randDna);
}
}
导入 import
变量存储
storage和memory,storage永久,memory暂时
状态变量(在函数之外声明的变量)默认为“存储”形式,并永久写入区块链;而在函数内部声明的变量是“内存”型的,它们函数调用结束后消失。
internal 和 external
除 public 和 private 属性之外,Solidity 还使用了另外两个描述函数可见性的修饰词:internal(内部) 和 external(外部)。
internal 和 private 类似,不过, 如果某个合约继承自其父合约,这个合约即可以访问父合约中定义的“内部”函数。
external 与public 类似,只不过这些函数只能在合约之外调用,它们不能被合约内的其他函数调用。
与其他合约的交互(接口的实现)
类似合约,其中不是函数,接口直接结束。
pragma solidity ^0.4.19;
import "./zombiefactory.sol";
// Create KittyInterface here
contract KittyInterface{
function getKitty(uint256 _id) external view returns (
bool isGestating,
bool isReady,
uint256 cooldownIndex,
uint256 nextActionAt,
uint256 siringWithId,
uint256 birthTime,
uint256 matronId,
uint256 sireId,
uint256 generation,
uint256 genes
) ;
}
contract ZombieFeeding is ZombieFactory {
function feedAndMultiply(uint _zombieId, uint _targetDna) public {
require(msg.sender == zombieToOwner[_zombieId]);
Zombie storage myZombie = zombies[_zombieId];
_targetDna = _targetDna % dnaModulus;
uint newDna = (myZombie.dna + _targetDna) / 2;
_createZombie("NoName", newDna);
}
}
在 Solidity中,您可以让一个函数返回多个值
接口是合约外定义,合约中使用,需要初始化接口。
三、高级solidity理论
智能协议的永固性
在你把智能协议传上以太坊之后,它就变得不可更改, 这种永固性意味着你的代码永远不能被调整或更新。
不能硬编码,而要采用“函数”,以便于 DApp 的关键部分可以以参数形式修改。
pragma solidity ^0.4.19;
import "./zombiefactory.sol";
contract KittyInterface {
function getKitty(uint256 _id) external view returns (
bool isGestating,
bool isReady,
uint256 cooldownIndex,
uint256 nextActionAt,
uint256 siringWithId,
uint256 birthTime,
uint256 matronId,
uint256 sireId,
uint256 generation,
uint256 genes
);
}
contract ZombieFeeding is ZombieFactory {
KittyInterface kittyContract;
function setKittyContractAddress(address _address) external {
kittyContract=KittyInterface(_address);
}
function feedAndMultiply(uint _zombieId, uint _targetDna, string species) public {
require(msg.sender == zombieToOwner[_zombieId]);
Zombie storage myZombie = zombies[_zombieId];
_targetDna = _targetDna % dnaModulus;
uint newDna = (myZombie.dna + _targetDna) / 2;
if (keccak256(species) == keccak256("kitty")) {
newDna = newDna - newDna % 100 + 99;
}
_createZombie("NoName", newDna);
}
function feedOnKitty(uint _zombieId, uint _kittyId) public {
uint kittyDna;
(,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
feedAndMultiply(_zombieId, kittyDna, "kitty");
}
}
OpenZeppelin库的Ownable
合约
- 构造函数:
function Ownable()
是一个 _ constructor_ (构造函数),构造函数不是必须的,它与合约同名,构造函数一生中唯一的一次执行,就是在合约最初被创建的时候。 - 函数修饰符:
modifier onlyOwner()
。 修饰符跟函数很类似,不过是用来修饰其他已有函数用的, 在其他语句执行前,为它检查下先验条件。 在这个例子中,我们就可以写个修饰符onlyOwner
检查下调用者,确保只有合约的主人才能运行本函数。我们下一章中会详细讲述修饰符,以及那个奇怪的_;
。
- 合约创建,构造函数先行,将其
owner
设置为msg.sender
(其部署者) - 为它加上一个修饰符
onlyOwner
,它会限制陌生人的访问,将访问某些函数的权限锁定在owner
上。 - 允许将合约所有权转让给他人。
pragma solidity ^0.4.19;
// 1. 在这里导入
import "./ownable.sol";
// 2. 在这里继承:
contract ZombieFactory is Ownable{
event NewZombie(uint zombieId, string name, uint dna);
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
struct Zombie {
string name;
uint dna;
}
Zombie[] public zombies;
mapping (uint => address) public zombieToOwner;
mapping (address => uint) ownerZombieCount;
function _createZombie(string _name, uint _dna) internal {
uint id = zombies.push(Zombie(_name, _dna)) - 1;
zombieToOwner[id] = msg.sender;
ownerZombieCount[msg.sender]++;
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 {
require(ownerZombieCount[msg.sender] == 0);
uint randDna = _generateRandomDna(_name);
randDna = randDna - randDna % 100;
_createZombie(_name, randDna);
}
}
函数修饰符看起来跟函数没什么不同,不过关键字modifier 告诉编译器,这是个modifier(修饰符),而不是个function(函数)。它不能像函数那样被直接调用,只能被添加到函数定义的末尾,用以改变函数的行为。
pragma solidity ^0.4.19;
import "./zombiefactory.sol";
contract KittyInterface {
function getKitty(uint256 _id) external view returns (
bool isGestating,
bool isReady,
uint256 cooldownIndex,
uint256 nextActionAt,
uint256 siringWithId,
uint256 birthTime,
uint256 matronId,
uint256 sireId,
uint256 generation,
uint256 genes
);
}
contract ZombieFeeding is ZombieFactory {
KittyInterface kittyContract;
// 修改这个函数:
function setKittyContractAddress(address _address) external onlyOwner {
kittyContract = KittyInterface(_address);
}
function feedAndMultiply(uint _zombieId, uint _targetDna, string species) public {
require(msg.sender == zombieToOwner[_zombieId]);
Zombie storage myZombie = zombies[_zombieId];
_targetDna = _targetDna % dnaModulus;
uint newDna = (myZombie.dna + _targetDna) / 2;
if (keccak256(species) == keccak256("kitty")) {
newDna = newDna - newDna % 100 + 99;
}
_createZombie("NoName", newDna);
}
function feedOnKitty(uint _zombieId, uint _kittyId) public {
uint kittyDna;
(,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
feedAndMultiply(_zombieId, kittyDna, "kitty");
}
}
驱动以太坊的能源GAS
一个 DApp 收取多少 gas 取决于功能逻辑的复杂程度。每个操作背后,都在计算完成这个操作所需要的计算资源,(比如,存储数据就比做个加法运算贵得多), 一次操作所需要花费的 gas 等于这个操作背后的所有运算花销的总和。
为什么要用gas来驱动?
以太坊就像一个巨大、缓慢、但非常安全的电脑。当你运行一个程序的时候,网络上的每一个节点都在进行相同的运算,以验证它的输出 —— 这就是所谓的“去中心化” 由于数以千计的节点同时在验证着每个功能的运行,这可以确保它的数据不会被被监控,或者被刻意修改。
讲道理,这就是去中心化的代价吗?
节省gas的方法:使用struct封装结构
所以,当 uint
定义在一个 struct
中的时候,尽量使用最小的整数子类型以节约空间。 并且把同样类型的变量放一起(即在 struct 中将把变量按照类型依次放置),这样 Solidity 可以将存储空间最小化。
最好把相同的类型放在一起会使用更少的存储空间。
时间单位
pragma solidity ^0.4.19;
import "./zombiefactory.sol";
contract KittyInterface {
function getKitty(uint256 _id) external view returns (
bool isGestating,
bool isReady,
uint256 cooldownIndex,
uint256 nextActionAt,
uint256 siringWithId,
uint256 birthTime,
uint256 matronId,
uint256 sireId,
uint256 generation,
uint256 genes
);
}
contract ZombieFeeding is ZombieFactory {
KittyInterface kittyContract;
function setKittyContractAddress(address _address) external onlyOwner {
kittyContract = KittyInterface(_address);
}
function _triggerCooldown(Zombie storage _zombie) internal {
_zombie.readyTime = uint32(now + cooldownTime);
}
// 2. 在这里定义 `_isReady` 函数
function _isReady(Zombie storage _zombie) internal view returns (bool){
return (_zombie.readyTime <= now);
}
function feedAndMultiply(uint _zombieId, uint _targetDna, string species) public {
require(msg.sender == zombieToOwner[_zombieId]);
Zombie storage myZombie = zombies[_zombieId];
_targetDna = _targetDna % dnaModulus;
uint newDna = (myZombie.dna + _targetDna) / 2;
if (keccak256(species) == keccak256("kitty")) {
newDna = newDna - newDna % 100 + 99;
}
_createZombie("NoName", newDna);
}
function feedOnKitty(uint _zombieId, uint _kittyId) public {
uint kittyDna;
(,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
feedAndMultiply(_zombieId, kittyDna, "kitty");
}
}
使用修饰符
pragma solidity ^0.4.19;
import "./zombiefeeding.sol";
contract ZombieHelper is ZombieFeeding {
modifier aboveLevel(uint _level, uint _zombieId) {
require(zombies[_zombieId].level >= _level);
_;
}
function changeName(uint _zombieId, string _newName) external aboveLevel(2,_zombieId)
{
require(msg.sender == zombieToOwner[_zombieId]);
zombies[_zombieId].name = _newName;
}
function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20,_zombieId)
{
require (msg.sender==zombieToOwner[_zombieId]);
zombies[_zombieId].dna = _newDna;
}
}
利用 ‘View’ 函数节省 Gas
