Solidity与dapp开发学习记录4

2023-10-30

目录

函数修饰符进阶

Payable修饰符

运用

取款 Withdraws

运用

准备好设计僵尸对战

随机数 Random Numbers

通过keccak256生成随机数

此方法容易受到不诚实节点的攻击

如何在以太坊中安全地生成随机数?

运用

僵尸对战

重构公共逻辑

运用

回到攻击函数

僵尸的输赢机制

僵尸获胜机制

僵尸获败机制


函数修饰符进阶

前面学了好多函数修饰符,一下子很难记完,先快速回顾一下吧。

可见性修饰符(控制何时何地可以调用函数):

private意味着它只能从合约内的其他函数调用;

internal类似于private,但也可以由继承自此的合约调用;

external只能在合同外调用;

最后,public可以在内部和外部的任何地方调用。


状态修饰符(告诉我们该函数如何与区块链交互):

view告诉我们,通过运行该函数,不会保存/更改任何数据。

pure告诉我们,该函数不仅不会将任何数据保存到区块链,而且也不会从区块链读取任何数据。如果从合约外部调用这两个函数,它们都不需要花费任何气体来调用(但如果由另一个函数内部调用,它们确实需要花费气体)。
自定义修饰符,例如,前面了解到的:onlyOwner和aboveLevel。对于这些,我们可以定义自定义逻辑来确定它们如何影响函数。

 这些修饰符都可以按如下方式堆叠在函数定义上:

function test() external view onlyOwner anotherModifier { /* ... */ }

 我们将再引入一个函数修饰符:payable。

Payable修饰符

Payable函数是Solidity和以太坊很酷的一部分——它们是一种可以接收以太(币)的特殊类型。

当你在正常的web服务器上调用API函数时,你不能在函数调用的同时发送美元,也不能发送比特币。 

但在以太坊中,由于货币(Ether)、数据(transaction payload)和合约代码本身都存在于以太坊上,因此您可以调用函数并同时向合约付款。

这样就可以用一些非常有趣的逻辑,比如需要向合同支付一定的款项才能执行函数。 

让我们看一个示例:

contract OnlineStore {
  function buySomething() external payable {
    // Check to make sure 0.001 ether was sent to the function call:
    require(msg.value == 0.001 ether);
    // If so, some logic to transfer the digital item to the caller of the function:
    transferThing(msg.sender);
  }
}

msg.value是一种查看以太币被发送到合同中的方式,以太币是一个内置单位。
有人会从web3.js(从DApp的JavaScript前端)调用函数,如下所示:

// Assuming `OnlineStore` points to your contract on Ethereum:
OnlineStore.buySomething({from: web3.eth.defaultAccount, value: web3.utils.toWei(0.001)})

 注意value字段,javascript函数调用指定要发送多少以太(0.001)。如果你把交易想象成一个信封,而你发送给函数调用的参数是你放进去的信的内容,那么添加一个value就像把现金放进信封一样——信和钱一起送到收件人手中。

注意:如果某个功能未标记为payable,并且您试图如上所述向其发送以太币,则该功能将拒绝您的交易。

运用

假设我们的游戏有一个功能,用户可以支付ETH来升级他们的僵尸。ETH将存储在您拥有的合约中-这是一个简单的例子,说明您如何在游戏中赚钱!


contract ZombieHelper is ZombieFeeding {

  // 1. Define levelUpFee here
  uint levelUpFee = 0.001 ether;
  modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _;
  }

  // 2. Insert levelUp function here
  function levelUp(uint _zombieId) external payable{
    require(msg.value == levelUpFee);
    zombies[_zombieId].level++;
  }
  function changeName(uint _zombieId, string calldata _newName) external aboveLevel(2, _zombieId) {
    require(msg.sender == zombieToOwner[_zombieId]);
    zombies[_zombieId].name = _newName;
  }

 


取款 Withdraws

您可以编写一个函数从合约中提取Ether,如下所示:

contract GetPaid is Ownable {
  function withdraw() external onlyOwner {
    address payable _owner = address(uint160(owner()));
    _owner.transfer(address(this).balance);//转换后才可以使用.transfer
  }
}

 需要注意的是,您不能将Ether转移到某个地址,除非该地址属于payable地址类型。但是_owner变量的类型为uint160,这意味着我们必须显式地将其转换为地址payable。

