【蚂蚁链学习3】蚂蚁搬家(蚂蚁链智能合约实战solidity)

【蚂蚁链学习3】蚂蚁搬家(蚂蚁链智能合约实战solidity),第1张

文章目录 第一章 生成蚂蚁的房子实战 第二章 mapping+identityMapping(映射)Identity(标识)实战 第三章 msg.sender全局函数实战 第四章 require实战 第五章 增加一些属性实战 第六章 Storage与Memory实战
接下来我们需要给我们的小蚂蚁建造一个属于他的小窝,让小蚂蚁有个自己的家。

第一章 生成蚂蚁的房子

小蚂蚁出生了,快来给它建造一所房子,给小蚂蚁一个安心的家。

实战

参照上面生成蚂蚁的例子,我们还需要给蚂蚁生成房子。

定义一个 事件 叫做 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];
  }
  
}

欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/zaji/925333.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存