# cryptoZombie 知识点总结

By [0xmind](https://paragraph.com/@0xmind) · 2022-03-13

---

*   声明版本
    

    pragma solidity ^0.4.19;
    
    contract HelloWorld {
    
    }
    

*   声明状态变量
    

    contract Example {
      // 这个无符号整数将会永久的被保存在区块链中
      uint myUnsignedInteger = 100;
    }
    
    // 其中 uint 实际上指 uint256, 又分 uint8/uint16/uint32 等位数
    

*   运算
    

    加法: x + y
    减法: x - y,
    乘法: x * y
    除法: x / y
    取模 / 求余: x % y (例如, 13 % 5 余 3, 因为13除以5，余3)
    乘方 x ** y = x^y
    

*   结构体
    

    struct Person {
      uint age;
      string name;
    }
    

*   数组
    

    // 分为静态数组和动态数组
    
    // 固定长度为2的静态数组:
    uint[2] fixedArray;
    // 固定长度为5的string类型的静态数组:
    string[5] stringArray;
    // 动态数组，长度不固定，可以动态添加元素:
    uint[] dynamicArray;
    
    // 公共数组
    Person[] public people;
    // 其它的合约可以从这个数组读取数据（但不能写入数据），所以这在合约中是一个有用的保存公共数据的模式
    

*   函数
    

    function eatHamburgers(string _name, uint _amount) {
    
    }
    

*   私有 / 公共函数
    

    // 我们合约中的其它函数才能够调用这个函数
    // 继承自父合约的子合约也不可以调用这个函数
    uint[] numbers;
    
    function _addToArray(uint _number) private {
      numbers.push(_number);
    }
    

*   函数更多属性
    

    // 返回值
    string greeting = "What's up dog";
    
    function sayHello() public returns (string) {
      return greeting;
    }
    
    // 修饰符 view 只能读取数据不能更改数据
    function sayHello() public view returns (string) {}
    
    // 修饰符 pure 这个函数甚至都不访问应用里的数据
    function _multiply(uint a, uint b) private pure returns (uint) {
      return a * b;
    }
    

*   Keccak256 和 类型转换
    

    // 散列函数,它用了SHA3版本。一个散列函数基本上就是把一个字符串转换为一个256位的16进制数字。字符串的一个微小变化会引起散列数据极大变化。
    

*   类型转换
    

    uint8 a = 5;
    uint b = 6;
    // 将会抛出错误，因为 a * b 返回 uint, 而不是 uint8:
    uint8 c = a * b;
    // 我们需要将 b 转换为 uint8:
    uint8 c = a * uint8(b);
    

*   事件
    

    // 这里建立事件
    event IntegersAdded(uint x, uint y, uint result);
    
    function add(uint _x, uint _y) public {
      uint result = _x + _y;
      //触发事件，通知app
      IntegersAdded(_x, _y, result);
      return result;
    }
    
    // 前端监听
    YourContract.IntegersAdded(function(error, result) {
      // 干些事
    })
    

*   使用 web3.js
    

    // 下面是调用合约的方式:
    var abi = /* abi是由编译器生成的 */
    var ZombieFactoryContract = web3.eth.contract(abi)
    var contractAddress = /* 发布之后在以太坊上生成的合约地址 */
    var ZombieFactory = ZombieFactoryContract.at(contractAddress)
    // `ZombieFactory` 能访问公共的函数以及事件
    
    // 某个监听文本输入的监听器:
    $("#ourButton").click(function(e) {
      var name = $("#nameInput").val()
      //调用合约的 `createRandomZombie` 函数:
      ZombieFactory.createRandomZombie(name)
    })
    
    // 监听 `NewZombie` 事件, 并且更新UI
    var event = ZombieFactory.NewZombie(function(error, result) {
      if (error) return
      generateZombie(result.zombieId, result.name, result.dna)
    })
    
    // 获取 Zombie 的 dna, 更新图像
    function generateZombie(id, name, dna) {
      let dnaStr = String(dna)
      // 如果dna少于16位,在它前面用0补上
      while (dnaStr.length < 16)
        dnaStr = "0" + dnaStr
    
      let zombieDetails = {
        // 前两位数构成头部.我们可能有7种头部, 所以 % 7
        // 得到的数在0-6,再加上1,数的范围变成1-7
        // 通过这样计算：
        headChoice: dnaStr.substring(0, 2) % 7 + 1，
        // 我们得到的图片名称从head1.png 到 head7.png
    
        // 接下来的两位数构成眼睛, 眼睛变化就对11取模:
        eyeChoice: dnaStr.substring(2, 4) % 11 + 1,
        // 再接下来的两位数构成衣服，衣服变化就对6取模:
        shirtChoice: dnaStr.substring(4, 6) % 6 + 1,
        //最后6位控制颜色. 用css选择器: hue-rotate来更新
        // 360度:
        skinColorChoice: parseInt(dnaStr.substring(6, 8) / 100 * 360),
        eyeColorChoice: parseInt(dnaStr.substring(8, 10) / 100 * 360),
        clothesColorChoice: parseInt(dnaStr.substring(10, 12) / 100 * 360),
        zombieName: name,
        zombieDescription: "A Level 1 CryptoZombie",
      }
      return zombieDetails
    }
    

*   映射（Mapping）和地址（Address）
    

    // 以太坊区块链由 _ account _ (账户)组成
    
    //  _映射_ 是另一种在 Solidity 中存储有组织数据的方法
    映射可以理解为一个包含一个或多个 key-value 键值对的对象
    

*   Msg.sender
    

    全局变量，指的是当前调用者（或智能合约）的 address
    

*   Require
    

    // require 使得函数在执行过程中，当不满足某些条件时抛出错误，并停止执行
    function sayHiToVitalik(string _name) public returns (string) {
      // 比较 _name 是否等于 "Vitalik". 如果不成立，抛出异常并终止程序
      // (敲黑板: Solidity 并不支持原生的字符串比较, 我们只能通过比较
      // 两字符串的 keccak256 哈希值来进行判断)
      require(keccak256(_name) == keccak256("Vitalik"));
      // 如果返回 true, 运行如下语句
      return "Hi!";
    }
    

*   继承（Inheritance）
    

    contract Doge {
      function catchphrase() public returns (string) {
        return "So Wow CryptoDoge";
      }
    }
    
    contract BabyDoge is Doge {
      function anotherCatchphrase() public returns (string) {
        return "Such Moon BabyDoge";
      }
    }
    

*   引入（Import）
    

    import "./someothercontract.sol";
    
    contract newContract is SomeOtherContract {
    
    }
    

*   Storage与Memory
    

    // Storage 变量是指永久存储在区块链中的变量。 
    // Memory 变量则是临时的，当外部函数对某合约调用完成时，内存型变量即被移除。
    

*   internal(内部) 和 external(外部)
    

    // internal 和 private 类似，不过， 如果某个合约继承自其父合约，这个合约即可以访问父合约中定义的“内部”函数。
    
    // external 与public 类似，只不过这些函数只能在合约之外调用 - 它们不能被合约内的其他函数调用。
    

*   与其他合约的交互-接口
    

    // 先定义一个 interface
    // 不使用大括号（{ 和 }）定义函数体，我们单单用分号（;）结束了函数声明
    contract NumberInterface {
      function getNum(address _myAddress) public view returns (uint);
    }
    
    // 在自己的合约中使用
    contract MyContract {
      address NumberInterfaceAddress = 0xab38...;
      // ^ 这是FavoriteNumber合约在以太坊上的地址
      NumberInterface numberContract = NumberInterface(NumberInterfaceAddress);
      // 现在变量 `numberContract` 指向另一个合约对象
    
      function someFunction() public {
        // 现在我们可以调用在那个合约中声明的 `getNum`函数:
        uint num = numberContract.getNum(msg.sender);
        // ...在这儿使用 `num`变量做些什么
      }
    }
    

*   处理多返回值
    

    function multipleReturns() internal returns(uint a, uint b, uint c) {
      return (1, 2, 3);
    }
    
    function processMultipleReturns() external {
      uint a;
      uint b;
      uint c;
      // 这样来做批量赋值:
      (a, b, c) = multipleReturns();
    }
    
    // 或者如果我们只想返回其中一个变量:
    function getLastReturnValue() external {
      uint c;
      // 可以对其他字段留空:
      (,,c) = multipleReturns();
    }
    

*   Ownable Contracts
    

    // OpenZeppelin库的Ownable 合约
    contract Ownable {
      address public owner;
      event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    
      // function Ownable()是一个 _ constructor_ (构造函数)，构造函数不是必须的，它与合约同名，构造函数一生中唯一的一次执行，就是在合约最初被创建的时候。
      function Ownable() public {
        owner = msg.sender;
      }
    
      // 在其他语句执行前，为它检查下先验条件。 在这个例子中，我们就可以写个修饰符 onlyOwner 检查下调用者，确保只有合约的主人才能运行本函数
      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;
      }
    }
    

