【CryptoZombies - 2 Solidity 进阶】001 智能合约的不可篡改性与Ownable
目录
一、前言
看了一些区块链的教程,论文,在网上刚刚找到了一个项目实战,CryptoZombies。
终于要更换新标题了,这次命名为进阶,也就是说之前的是基础教程,因为从这一次的内容开始,我们将通过这个项目实战,更加深入了解solidity。也更能区分solidity和其他的一些编程语言的区别。当然,我们会在后面详细说明。
如果你想了解更多有关于机器学习、深度学习、区块链、计算机视觉等相关技术的内容,想与更多大佬一起沟通,那就扫描下方二维码加入我们吧!
二、不可篡改性与外部依赖
1、讲解
1.不可篡改
我们学完了solidity的基础,但是我们发现,我们讲的 Solidity 和其他语言没有质的区别,它长得也很像 JavaScript。但是以太坊solidity开发的是去中心化的应用DAPP,这个和普通的应用是有很大的差别的。
我们都知道,以太坊是区块链2.0,我们在以太坊上部署合约,就满足区块链中的一个非常重要的特性,不可篡改。
所以如果我们将我们写好的合约上传到以太坊,就不能再修改了。合约就会永远保存在以太坊上,这个特性保证了合约的安全性。但是我们的合约编写的一旦有漏洞,我们就只能放弃这个合约,然后转移到新的修复后的合约上。
总结一下,这个特性的优点如下:
因为其不可篡改,一个合约定义并部署好后,所有的程序会按照既定的代码一丝不苟的执行,其结果不会被中途恶意用户篡改。从而极大的保证了合约的安全性。也更具公平性。
也不是说这个特性是没有缺点的:
因为其不可篡改,一旦发现合约存在问题,那这个合约就作废了,并且一直留存在以太坊中,久而久之,无用的协议增多,白白浪费很多内存。就像你电脑中一旦有了垃圾文件,一直删不掉,着实挺难受,强迫症更难受。
2.外部依赖关系
我们之前调用加密小猫,在地址中使用的地址是固定的,也就是硬编码到DAPP中的,一旦加密小猫出现问题,那我们的DAPP也就会出现问题了,所以我们在真实的使用中是不能使用硬编码的,我们要通过一个函数来获取其地址,这样我们就可以便于在DAPP的关键部分中使用参数进行数据的修改了。
在这个过程中我们对外部的合约有依赖,涉及到了外部依赖关系,这种依赖,我们应该是灵活可动态修正的依赖。
2、实战
1.要求
我们来定义一个函数,从 kitty 合约中获取它的基因:
1.删除采用硬编码 方式的
ckAddress
代码行。2.之前创建
kittyContract
变量的那行代码,修改为对kittyContract
变量的声明 -- 暂时不给它指定具体的实例。3.创建名为
setKittyContractAddress
的函数, 它带一个参数_address
(address
类型), 可见性设为external。
4.在函数内部,添加一行代码,将
kittyContract
变量设置为返回值:KittyInterface(_address)
。
pragma solidity >=0.5.0 <0.6.0;
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 {
// 1. Remove this:
address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;
// 2. Change this to just a declaration:
KittyInterface kittyContract = KittyInterface(ckAddress);
// 3. Add setKittyContractAddress method here
function feedAndMultiply(uint _zombieId, uint _targetDna, string memory _species) public {
require(msg.sender == zombieToOwner[_zombieId]);
Zombie storage myZombie = zombies[_zombieId];
_targetDna = _targetDna % dnaModulus;
uint newDna = (myZombie.dna + _targetDna) / 2;
if (keccak256(abi.encodePacked(_species)) == keccak256(abi.encodePacked("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");
}
}
2.代码
pragma solidity >=0.5.0 <0.6.0;
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 {
// 1. Remove this:
// 2. Change this to just a declaration:
KittyInterface kittyContract;
// 3. Add setKittyContractAddress method here
function setKittyContractAddress(address _address) external {
kittyContract = KittyInterface(_address);
}
function feedAndMultiply(uint _zombieId, uint _targetDna, string memory _species) public {
require(msg.sender == zombieToOwner[_zombieId]);
Zombie storage myZombie = zombies[_zombieId];
_targetDna = _targetDna % dnaModulus;
uint newDna = (myZombie.dna + _targetDna) / 2;
if (keccak256(abi.encodePacked(_species)) == keccak256(abi.encodePacked("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");
}
}
三、Ownable
1、讲解
在上面的例题中,我们将函数声明为外部。我想大家应该还记得外部的特点:
定义为外部的函数只能在合约之外调用 - 它们不能被合约内的其他函数调用。
如果我们这样定义函数,函数之外的合约都可以调用。也就是说任何可以调用该函数的人都可以修改加密猫的地址。我们确实希望这个地址能够在合约中修改,但是我们并不希望所有人都可以修改,所以我们还要继续完善权限。
在这里我们使用Ownable合约。首先我们先举一个示例,然后再逐步讲解。
contract Ownable {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
function Ownable() public {
owner = msg.sender;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0));
OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}
在这里,我们涉及到如下知识点:
1.构造函数
这个名词,想必大家都很熟悉了,在这里的构造函数是:
function Ownable() public {
owner = msg.sender;
}
在以太坊中,构造函数不是必须的,但是一旦我们使用,就要遵守相关规则:
1.与合约同名。
2.构造函数只在合约最初被创建的时候执行一次。
2.函数修饰符
函数修饰符跟函数很类似,不过函数修饰符是用来修饰其他已有函数的,在这里的函数修饰符是:
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
函数修饰符在这里的作用是:
在其他语句执行前,为它检查下先验条件。
在我们的这个样例中,我们使用函数修饰符检查调用者,确保只有合约的主人才能运行本函数。
函数修饰符看起来跟函数没什么不同,不过关键字modifier
告诉编译器,这是个modifier(修饰符)
,而不是个function(函数)
。它不能像函数那样被直接调用,只能被添加到函数定义的末尾,用以改变函数的行为。
接下来我们来看一下修饰符如何使用。
contract MyContract is Ownable {
event LaughManiacally(string laughter);
//注意! `onlyOwner`上场 :
function likeABoss() external onlyOwner {
LaughManiacally("Muahahahaha");
}
}
对于上述代码执行过程如下:
1.调用
likeABoss
函数时,首先执行onlyOwner
中的代码。2.执行到
onlyOwner
中的_;
语句时,程序再返回并执行likeABoss
中的代码。注:一般修饰符常用于添加require检查。
除了上面这两个还有很多,我们以后再慢慢讲到。
所以一个Ownable合约基本流程如下:
1.合约创建,构造函数先行,将其
owner
设置为msg.sender
(其部署者)。2.为它加上一个修饰符
onlyOwner
,它会限制陌生人的访问,将访问某些函数的权限锁定在owner
上。3.允许将合约所有权转让给他人
。
2、实战
1.要求
在僵尸代码中实现小猫的基因。:
1.在程序中导入 ownable.sol 的内容。 。
2.修改 ZombieFactory 合约, 让它继承自 Ownable。
3.将
onlyOwner
函数修饰符添加到setKittyContractAddress
中。
2.代码
pragma solidity >=0.5.0 <0.6.0;
// 1. Import here
import "./ownable.sol";
// 2. Inherit here:
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 memory _name, uint _dna) internal {
uint id = zombies.push(Zombie(_name, _dna)) - 1;
zombieToOwner[id] = msg.sender;
ownerZombieCount[msg.sender]++;
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 {
require(ownerZombieCount[msg.sender] == 0);
uint randDna = _generateRandomDna(_name);
randDna = randDna - randDna % 100;
_createZombie(_name, randDna);
}
}
pragma solidity >=0.5.0 <0.6.0;
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;
// Modify this function:
function setKittyContractAddress(address _address) external onlyOwner {
kittyContract = KittyInterface(_address);
}
function feedAndMultiply(uint _zombieId, uint _targetDna, string memory _species) public {
require(msg.sender == zombieToOwner[_zombieId]);
Zombie storage myZombie = zombies[_zombieId];
_targetDna = _targetDna % dnaModulus;
uint newDna = (myZombie.dna + _targetDna) / 2;
if (keccak256(abi.encodePacked(_species)) == keccak256(abi.encodePacked("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");
}
}