接下来我们需要给我们的小蚂蚁建造一个属于他的小窝,让小蚂蚁有个自己的家。 第一章 生成蚂蚁的房子
小蚂蚁出生了,快来给它建造一所房子,给小蚂蚁一个安心的家。
实战参照上面生成蚂蚁的例子,我们还需要给蚂蚁生成房子。
定义一个 事件 叫做 NewHouse。 它有4个参数: houseId (uint) 带indexed属性, name (string), existGoods (uint),和 maxGoods (uint)。建立一个struct 命名为 House。我们的 House 结构体有三个属性: name (类型为 string), existGoods (类型为 uint),和 maxGoods (类型为 uint)。创建一个数据类型为 House 的结构体数组,用 public 修饰,命名为:houses。建立一个函数,命名为createHouse。它有三个参数: _houseName (类型为string), _existGoods (类型为uint),和_maxGoods (类型为uint)。参照蚂蚁的例子,定义房子id。 声明一个变量 houseId,数据类型是 uint。在下一行中把它用到 NewHouse 事件中。pragma solidity ^0.4.20;
contract AntFamily {
event NewAnt(uint indexed antId, string name, uint dna);
event NewHouse(uint indexed houseId, string name, uint existGoods, uint maxGoods);
uint dnaDigits = 12;
uint dnaModulus = 10 ** dnaDigits;
struct Ant {
string name;
uint dna;
}
struct House {
string name;
uint existGoods;
uint maxGoods;
}
Ant[] public ants;
House[] public houses;
function createAnt(string _name, uint _dna) {
uint id = ants.push(Ant(_name, _dna)) - 1;
emit NewAnt(id, _name, _dna);
}
function createRandomAnt(string _name) {
uint rand = uint(keccak256(_name));
uint randDna = rand % dnaModulus;
createAnt(_name, randDna);
}
function createHouse(string _houseName, uint _existGoods, uint _maxGoods) {
uint houseId = houses.push(House(_houseName, _existGoods, _maxGoods)) - 1;
emit NewHouse(houseId, _houseName, _existGoods, _maxGoods);
}
}
第二章 mapping+identity
Mapping(映射)
在之前的课程中,我们学习了结构体和数组 。映射是另一种在Solidity中存储数据的方法,使用关键字mapping来声明。
映射是这样定义的:
//可以用来通过userId 存储/查找的用户名
mapping (uint => string) userIdToName;
映射本质上是存储和查找数据所用的键-值对。我们可以直接通过键来查询该键对应的值。在上面的例子中,键是一个uint,值是一个 string。
注:映射中键必须是唯一的,而值是可以重复的。
Identity(标识)蚂蚁链平台中,solidity合约使用identity替代官方solidity的address关键字。identity表示合约地址或账户地址,均为32字节,而官方solidity中address表示的地址是20字节。
//定义了一个identity类型
identity owner;
实战
为了存储蚂蚁和房子的所有权,我们会使用到三个映射:一个记录蚂蚁拥有者的地址,一个记录某地址所拥有蚂蚁的数量,最后一个记录房子拥有者的地址。
创建一个叫做 antToOwner 的映射。其键是一个uint(我们将根据它的 id 存储和查找蚂蚁),值为 identity。映射属性为public。创建一个名为 ownerAntCount 的映射,其中键是 identity,值是 uint。创建一个叫做 houseToOwner 的映射。其键是一个uint(我们将根据它的 id 存储和查找房子),值为 identity。pragma solidity ^0.4.20;
contract AntFamily {
event NewAnt(uint indexed antId, string name, uint dna);
event NewHouse(uint indexed houseId, string name, uint existGoods, uint maxGoods);
uint dnaDigits = 12;
uint dnaModulus = 10 ** dnaDigits;
struct Ant {
string name;
uint dna;
}
struct House {
string name;
uint existGoods;
uint maxGoods;
}
Ant[] public ants;
House[] public houses;
mapping (uint => identity) public antToOwner;
mapping (identity => uint) ownerAntCount;
mapping (uint => identity) houseToOwner;
function createAnt(string _name, uint _dna) {
uint id = ants.push(Ant(_name, _dna)) - 1;
emit NewAnt(id, _name, _dna);
}
function createRandomAnt(string _name) {
uint rand = uint(keccak256(_name));
uint randDna = rand % dnaModulus;
createAnt(_name, randDna);
}
function createHouse(string _houseName, uint _existGoods, uint _maxGoods) {
uint houseId = houses.push(House(_houseName, _existGoods, _maxGoods)) - 1;
emit NewHouse(houseId, _houseName, _existGoods, _maxGoods);
}
}
第三章 msg.sender全局函数
蚂蚁链平台提供一些全局接口函数可以直接使用,其中一个就是 msg.sender,它指的是交易的发送方,会返回一个identity类型。
以下是获取 msg.sender的一个例子:
function getSender() public returns (identity) {
// 获取调用者
return msg.sender;
}
注:类似的接口函数还有例如block.number获取当前块高,更多全局接口函数可以参考蚂蚁链开发者知识库。
实战我们来修改 createAnt 方法 和 createHouse 方法,将蚂蚁和房子分配给函数调用者吧。
1.在得到新的蚂蚁 id 后,更新 antToOwner 映射,在 id 下面存入 msg.sender。
2.我们为这个 msg.sender 名下的 ownerAntCount 加 1。
跟在 JavaScript 中一样, 在 Solidity 中你也可以用 ++ 使 uint 递增。
uint number = 0;
number++;
// `number` 现在是 `1`了
3.在得到新的房子 id 后,更新 houseToOwner 映射,在 id 下面存入 msg.sender。修改三行代码即可。
pragma solidity ^0.4.20;
contract AntFamily {
event NewAnt(uint indexed antId, string name, uint dna);
event NewHouse(uint indexed houseId, string name, uint existGoods, uint maxGoods);
uint dnaDigits = 12;
uint dnaModulus = 10 ** dnaDigits;
struct Ant {
string name;
uint dna;
}
struct House {
string name;
uint existGoods;
uint maxGoods;
}
Ant[] public ants;
House[] public houses;
mapping (uint => identity) public antToOwner;
mapping (identity => uint) ownerAntCount;
mapping (uint => identity) houseToOwner;
function createAnt(string _name, uint _dna) {
uint id = ants.push(Ant(_name, _dna)) - 1;
antToOwner[id] = msg.sender;
ownerAntCount[msg.sender]++;
emit NewAnt(id, _name, _dna);
}
function createRandomAnt(string _name) {
uint rand = uint(keccak256(_name));
uint randDna = rand % dnaModulus;
createAnt(_name, randDna);
}
function createHouse(string _houseName, uint _existGoods, uint _maxGoods) {
uint houseId = houses.push(House(_houseName, _existGoods, _maxGoods)) - 1;
houseToOwner[houseId] = msg.sender;
emit NewHouse(houseId, _houseName, _existGoods, _maxGoods);
}
}
第四章 require
在之前的章节中,我们已经让用户通过调用createRandomAnt函数来创建新的蚂蚁。但是,如果用户能持续调用这个函数来创建出无限多个蚂蚁,这游戏就太没意思了!
于是,我们作出以下限定:每个玩家只能调用一次这个函数。 这样一来,新玩家可以在刚开始玩游戏时通过调用它为其创建初始蚂蚁。
我们怎样才能限定每个玩家只能调用一次这个函数呢?
答案是使用require。 require使得函数在执行过程中,当不满足某些条件时抛出错误,并停止执行:
function sayHiToBob(string _name) public returns (string) {
// 比较 _name 是否等于 "Bob". 如果不成立,抛出异常并终止程序
require(keccak256(_name) == keccak256("Bob"));
// 如果返回 true, 运行如下语句
return "Hi!";
}
如果你这样调用函数 sayHiToBob(“Bob”),它会返回“Hi!”。而如果你调用的时候使用了其他参数,它则会抛出错误并停止执行。
因此,在调用一个函数之前,用 require 验证前置条件是非常有必要的。
实战在我们的蚂蚁搬家游戏中,我们不希望用户通过反复调用 createRandomAnt 来创建无限多个蚂蚁 。
我们使用 require 来确保这个函数只有在每个用户第一次调用它的时候执行,用以创建唯一的一个蚂蚁。
在 createRandomAnt 的前面放置 require 语句。 使得函数先检查 ownerAntCount [msg.sender] 的值为 0 ,不然就抛出一个错误,输出错误信息为 “只能创建一只蚂蚁”。注意:在 Solidity 中,关键词放置的顺序并不重要
虽然参数的两个位置是等效的。 但是,由于我们的答案检查器比较呆板,它只能认定其中一个为正确答案于是在这里,我们就约定把ownerAntCount [msg.sender]放前面吧pragma solidity ^0.4.20;
contract AntFamily {
event NewAnt(uint indexed antId, string name, uint dna);
event NewHouse(uint indexed houseId, string name, uint existGoods, uint maxGoods);
uint dnaDigits = 12;
uint dnaModulus = 10 ** dnaDigits;
struct Ant {
string name;
uint dna;
}
struct House {
string name;
uint existGoods;
uint maxGoods;
}
Ant[] public ants;
House[] public houses;
mapping (uint => identity) public antToOwner;
mapping (identity => uint) ownerAntCount;
mapping (uint => identity) houseToOwner;
function createAnt(string _name, uint _dna) {
uint id = ants.push(Ant(_name, _dna)) - 1;
antToOwner[id] = msg.sender;
ownerAntCount[msg.sender]++;
emit NewAnt(id, _name, _dna);
}
function createRandomAnt(string _name) {
require(ownerAntCount[msg.sender] == 0, "只能创建一只蚂蚁");
uint rand = uint(keccak256(_name));
uint randDna = rand % dnaModulus;
createAnt(_name, randDna);
}
function createHouse(string _houseName, uint _existGoods, uint _maxGoods) {
uint houseId = houses.push(House(_houseName, _existGoods, _maxGoods)) - 1;
houseToOwner[houseId] = msg.sender;
emit NewHouse(houseId, _houseName, _existGoods, _maxGoods);
}
}
第五章 增加一些属性
我们的小蚂蚁已经有 name 和 DNA 的属性了,现在让我们给它增加一些新的属性吧。
实战我们来给蚂蚁添2个新功能:level 和 moveCount。
为 Ant 结构体 添加两个属性:level(uint32)和 moveCount (uint32) 。因为希望同类型数据打成一个包,所以把它们放在结构体的末尾。因为我们给 Ant 结构体中添加 level 和 moveCount 两个参数,现在创建一个新的 Ant 结构体时,需要修改 createAnt(),在其中把新旧参数都初始化一下。修改 ants.push 那一行, 添加加2个参数:1(表示当前的 level )0(表示当前搬运的次数)。
pragma solidity ^0.4.20;
contract AntFamily {
event NewAnt(uint indexed antId, string name, uint dna);
event NewHouse(uint indexed houseId, string name, uint existGoods, uint maxGoods);
uint dnaDigits = 12;
uint dnaModulus = 10 ** dnaDigits;
struct Ant {
string name;
uint dna;
uint level;
uint moveCount;
}
struct House {
string name;
uint existGoods;
uint maxGoods;
}
Ant[] public ants;
House[] public houses;
mapping (uint => identity) public antToOwner;
mapping (identity => uint) ownerAntCount;
mapping (uint => identity) houseToOwner;
function createAnt(string _name, uint _dna) {
uint id = ants.push(Ant(_name, _dna, 1, 0)) - 1;
antToOwner[id] = msg.sender;
ownerAntCount[msg.sender]++;
emit NewAnt(id, _name, _dna);
}
function createRandomAnt(string _name) {
require(ownerAntCount[msg.sender] == 0, "只能创建一只蚂蚁");
uint rand = uint(keccak256(_name));
uint randDna = rand % dnaModulus;
createAnt(_name, randDna);
}
function createHouse(string _houseName, uint _existGoods, uint _maxGoods) {
uint houseId = houses.push(House(_houseName, _existGoods, _maxGoods)) - 1;
houseToOwner[houseId] = msg.sender;
emit NewHouse(houseId, _houseName, _existGoods, _maxGoods);
}
}
第六章 Storage与Memory
在Solidity中,有两个地方可以存储变量 —— storage 或 memory。
storage变量是指永久存储在区块链中的变量。 memory变量则是临时的,当外部函数对某合约调用完成时,memory变量即被移除。 你可以把它想象成存储在你电脑的硬盘或是RAM中数据的关系。
大多数时候你都用不到这些关键字,默认情况下Solidity会自动处理它们。 状态变量(在函数之外声明的变量)默认为storage形式,并永久写入区块链;而在函数内部声明的变量是memory型的,它们会在函数调用结束后消失。
然而也有一些情况下,你需要手动声明存储类型,主要用于处理函数内的结构体和数组时:
contract BookStore {
struct Book {
string name;
string status;
}
Book[] books;
function buyBook(uint _index) public {
// Book myBook = books[_index];
// 上面的例子看上去很直接,不过Solidity将会给出警告
// 告诉你在这里应该明确定义 `storage` 或者 `memory`。
// 所以你应该明确定义 `storage`:
Book storage myBook = books[_index];
myBook.status = "solded";
// 这将永久把 `books[_index]` 变为区块链上的存储
// 如果你只想要一个副本,可以使用`memory`:
Book memory anotherBook = books[_index + 1];
// 这样 `anotherBook` 就仅仅是一个内存里的副本了
anotherBook.status = "solded!";
// 这样仅仅修改临时变量,对 `books[_index + 1]` 没有任何影响
// 不过你可以这样做:
books[_index + 1] = anotherBook;
// 如果你想把副本的改动保存回区块链存储
}
}
如果你还没有完全理解究竟应该使用哪一个,也不用担心 —— 在本教程中,我们将告诉你何时使用 storage 或是 memory,并且当你不得不使用到这些关键字的时候,Solidity 编译器也发警示提醒你的。
现在,只要知道在某些场合下也需要你显式地声明 storage 或 memory就够了!
实战 创建一个名为 moveGoods 的函数。 使用三个参数:_antId( uint类型 )_originHouseId( uint类型 ) 和_targetHouseId (也是 uint 类型)。我们不希望别人用我们的蚂蚁去搬家。 首先,我们确保对自己蚂蚁的所有权。 通过添加一个require 语句来确保 msg.sender 只能是这个蚂蚁的主人(类似于我们在 createRandomAnt 函数中做过的那样),如果不是这个蚂蚁的主人就抛出一个错误,输出错误信息为 “只能用自己的蚂蚁搬东西”。注意:同样,因为我们的答案检查器比较呆萌,只认识把 msg.sender 放在前面的答案,如果你切换了参数的顺序,它就不认得了。 但你正常编码时,如何安排参数顺序都是正确的。
同理,我们不希望把东西搬去别人的房子,也不希望把别人房子的东西搬到我们房子里,所以我们也需要确保对自己房子的所有权。与上一步类似,添加一个require 语句来确保 msg.sender 只能是两个房子的主人,如果不是自己的房子就抛出一个错误,输出错误信息为 “只能给自己家搬东西”。为了获取两个房子的已经搬过的物品数量,我们的函数需要声明名为 originHouse 和 targetHouse 数据类型都为House的本地变量(这是一个 storage 型的指针)。 将其值设定为在 houses 数组中索引为_originHouseId 和 _targetHouseId 所指向的值。pragma solidity ^0.4.20;
contract AntFamily {
event NewAnt(uint indexed antId, string name, uint dna);
event NewHouse(uint indexed houseId, string name, uint existGoods, uint maxGoods);
uint dnaDigits = 12;
uint dnaModulus = 10 ** dnaDigits;
struct Ant {
string name;
uint dna;
uint level;
uint moveCount;
}
struct House {
string name;
uint existGoods;
uint maxGoods;
}
Ant[] public ants;
House[] public houses;
mapping (uint => identity) public antToOwner;
mapping (identity => uint) ownerAntCount;
mapping (uint => identity) houseToOwner;
function createAnt(string _name, uint _dna) {
uint id = ants.push(Ant(_name, _dna, 1, 0)) - 1;
antToOwner[id] = msg.sender;
ownerAntCount[msg.sender]++;
emit NewAnt(id, _name, _dna);
}
function createRandomAnt(string _name) {
require(ownerAntCount[msg.sender] == 0, "只能创建一只蚂蚁");
uint rand = uint(keccak256(_name));
uint randDna = rand % dnaModulus;
createAnt(_name, randDna);
}
function createHouse(string _houseName, uint _existGoods, uint _maxGoods) {
uint houseId = houses.push(House(_houseName, _existGoods, _maxGoods)) - 1;
houseToOwner[houseId] = msg.sender;
emit NewHouse(houseId, _houseName, _existGoods, _maxGoods);
}
function moveGoods(uint _antId, uint _originHouseId, uint _targetHouseId) {
require(msg.sender == antToOwner[_antId], "只能用自己的蚂蚁搬东西");
require(msg.sender == houseToOwner[_originHouseId], "只能给自己家搬东西");
require(msg.sender == houseToOwner[_targetHouseId], "只能给自己家搬东西");
House storage originHouse = houses[_originHouseId];
House storage targetHouse = houses[_targetHouseId];
}
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)