您可以使用转账将资金发送到任何以太坊地址。例如,您可以使用一个函数,如果对方为某个项目多付了费用,则将Ether传输回msg.sender: 

uint itemFee = 0.001 ether;
msg.sender.transfer(msg.value - itemFee);

或者,在与买家和卖家使用的合约中,您可以将卖家的地址保存在存储器中,然后当某人购买他的物品时,将买家支付的费用转移给他:seller.transfer(msg.value)。
这些是让以太坊编程变得非常酷的一些例子——你可以拥有像这样不受任何人控制的去中心化市场。

运用


  // 1. Create withdraw function here
  function withdraw() external onlyOwner{
    address payable _owner = address(uint160(owner()));
    _owner.transfer(address(this).balance);
  }
  // 2. Create setLevelUpFee function here
  function setLevelUpFee(uint _fee) external onlyOwner{
    levelUpFee = _fee;
  }

准备好设计僵尸对战

让我们回顾一下创建新合约的过程。重复才能掌握!
如果您记不住执行这些操作的语法,请查看zombiehelper.sol的语法-但试着不先偷看来测试你的知识。
在文件顶部声明我们使用的是Solidity version >=0.5.0 <0.6.0。
从zombiehelper.sol导入。
声明从ZombieHelper继承的名为ZombieAttack的新合约,暂时将合同正文留空。

pragma solidity >=0.5.0 <0.6.0;

import "./zombiehelper.sol";

contract ZombieAttack is ZombieHelper{

}

随机数 Random Numbers

让我们先搞清楚一下僵尸对战的逻辑。

所有好的游戏都需要一定程度的随机性。那么我们如何在Solidity中生成随机数呢?

实际上真正的随机数是不能安全地生成的。

通过keccak256生成随机数

Solidity中最好的随机性来源是keccak256哈希函数。

// Generate a random number between 1 and 100:
uint randNonce = 0;
uint random = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % 100;
randNonce++;
uint random2 = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % 100;

这将做的是获取现在的时间戳,即msg.sender和一个递增的nonce(一个只使用一次的数字,因此我们不会使用相同的输入参数运行相同的哈希函数两次)。

然后,它将“打包”输入,并使用keccak将其转换为随机散列。接下来,它将把哈希转换为uint,然后使用%100只取最后2位。这将给我们一个介于0和99之间的完全随机数。 

此方法容易受到不诚实节点的攻击

在以太坊中,当您调用合约上的函数时,事务将广播到网络上的一个或多个节点。然后,网络上的节点收集一堆事务,尝试第一个解决计算密集型数学问题,作为“工作证明”,然后将该组事务及其工作证明(PoW)作为块发布给网络的其他部分。

一旦一个节点解决了PoW,其他节点就停止尝试解决PoW,验证其他节点的事务列表是否有效,然后接受该块并继续尝试解决下一个块。 这使得我们的随机数函数是可利用的。

假设我们有一个掷硬币合约-正面你的钱翻倍,反面你失去一切。假设它使用上述随机函数来确定头部或尾部。(随机>=50为头部,随机<50为尾部)。

如果我正在运行一个节点,我只能将事务发布到我自己的节点,而不能共享它。然后我可以运行硬币翻转功能,看看我是否赢了-如果我输了,选择不在我要解决的下一个区块中包含该交易。我可以无限期地这样做,直到我最终赢得了硬币翻转,解决了下一个区块,并获得了利润。

利用个人节点提前知道自己是否赢了。 

如何在以太坊中安全地生成随机数?

因为所有参与者都可以看到区块链的全部内容,这是一个难题。你可以阅读这个StackOverflow线程来获得一些想法。一个想法是使用预言机从以太坊区块链外部访问随机数函数。

当然,由于网络上数以万计的以太坊节点正在竞争解决下一个区块,我解决下一块的几率极低。我需要花费大量时间或计算资源才能从中获利,但如果回报足够高(比如我有机会在硬币翻转功能上赚取100000000美元),我就值得去攻击。

因此,虽然这种随机数生成在以太坊上并不安全,但在实践中,除非我们的随机函数在线上有很多钱,否则游戏的用户可能没有足够的资源来攻击它。

运用

实现一个随机数函数,我们可以使用它来确定我们的战斗结果,即使它不完全安全。 

pragma solidity >=0.5.0 <0.6.0;

import "./zombiehelper.sol";