*   合约继承 Ownable
    

    import "./ownable.sol";
    contract ZombieFactory is Ownable {}
    

*   gas优化1
    

    // 把 uint 绑定到 struct 里面
    // 如果一个 struct 中有多个 uint，则尽可能使用较小的 uint, Solidity 会将这些 uint 打包在一起，从而占用较少的存储空间
    struct NormalStruct {
      uint a;
      uint b;
      uint c;
    }
    
    struct MiniMe {
      uint32 a;
      uint32 b;
      uint c;
    }
    
    // 因为使用了结构打包，`mini` 比 `normal` 占用的空间更少
    NormalStruct normal = NormalStruct(10, 20, 30);
    MiniMe mini = MiniMe(10, 20, 30); 
    
    // 当 uint 定义在一个 struct 中的时候，尽量使用最小的整数子类型以节约空间。 并且把同样类型的变量放一起（即在 struct 中将把变量按照类型依次放置）
    

*   时间单位
    

    // Solidity 使用自己的本地时间单位
    // 变量 now 将返回当前的unix时间戳（自1970年1月1日以来经过的秒数）
    // Solidity 还包含秒(seconds)，分钟(minutes)，小时(hours)，天(days)，周(weeks) 和 年(years) 等时间单位
    
    // 必须使用 uint32（...） 进行强制类型转换，因为 now 返回类型 uint256。
    // 所以我们需要明确将它转换成一个 uint32 类型的变量
    

