实验五:认识智能合约&线上 IDE 实现 Solidity 合约
实验概述
本实验参考自以太坊中的以太猫游戏和 Loom Network 团队的智能合约教学案例,进行 Solidity 智能合约入门与 remix 在线 IDE 使用练习,通过构建一个“宠物游戏”来学习智能合约的编写,在实验中穿插 Solidity 基础知识。
实验5-1:Solidity 基础
电子宠物很多人都知道,乃至养过,其中以拓麻歌子最为著名。本实验则以电子宠物为背景,来用智能合约的方式创造一个新的“电子宠物世界”。
实验 5-1 目的创造一个“宠物孵化池”, 通过孵化池,就能够从中诞生一个全新的宠物。那么这个孵化池需要满足这么几个功能:
• 数据库中能够记录所有的宠物信息
• 应该在孵化池中存在这样一个接口,让我们能够从中孵化宠物
• 每个宠物都应该是独一无二的
宠物 DNA
参考以太猫的 DNA,宠物的独立标识则为他的 DNA。本实验定义的 DNA 很简单,由一个 16 位的整数组成:
8356281049284737
事实上,每一只电子宠物也是由这样一串数字构成,他的各位则表示了不同的属性。比如前 2 位表示物种,紧接着的 2 位表示有没有翅膀,等等。
实验内容
- 清空原有.sol 文件,新建文件 AnimalIncubators.sol
- 为了建立宠物部队,先建立一个基础合约 AnimalIncubators,并指定 Solidity 编译器版本。
- 因为我们的宠物 DNA 由十六位数字组成,所以首先,需要在合约中定义 dnaDigits 为 uint 数据类型, 并赋值 16,用来表示 dna 的位数。
- 为了保证我们的宠物的 DNA 只含有 16 个字符,我们需要一个 uint 变量等于 10^16。便于后续取模。
– 建立一个 dnaLength 变量, 令其等于 10 的 dnaDigits 次方。 - 创建宠物结构体
– 建立一个名为 Animal 的结构体。在其中有两个属性: name (类型为
string), 和 dna (类型为 uint)。 - 准备工作完成后,首先需要将宠物们保存在合约中,并且让其它合约也能够看到这些宠物们,因此需要一个公共数组。
– 创建一个 Animal 的结构体数组,用 public 修饰,命名为 animals. - 定义一个事件 NewAnimal。 它有 3 个参数: AnimalId (uint), name (string),和 dna (uint)。
- 定义一个 孵化宠物函数,其功能为:孵化一个新宠物并添加入 animals 数组中
• 建立一个私有函数 _createAnimal。 它有两个参数: _name (类型为 string), 和 _dna (类型为 uint)。
• 在函数体里新创建一个 Animal, 然后把它加入 animals 数组中。 很显然新创建的宠物属性就来自于函数的入参,同时将 animals 的索引值记录下来为 animalId。
• 在函数结束触发事件 NewAnimal。 - 定义 DNA 生成函数:能够根据字符串随机生成一个 DNA。
• 创建一个函数 _generateRandomDna,将属性标记为 private。它只接收一个输入变量 _str (类型 string), 返回一个 uint 类型的数值。此函数只读取我们合约中的一些变量,所以可以标记 view 属性。
• 使用以太坊内部的哈希函数 keccak256,根据输入参数_str 来生成一个十六进制数,类型转换为 uint 后,返回该值的后 dnaLength 位。
Ethereum 内部有一个散列函数 keccak256,它用了 SHA3 版本。一个散列函数基本上就是把一个字符串转换为一个 256 位的 16 进制数字。字符串的一个微小变化会引起散列数据极大变化。 这在 Ethereum 中有很多应用,但是现在我们只是用它造一个伪随机数。
使用样例:keccak256("abcdefg") - 定义 第一个公共函数来把上面定义的若干部件组合起来,使得实现这样的一个功能:该函数能够接收宠物的名字,然后用这个名字来生成宠物的 DNA
– 创建一个 public 函数,命名为 createRandomAnimal. 它将接收一个变量 _name (数据类型是 string)。
– 首先调用 _generateRandomDna 函数,传入 _name 参数来生成一个DNA,取名为 randDna。
– 调用 _createAnimal 函数,将这个新生成的宠物记录下来, 传入参数: _name 和 randDna。
需实现效果
在 JavaScript VM 环境下,部署 AnimalIncubators 合约。创建三个分别叫Drogon、Rheagal、Viserion 的宠物,展示其 DNA。
实验流程
创造一个“宠物孵化池”, 通过孵化池,就能够从中诞生一个全新的宠物。
在 JavaScript VM 环境下,部署 AnimalIncubators 合约。创建三个分别叫 Drogon、Rheagal、Viserion 的宠物。
- 新建文件 AnimalIncubators.sol。
- 建立一个基础合约 AnimalIncubators,并指定 Solidity编译器版本。
- 在合约中定义dnaDigits 为 uint 数据类型, 并赋值 16,用来表示 dna 的位数。
- 定义一个 uint 变量等于10^16,便于后续取模。
- 创建宠物结构体,包含name和dna两个属性。
- 创建一个公共数组,将宠物们保存在合约中,并且让其它合约也能够看到这些宠物们。
- 定义一个事件 NewAnimal,包含 3 个参数: AnimalId (uint), name (string),和 dna (uint)。
- 定义一个 孵化宠物函数,其功能为:孵化一个新宠物并添加入 animals 数组中。
- 定义 DNA 生成函数,使其能够根据字符串随机生成一个 DNA。
- 定义 第一个公共函数来把上面定义的若干部件组合起来,使得该函数能够接收宠物的名字,然后用这个名字来生成宠物的 DNA。
源代码
AnimalIncubators.sol
pragma solidity ^0.4.26;
contract AnimalIncubators{
uint dnaDigits=16;
uint dnaLength=10**16;
struct Animal{
string name;
uint dna;
}
Animal[] public animals;
event NewAnimal(uint AnimalID,string name,uint dna);
function _createAnimal(string _name,uint _dna) private{
animals.push(Animal(_name,_dna));
uint animalID=animals.length-1;
NewAnimal(animalID,_name,_dna);
}
function _generateRandomDna(string _str)private view returns(uint){
return uint(keccak256(_str))%dnaLength;
}
function createRandomAnimal(string _name) public{
uint randDna=_generateRandomDna(_name);
_createAnimal(_name,randDna);
}
}
实验结果截图
创建名为Drogon的宠物:
创建名为Rheagal的宠物:
创建名为Viserion的宠物:
实验5-2:Solidity 进阶——宠物成长系统
实验一中创建了一个函数用来生成宠物,并且存入区块链上的宠物数据库中。 实验二中,我们会模拟以太猫的繁殖机制,创建一个宠物成长系统,让宠物可以通过进食进行成长,系统会通过宠物和食物的 DNA 计算出新宠物的 DNA。
实验内容
- 首先使用地址给宠物指定“主人”,为了存储宠物的所有权,我们会使用到两个映射:一个记录宠物拥有者的地址,另一个记录某地址所拥有宠物的数量。
– 创建一个叫做 AnimalToOwner 的映射。其键是一个 uint(我们将根据它的 id 存储和查找宠物),值为 address。映射属性为 public。
– 创建一个名为 ownerAnimalCount 的映射,其中键是 address,值是 uint。 - 修改_createAnimal 函数来使用映射
– 首先,在得到新的宠物 animalId 后,更新 AnimalToOwner 映射,在AnimalId 下面存入 msg.sender。
– 然后,我们为这个 msg.sender 名下的 ownerAnimalCount 加 1。 - 在 createRandomAnimal 开头使用 require 来确保这个函数只有在每个用户第一次调用它的时候执行,用以创建初始宠物。 判断方式:判断该用户的宠物数是否为 0
- 创建新文件 AnimalFeeding.sol 文件,在其中创建 AnimalFeeding 合约,继承自 AnimalIncubators。记得设置编译版本与 import。
- 在 AnimalFeeding 合约中,增加进食功能: 当一个宠物进食后,它自身的DNA 将与食物的 DNA 结合在一起,形成一个新的宠物 DNA ,
– 创建一个名为 feedAndGrow 的函数。 包含两个参数:_AnimalId(uint)、_targetDna (uint),分别表示宠物、食物。 设置属性为 public 。
– 要求只有宠物的主人才能给宠物喂食,在函数开始添加一个 require 语句来确保 msg.sender 只能是这个宠物的主人。
– 声明一个名为 myAnimal 数据类型为 Animal 的本地变量(这是一个storage 型的指针),将其值设定为在 animals 数组中索引为_AnimalId 所指向的值。 - 完成 feedAndGrow 函数:
– 取得_targetDna 的后 dnaDigits 位
– 生成新的宠物 DNA:计算宠物与食物 DNA 的平均值
– 为宠物添加标识:将新的宠物 DNA 最后两位改为“99”。
– 调用_createAnimal 生成新宠物,新宠物名字为“No-one”。(需要修改_createAnimal 函数属性使对继承可见)。 - 在 AnimalFeeding 合约中增加捕食函数:
function _catchFood(uint _name) internal pure returns (uint) {
uint rand = uint(keccak256(_name));
return rand;
} - 在 AnimalFeeding 合约中,增加进食功能:宠物抓住食物后进食,然后成长为一个新宠物
– 创建一个名为 feedOnFood 的函数。它需要 2 个 uint 类型的参数,
_AnimalId 和_FoodId ,这是一个 public 类型的函数。
– 调用_catchFood 函数,获得一个食物 DNA。
– 调用 feedAndGrow 函数。传入宠物 id 和食物 dna,调用feedAndGrow。
需实现效果
部署 AnimalFeeding 合约,实现以下效果:
• 同一账户只可调用一次 createRandomAnimal
• 以三个用户身份添加 Drogon、Rheagal、Viserion 的宠物
• 让 Drogon 宠物进食成长一次,展示新宠物的主人与 Drogon 相同
实验流程
模拟以太猫的繁殖机制,创建一个宠物成长系统,让宠物可以通过进食进行成长,系统会通过宠物和食物的 DNA 计算出新宠物的 DNA。
部署 AnimalFeeding 合约,实现以下效果:
-
同一账户只可调用一次 createRandomAnimal。
-
以三个用户身份添加 Drogon、Rheagal、Viserion 的宠物。
-
让 Drogon 宠物进食成长一次,展示新宠物的主人与 Drogon 相同。
实验流程如下:
- 首先使用地址给宠物指定“主人”,为了存储宠物的所有权,先创建两个映射:一个记录宠物拥有者的地址,另一个记录某地址所拥有宠物的数量。
- 修改_createAnimal 函数来使用映射。
- 在 createRandomAnimal 开头使用 require 来确保这个函数只有在每个用户第一次调用它的时候执行,用以创建初始宠物。
- 创建新文件 AnimalFeeding.sol 文件,在其中创建 AnimalFeeding 合约,继承自AnimalIncubators。
- 在 AnimalFeeding 合约中,增加进食功能: 当一个宠物进食后,它自身的DNA 将与食物的 DNA 结合在一起,形成一个新的宠物 DNA。
- 完成 feedAndGrow 函数。
- 在 AnimalFeeding 合约中增加捕食函数。
- 在 AnimalFeeding 合约中,增加进食功能:宠物抓住食物后进食,然后成长为一个新宠物。
源代码
AnimalIncubators.sol
pragma solidity ^0.4.26;
contract AnimalIncubators{
uint dnaDigits=16;
uint dnaLength=10**16;
struct Animal{
string name;
uint dna;
}
mapping(uint=>address) public AnimalToOwner;
mapping(address=>uint) ownerAnimalCount;
Animal[] public animals;
event NewAnimal(uint AnimalID,string name,uint dna);
function _createAnimal(string _name,uint _dna) internal{
animals.push(Animal(_name,_dna));
uint _animalID=animals.length-1;
AnimalToOwner[_animalID]=msg.sender;
ownerAnimalCount[msg.sender]+=1;
NewAnimal(_animalID,_name,_dna);
}
function _generateRandomDna(string _str)private view returns(uint){
return uint(keccak256(_str))%dnaLength;
}
function createRandomAnimal(string _name) public{
require(ownerAnimalCount[msg.sender]==0);
uint randDna=_generateRandomDna(_name);
_createAnimal(_name,randDna);
}
}
AnimalFeeding.sol
pragma solidity ^0.4.26;
import "./AnimalIncubators.sol";
contract AnimalFeeding is AnimalIncubators{
function feedAndGrow(uint _AnimalId,uint _targetDna)public{
require(keccak256(msg.sender)==keccak256(AnimalToOwner[_AnimalId]));
Animal storage myAnimal=animals[_AnimalId];
uint _tarDna=_targetDna%dnaLength;
uint _petDna=uint((myAnimal.dna+_tarDna)/2);
uint _newDna=(_petDna/100)*100+99;
_createAnimal("No-one",_newDna);
}
function _catchFood(uint _name) internal pure returns(uint){
uint rand=uint(keccak256(_name));
return rand;
}
function feedOnFood(uint _AnimalId,uint _FoodId) public{
uint _foodDna=_catchFood(_FoodId);
feedAndGrow(_AnimalId,_foodDna);
}
}
实验结果截图
创建宠物Drogon,运行正常:
在同一账户创建宠物Rheagal,发生了报错:
由此可说明同一账户只可调用一次 createRandomAnimal。
创建一个新的账户,创建宠物Rheagal:
再创建一个新的账户,创建宠物Viserion:
返回第一个账户,查询宠物主人地址:
对宠物Drogon进行一次投喂,得到新宠物,再次查询宠物主人地址:
可以看到新宠物主人的地址没有发生变化,因此新宠物的主人与Drogon相同。
实验5-3:Solidity 高阶理论
Solidity 很像 JavaScript,但是以太坊上的合约与普通的程序最大的区别就是以太
坊上的合约代码一旦上链就无法更改,即使合约出现 bug,也无法进行修改,之恶
能放弃这个合约让用户去使用一个新的被修复过的合约,虽然这可能会造成使用上
的不便,但这也是智能合约的优势之一,一旦你的代码上链,就无法被他人恶意篡
改,其他人调用也只能以预设的逻辑一直执行下去。
实验内容
- 将 Ownable 代码复制一份到新文件 ownable.sol 中,让 AnimalIncubators 作为子类继承 Ownable。
- 给 createRandomAnimal 函数添加 onlyOwner,再用不同的账户进行调用,看看有什么区别,完成后再删掉它,毕竟其他玩家也要调用。
- 在 AnimalIncubators.sol 中给宠物添 2 个新属性: level(uint32)和readyTime(uint32) - 一个是等级,一个是进食的冷却时间。
- 给 DApp 添加一个“冷却周期”的设定,让宠物两次进食之间必须等待 1min。
– 声明一个名为 cooldownTime 的 uint,并将其设置为 1 分钟
– 修改_createAnimal 中添加。
注: now 返回类型 uint256 ,需要类型转换。
再来到 AnimalFeeding.sol 的 feedAndGrow 函数:
– 修改可见性 internal 以保障合约安全。
– 在_targetDna 计算前,检查该宠物是否已经冷却完毕。
– 在函数结束时重新设置宠物冷却周期,以表示其捕食行为重新进入冷却。 - 接下来撰写一个属于宠物自己的函数修饰符,让宠物能够在达到一定水平后获得特殊能力:
– 创建一个新的文件 AnimalHelper.sol, 定义合约 AnimalHelper 继承自 nimalFeeding
– 创建一个名为 aboveLevel 的 modifier,它接收 2 个参数, _level(uint) 以及 _AnimalId (uint)。
– 函数逻辑确保宠物 Animals[_AnimalId].level 大于或等于 _level。
– 修饰符的最后一行为 _;,表示修饰符调用结束后返回,并执行调用函数余下的部分。 - 添加一些使用 aboveLevel 修饰符的函数来作为达到 level 的奖励。激励玩家们去升级他们的宠物:
– 创建一个名为 changeName 的函数。它接收 2 个参数:_AnimalId(uint 类型)以及 _newName(string 类型),可见性为 external。
它带有一个 aboveLevel 修饰符,调用的时候通过 _level 参数传入 2,当然,别忘了同时传 _AnimalId 参数。 在函数中使用 require 检查msg.sender 是否是宠物主人,如果是则将宠物名改为_newName
– 在 changeName 下创建另一个名为 changeDna 的函数。它的定义和内容几乎和 changeName 相同,不过它第二个参数是 _newDna(uint 类型),在修饰符 aboveLevel 的 _level 参数中传递 20 。在函数中可以把宠物的 dna 设置为 _newDna 。 - 定义一个新函数 getAnimalsByOwner 来获取某个玩家的所有宠物:
– 函数有一个参数_owner(address),声明为 external view 属性,返回一个 uint 数组。
– 声明一个名为 result 的 uint [] memory (内存变量数组) ,其长度为该_owner 拥有的宠物数量。
– 使用 for 循环遍历 Animals 数组,将主人为_owner 的宠物添加入result
– 返回 result
这样 getAnimalsByOwner 不花费任何 gas。
需实现效果
• 展示宠物的冷却时间。
• 成功实现 getAnimalsByOwner 函数。
实验流程
为DApp 添加一个“冷却周期”的设定,让宠物两次进食之间必须等待 1min。并成功实现getAnimalsByOwner 函数。
- 将 Ownable 代码复制一份到新文件 ownable.sol 中,让 AnimalIncubators 作为子类继承 Ownable。
- 给 createRandomAnimal 函数添加 onlyOwner,用不同的账户进行调用,观察区别,最后删除。
- 在 AnimalIncubators.sol 中给宠物添 2 个新属性: level(uint32)和 readyTime(uint32)。
- 给 DApp 添加一个“冷却周期”的设定,让宠物两次进食之间必须等待 1min。
- 撰写一个属于宠物自己的函数修饰符,让宠物能够在达到一定水平后获得特殊能力。
- 添加一些使用 aboveLevel 修饰符的函数来作为达到 level 的奖励。激励玩家们升级宠物。
- 定义一个新函数 getAnimalsByOwner 来获取某个玩家的所有宠物。
源代码
Ownable.sol
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic a
uthorization control
* functions, this simplifies the implementation of "user permissions".
*/
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;
}
}
AnimalIncubators.sol
pragma solidity ^0.4.26;
import "./ownable.sol";
contract AnimalIncubators is Ownable{
uint dnaDigits=16;
uint dnaLength=10**16;
uint32 cooldownTime=60;
struct Animal{
string name;
uint dna;
uint32 level;
uint32 readyTime;
}
mapping(uint=>address) public AnimalToOwner;
mapping(address=>uint) ownerAnimalCount;
Animal[] public animals;
event NewAnimal(uint AnimalID,string name,uint dna);
function _createAnimal(string _name,uint _dna) internal{
animals.push(Animal(_name,_dna,0,uint32(now)));
uint _animalID=animals.length-1;
AnimalToOwner[_animalID]=msg.sender;
ownerAnimalCount[msg.sender]+=1;
NewAnimal(_animalID,_name,_dna);
}
function _generateRandomDna(string _str)private view returns(uint){
return uint(keccak256(_str))%dnaLength;
}
function createRandomAnimal(string _name) public{
require(ownerAnimalCount[msg.sender]==0);
uint randDna=_generateRandomDna(_name);
_createAnimal(_name,randDna);
}
}
AnimalFeeding.sol
pragma solidity ^0.4.26;
import "./AnimalIncubators.sol";
contract AnimalFeeding is AnimalIncubators{
function feedAndGrow(uint _AnimalId,uint _targetDna)internal{
require(keccak256(msg.sender)==keccak256(AnimalToOwner[_AnimalId]));
Animal storage myAnimal=animals[_AnimalId];
require(now>=myAnimal.readyTime);
uint _tarDna=_targetDna%dnaLength;
uint _petDna=uint((myAnimal.dna+_tarDna)/2);
uint _newDna=(_petDna/100)*100+99;
_createAnimal("No-one",_newDna);
myAnimal.readyTime=uint32(now)+cooldownTime;
}
function _catchFood(uint _name) internal pure returns(uint){
uint rand=uint(keccak256(_name));
return rand;
}
function feedOnFood(uint _AnimalId,uint _FoodId) public{
uint _foodDna=_catchFood(_FoodId);
feedAndGrow(_AnimalId,_foodDna);
}
}
AnimalHelper.sol
pragma solidity ^0.4.26;
import "./AnimalFeeding.sol";
contract AnimalHelper is AnimalFeeding{
modifier aboveLevel(uint _level,uint _AnimalId){
require(animals[_AnimalId].level>=_level);
_;
}
function changeName(uint _AnimalId,string _newName) external aboveLevel(2,_AnimalId){
require(keccak256(msg.sender)==keccak256(AnimalToOwner[_AnimalId]));
animals[_AnimalId].name=_newName;
}
function changeDna(uint _AnimalId,uint _newDna) external aboveLevel(20,_AnimalId){
require(keccak256(msg.sender)==keccak256(AnimalToOwner[_AnimalId]));
animals[_AnimalId].dna=_newDna;
}
function getAnimalsByOwner(address _owner) external view returns (uint[]){
uint[] memory result=new uint[](ownerAnimalCount[_owner]);
uint cnt=0;
for (uint i=1;i<animals.length;i++){
if(keccak256(AnimalToOwner[i])==keccak256(_owner)){
result[cnt]=i;
cnt++;
}
}
return result;
}
}
实验结果截图
新建一只宠物Drogon,第一次投喂进行,中断没有发生报错:
紧接着再次投喂,终端发生报错,执行失败:
getAnimalsByOwner 函数的源码在上述AnimalHelper.sol文件中。
选取一个宠物主人的地址,用getAnimalsByOwner函数进行执行:
实验5-4:solidity 高阶篇
实验内容
- 在 AnimalHelper 中,添加支付系统——宠物主人可以通过支付 ETH(对,氪金)的方式来强化他们的宠物。这些支付的 ETH 将存储在你拥有的合约中,向你展示如何通过你的合约赚钱。
– 定义一个名为 powerUpFee 的 uint 类型变量, 将值设定为 0.001 ether。
– 定义一个名为 powerUp 的函数。 它将接收一个 uint 参数 _animalId。
函数记得修饰 external 以及 payable 属性。
– 这个函数首先应该 require 确保 msg.value 等于 powerUpFee。
– 函数将增加指定宠物的 level 属性: animals[_animalId].level++。
需实现效果:成功升级,并改名。 - 在 AnimalHelper 中,添加提现系统
– 创建一个 withdraw 函数,参考上面的 GetPaid 样例。
– 以太的价格在过去几年内不停地波动,所以我们应该再创建一个函数,允许我们以合约拥有者的身份来设置 powerUpFee。
a. 创建一个函数,名为 setPowerUpFee, 其接收一个参数 uint_fee,是 external 并标记为仅 owner 可用。
b. 这个函数会将 powerUpFee 等于 _fee。
需实现效果:实现一次提现操作。 - 实现战斗升级系统
新建一个文件 AnimalAttack.sol,在其中新建一个继承自 AnimalHelper 的合约 AnimalAttack,在这之下编辑新的合约的主要部分。
需实现效果:
• 你选择一只自己的宠物,然后选择一个对手的宠物去战斗。
• 如果你是战斗发起方(先手),你将有 75%的几率获胜,防守方将有 25%的几率获胜。
• 每一只宠物(攻守双方)都会有一个 winCount 和一个 lossCount,用来记录该宠物的战斗结果。
• 若发起方获胜,这只宠物的 level 将增加。
• 如果攻击方失败,除了失败次数将加一外,什么都不会发生。
• 无论输赢,当前宠物的战斗冷却时间都将被重置(很显然,饿了嘛)。
实验流程
- 在 AnimalHelper 中,添加支付系统——宠物主人可以通过支付 ETH 的方式来强化他们的宠物。
- 在 AnimalHelper 中,添加提现系统。
- 实现战斗升级系统。
- 选择一只自己的宠物与一个对手的宠物去战斗。
源代码
AnimalIncubators.sol
pragma solidity ^0.4.26;
import "./ownable.sol";
contract AnimalIncubators is Ownable{
uint dnaDigits=16;
uint dnaLength=10**16;
uint32 cooldownTime=60;
struct Animal{
string name;
uint dna;
uint32 level;
uint32 readyTime;
uint winCount;
uint lossCount;
}
mapping(uint=>address) public AnimalToOwner;
mapping(address=>uint) ownerAnimalCount;
Animal[] public animals;
event NewAnimal(uint AnimalID,string name,uint dna);
function _createAnimal(string _name,uint _dna) internal{
animals.push(Animal(_name,_dna,0,uint32(now),0,0));
uint _animalID=animals.length-1;
AnimalToOwner[_animalID]=msg.sender;
ownerAnimalCount[msg.sender]+=1;
NewAnimal(_animalID,_name,_dna);
}
function _generateRandomDna(string _str)private view returns(uint){
return uint(keccak256(_str))%dnaLength;
}
function createRandomAnimal(string _name) public{
require(ownerAnimalCount[msg.sender]==0);
uint randDna=_generateRandomDna(_name);
_createAnimal(_name,randDna);
}
}
AnimalHelper.sol
pragma solidity ^0.4.26;
import "./AnimalFeeding.sol";
contract AnimalHelper is AnimalFeeding{
uint powerUpFee=0.001 ether;
modifier aboveLevel(uint _level,uint _AnimalId){
require(animals[_AnimalId].level>=_level);
_;
}
function changeName(uint _AnimalId,string _newName) external aboveLevel(2,_AnimalId){
require(keccak256(msg.sender)==keccak256(AnimalToOwner[_AnimalId]));
animals[_AnimalId].name=_newName;
}
function changeDna(uint _AnimalId,uint _newDna) external aboveLevel(20,_AnimalId){
require(keccak256(msg.sender)==keccak256(AnimalToOwner[_AnimalId]));
animals[_AnimalId].dna=_newDna;
}
function getAnimalsByOwner(address _owner) external view returns (uint[]){
uint[] memory result=new uint[](ownerAnimalCount[_owner]);
uint cnt=0;
for (uint i=1;i<animals.length;i++){
if(keccak256(AnimalToOwner[i])==keccak256(_owner)){
result[cnt]=i;
cnt++;
}
}
return result;
}
function powerUp(uint _animalId) external payable{
require(msg.value==powerUpFee);
animals[_animalId].level++;
}
function withdraw() external onlyOwner{
owner.transfer(this.balance);
}
function setPowerUpFee(uint _fee) external onlyOwner{
powerUpFee=_fee;
}
}
AnimalAttack.sol
pragma solidity ^0.4.26;
import "./AnimalHelper.sol";
contract AnimalAttack is AnimalHelper{
uint randNonce=0;
function winProbability() public returns(bool){
uint random=uint(keccak256(now,msg.sender,randNonce))%100;
randNonce++;
return (random<75);
}
function fight(uint _attackAnimalId,uint _defenceAnimalId) external{
if(winProbability()){
animals[_attackAnimalId].level++;
animals[_attackAnimalId].winCount++;
animals[_defenceAnimalId].lossCount++;
}
else{
animals[_attackAnimalId].lossCount++;
animals[_defenceAnimalId].winCount++;
}
animals[_attackAnimalId].readyTime=uint32(now);
animals[_defenceAnimalId].readyTime=uint32(now);
}
}
实验结果截图
创建两只宠物进行战斗,函数成功执行:
标签:宠物,dna,name,AnimalId,Solidity,uint,IDE,合约,函数 From: https://www.cnblogs.com/Silverplan/p/17977122