contract ZombieAttack is ZombieHelper {
  // Start here
  uint randNonce = 0;
  function randMod(uint _modulus) internal returns(uint){
    randNonce++;
    return uint(keccak256(abi.encodePacked(now,msg.sender,randNonce))) % _modulus;
  }
}

僵尸对战

上面我们已经实现了合约里的随机性,我们可以应用在僵尸对战中计算结果。

我们的僵尸战斗逻辑将如下:
你选择一个僵尸,然后选择对手的僵尸进行攻击。
攻击僵尸有70%的几率获胜。防守的僵尸将有30%的胜算。
所有僵尸(攻击和防御)将有一个winCount和一个lossCount,这将根据战斗的结果而增加。
如果攻击僵尸获胜,它会升级并产生一个新的僵尸。
如果它丢失,则不会发生任何事情(除了它的lossCount递增)。
无论它是赢还是输,攻击僵尸的冷却时间都会被触发。
这是一个需要实现的逻辑,所以我们将在接下来分几部分来实现。 

 先做个小准备

pragma solidity >=0.5.0 <0.6.0;

import "./zombiehelper.sol";

contract ZombieAttack is ZombieHelper {
  uint randNonce = 0;
  // Create attackVictoryProbability here
  uint attackVictoryProbability = 70;
  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % _modulus;
  }

  // Create new function here
  function attack(uint _zombieId,uint _targetId) external{

  }
}

重构公共逻辑

我们不希望别人能使用我们的僵尸来攻击,因此我们需要确保用户真正拥有他们的僵尸,这涉及到一个安全问题。

如何用一个函数来检查调用该函数的人是否是他提供的_zombieId的所有者?

很简单,前面changeName()等函数中有用过的:

require(msg.sender == zombieToOwner[_zombieId]);

这是我们攻击函数所需要的逻辑。由于我们多次使用相同的逻辑,让我们将其移入自己的修饰符中,以清理代码并避免重复。

运用

创建名为ownerOf的modifier(确保是_zombieId的所有者)。它需要一个参数_zombieId(一个uint)。

记得modifier函数体以 _; 结尾。


  // 1. 创建修饰符
  modifier ownerOf(uint _zombieId){
    require(msg.sender==zombieToOwner[_zombieId]);
    _;
  }
  function setKittyContractAddress(address _address) external onlyOwner {
    kittyContract = KittyInterface(_address);
  }

  function _triggerCooldown(Zombie storage _zombie) internal {
    _zombie.readyTime = uint32(now + cooldownTime);
  }

  function _isReady(Zombie storage _zombie) internal view returns (bool) {
      return (_zombie.readyTime <= now);
  }

  // 2. 在函数定义中添加新建的修饰符(别忘了传参_zombieId):
  function feedAndMultiply(uint _zombieId, uint _targetDna, string memory _species) internal ownerOf(_zombieId){
    // 3. 删除下面//这行
    //require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    require(_isReady(myZombie));
    _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);
    _triggerCooldown(myZombie);
  }

后面把其他几个需要修改的函数changeName()、changeDna() 都加个修饰符即可 


回到攻击函数

做好ownerOf修饰符修改后,我们继续写zombieattack.sol中的攻击函数。

pragma solidity >=0.5.0 <0.6.0;

import "./zombiehelper.sol";

contract ZombieAttack is ZombieHelper {
  uint randNonce = 0;
  uint attackVictoryProbability = 70;

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % _modulus;

  }

  // 1. Add modifier here
  function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId){
    // 2. Start function definition here
    Zombie storage myZombie = zombies[_zombieId];
    Zombie storage enemyZombie = zombies[_targetId];
    uint rand = randMod(100);
    //0-100的随机数
  }
}

僵尸的输赢机制

对于我们的僵尸游戏,我们将要跟踪我们的僵尸赢得和输掉了多少场战斗。这样我们就可以在游戏状态下保持“僵尸排行榜”。
我们可以在DApp中以多种方式存储这些数据——以单个映射、排行榜结构或僵尸结构本身的形式。
根据我们打算如何与数据交互,每种方法都有自己的好处和权衡。为了简单起见,我们将把统计数据存储在Zombie结构中,并将其称为winCount和lossCount。

修改我们的Zombie结构,使其具有另外两个属性:
a、 winCount,一个uint16
b、 lossCount,也是一个uint16

注意:请记住,因为我们可以在结构中打包uint,所以我们希望使用我们可以避免溢出的最小uint。uint8太小了,因为2^8=256-如果我们的僵尸每天攻击一次,他们可能会在一年内溢出。但2^16是65536,因此,除非用户连续179年每天都输赢,否则我们在这里应该是安全的。