*   gas优化2
    

    // 使用 view 查询数据，但是注意:
    // 如果一个 view 函数在另一个函数的内部被调用，
    // 而调用函数与 view 函数的不属于同一个合约，也会产生调用成本。
    //这是因为如果主调函数在以太坊创建了一个事务，它仍然需要逐个节点去验证。
    //所以标记为 view 的函数只有在外部调用时才是免费的。(在所能只读的函数上标记上表示“只读”的“external view 声明)
    

*   修饰符 payable
    

一种可以接收以太的特殊函数

    contract OnlineStore {
      function buySomething() external payable {
        // 检查以确定0.001以太发送出去来运行函数:
        require(msg.value == 0.001 ether);
        // 如果为真，一些用来向函数调用者发送数字内容的逻辑
        transferThing(msg.sender);
      }
    }
    

*   随机数
    

这个方法首先拿到now的时间戳、msg.sender、 以及一个自增数nonce（一个仅会被使用一次的数，这样我们就不会对相同的输入值调用一次以上哈希函数了）。

    // 生成一个0到100的随机数:
    uint randNonce = 0;
    uint random = uint(keccak256(now, msg.sender, randNonce)) % 100;
    randNonce++;
    uint random2 = uint(keccak256(now, msg.sender, randNonce)) % 100;
    

*   转移标准
    

ERC721 规范有两种不同的方法来转移代币

1.  第一种方法是代币的拥有者调用transfer 方法，传入他想转移到的 address 和他想转移的代币的 \_tokenId。
    
2.  第二种方法是代币拥有者首先调用 approve，然后传入与以上相同的参数。接着，该合约会存储谁被允许提取代币，通常存储到一个 mapping (uint256 => address) 里。然后，当有人调用 takeOwnership 时，合约会检查 msg.sender 是否得到拥有者的批准来提取代币，如果是，则将代币转移给他。
    

*   预防溢出 假设我们有一个uint8, 只能存储8 bit数据。这意味着我们能存储的最大数字就是二进制11111111(或者说十进制的 2^8 - 1 = 255)
    

    uint8 number = 255;
    number++;
    // number = 0
    

*   使用safeMath
    

    import "safemath.sol"
    
    contract Hello{
      // 声明了 using SafeMath for uint 后，我们用来调用这些方法的 uint 就自动被作为第一个参数传递进去了
      using SafeMath for uint;
      using SafeMath32 for uint32;
      using SafeMath16 for uint16;
      uint test = 2;
      test = test.mul(3); // 等于 test = test * 3
    }
    

*   注释 - 使用 natspec 标准
    

    /// @title 一个简单的基础运算合约
    /// @author H4XF13LD MORRIS 💯💯😎💯💯
    /// @notice 现在，这个合约只添加一个乘法
    contract Math {
      /// @notice 两个数相乘
      /// @param x 第一个 uint
      /// @param y  第二个 uint
      /// @return z  (x * y) 的结果
      /// @dev 现在这个方法不检查溢出
      function multiply(uint x, uint y) returns (uint z) {
        // 这只是个普通的注释，不会被 natspec 解释
        z = x * y;
      }
    }

---

*Originally published on [0xmind](https://paragraph.com/@0xmind/cryptozombie)*