既然我们在Zombie结构上有了新的属性,我们需要在_createZombie()中更改函数定义。
更改创建僵尸的定义,使其创建每个新的僵尸,0胜0负。


    struct Zombie {
      string name;
      uint dna;
      uint32 level;
      uint32 readyTime;
      // 1. Add new properties here
      uint16 winCount;
      uint16 lossCount;
    }

    Zombie[] public zombies;

    mapping (uint => address) public zombieToOwner;
    mapping (address => uint) ownerZombieCount;

    function _createZombie(string memory _name, uint _dna) internal {
        // 2. Modify new zombie creation here:
        uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime),0,0)) - 1;
        zombieToOwner[id] = msg.sender;
        ownerZombieCount[msg.sender]++;
        emit NewZombie(id, _name, _dna);
    }

僵尸获胜机制

现在我们有了winCount和lossCount,我们可以根据哪个僵尸赢得了战斗来更新它们。

前面我们计算了一个从0到100的随机数。现在让我们使用这个数字来确定谁赢得了比赛,并相应地更新我们的统计数据。

前面已经设置了attackVictoryProbability=70,也就是说随机数取在70以内我们的僵尸就算胜利,这时候设置我的僵尸winCount+1,等级+1,敌方僵尸lossCount+1。

pragma solidity >=0.5.0 <0.6.0;

import "./zombiehelper.sol";

contract ZombieAttack is ZombieHelper {
  uint randNonce = 0;
  uint attackVictoryProbability = 70;

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % _modulus;
  }

  function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId) {
    Zombie storage myZombie = zombies[_zombieId];
    Zombie storage enemyZombie = zombies[_targetId];
    uint rand = randMod(100);
    // Start here
    if(rand <= attackVictoryProbability){
      myZombie.winCount++;
      myZombie.level++;
      enemyZombie.lossCount++;
      feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
        //通过feedAndMultiply运行内部冷却的代码实现我的僵尸冷却,并感染战败僵尸。
    }
  }
}

僵尸获败机制

在我们的游戏中,当僵尸输掉比赛时,他们不会降低level-只是在lossCount加个1,以及他们的冷却时间被触发,因此他们必须等待一天才能再次进攻。

由于没打过敌方僵尸,所以只有冷却没有感染。


    if (rand <= attackVictoryProbability) {
      myZombie.winCount++;
      myZombie.level++;
      enemyZombie.lossCount++;
      feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
    } else{
      // start here
      myZombie.lossCount++;
      enemyZombie.winCount++;
      _triggerCooldown(myZombie);
      }

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Solidity与dapp开发学习记录4 的相关文章

随机推荐

  • 0-1背包问题使用回溯法

    对于0 1 背包问题可以用动态规划算法解决 这里先不说这种方法 只介绍回溯法 0 1背包问题的回溯法解决的解空间是子集树 下面给出最简洁的代码 比较方便理解呢 include
  • SSD固态硬盘的结构和基本工作原理概述

    我们都知道 早期的电脑CPU是可以直接从硬盘上面读取数据进行处理的 随着科技的进步 时代的发展 计算机硬件的发展速度也是极其迅猛 CPU主频的不断提升 从单核到双核 再到多核 CPU的处理速度越来越快 而硬盘的的读写速度已经远远跟不上CPU
  • Elasticsearch报错ValueError: Either ‘hosts‘ or ‘cloud_id‘ must be specified

    这个错误是由于在初始化 Elasticsearch 客户端时未指定有效的主机地址 hosts 或 Cloud ID cloud id 而引起的 Elasticsearch 客户端需要知道连接的 Elasticsearch 实例的位置才能正常
  • MMYOLO框架标注、训练、测试全流程(补充篇)

    前言 MMYOLO框架是一个基于PyTorch和MMDetection的YOLO系列算法开源工具箱 MMYOLO定位为YOLO系列热门开源库以及工业应用核心库 MMYOLO框架Github项目地址 支持的任务 目标检测 旋转目标检测 支持的
  • 【blender建模功能】03 倒角工具

    blender 03 倒角工具 基操 宽度类型 其他参数 倒角问题 顶点倒角 1 基础操作 2 宽度类型 3 其他参数 3 1 材质编号 3 2 平滑 3 2 1 自动光滑 3 2 2 硬化法线 3 3 钳制重叠 3 4 外衔接 内衔接 3
  • UE4联网2——视角同步

    在做完子弹的同步后发现和客户端和服务器的玩家的仰角是不同步的 所以在角色代码中加入tick函数更新玩家的仰角pitch 这里我们需要用到一个变量RemoteViewPitch 这是在pawn中定义的已经复制的公有变量 rpc 值得注意的是它
  • 忽略大小写的字符串比较

    问题描述 一般我们用strcmp可比较两个字符串的大小 比较方法为对两个字符串从前往后逐个字符相比较 按 ASCII 码值大小比较 直到出现不同的字符或遇到 0 为止 如果全部字符都相同 则认为相同 如果出现不相同的字符 则以第一个不相同的
  • vue3引用ElementPlus出错|如何在vue中引用TypeScript

    具体错误 直接套用elementplus官方文档里的模版 报错 Module parse failed Unexpected token You may need an additional loader to handle the res
  • 运放噪声如何计算?

    一 噪声 运放的噪声分为 1 电压噪声en v 2 电流噪声在电阻Rs和R1 R2上产生的等效噪声en i 3 电阻的热噪声enr 总输入噪声计算公式 en in sqrt env 2 eni 2 enr 2 总输出噪声计算公式 en ou
  • [第七届蓝帽杯全国大学生网络安全技能大赛 蓝帽杯 2023]——Web方向部分题 详细Writeup

    Web LovePHP 你真的熟悉PHP吗 源码如下
  • 【C++】C++入门

    目录 一 C 关键字 二 命名空间 2 1命名空间的定义 2 2命名空间的使用 2 2 1加命名空间名称和作用域限定符 2 2 2使用using 将命名空间中某个成员引入 2 2 3使用using namespace将命名空间引入 三 C
  • 【KnowledgeBase】CLIP多模态代码试玩

    文章目录 前言 一 CLIP整体流程简述 二 代码试玩 参考 前言 多模态CLIP的推理部分代码简单试玩一下 致敬大佬的CLIP 论文链接 Learning Transferable Visual Models From Natural L
  • JS中的“&&”与“&”和“

    在JavaScript中 和 是逻辑运算符 和 是位运算符 四个运算符主要区别是运算方法不一样 1 JavaScript中的位运算符 运算方法 两个数值的个位分别相与 同时为1才得1 只要一个为0就为0 举个例子 31 2 结果为2 理由
  • python-报错

    报错 异常名称 描述 BaseException 所有异常的基类 SystemExit 解释器请求退出 KeyboardInterrupt 用户中断执行 通常是输入 C Exception 常规错误的基类 StopIteration 迭代器
  • JS 统计字符

    var str id content value replace r n g n var length t str length
  • 使用Koa2进行Web开发(一)

    这篇文章是我正在进行写作的 新时期的Node js入门 的一部分 Connect Express与Koa 为了更好地理解后面的内容 首先需要梳理一下Node中Web框架的发展历程 Connect 在connect官方网站提供的定义是 Con
  • Python实现贝叶斯优化器(Bayes_opt)优化卷积神经网络回归模型(CNN回归算法)项目实战

    说明 这是一个机器学习实战项目 附带数据 代码 文档 视频讲解 如需数据 代码 文档 视频讲解可以直接到文章最后获取 1 项目背景 贝叶斯优化器 BayesianOptimization 是一种黑盒子优化器 用来寻找最优参数 贝叶斯优化器是
  • 从枚举类型的ordinal()方法说起

    文章背景 本周有一个开发任务涉及到了枚举类型的修改 需要对枚举类型新增一项 在新增的时候我没有加在已有项的最后面 而是在中间随便找了个位置 其实也不是很随便 我是根据语义关联性觉得放在某一项后面比较合适 没想到的是 我的无心之举经造成了大
  • 安装centos 8

    安装centos 8 首先下载centos 8镜像 地址 http mirrors aliyun com centos 8 isos x86 64 这里选择了boot版本 boot版与完整版的区别是体积小 通过网络源安装 下载完成大概有70
  • Solidity与dapp开发学习记录4

    目录 函数修饰符进阶 Payable修饰符 运用 取款 Withdraws 运用 准备好设计僵尸对战 随机数 Random Numbers 通过keccak256生成随机数 此方法容易受到不诚实节点的攻击 如何在以太坊中安全地生成随机数 运