以太坊实战

本篇博客中记录以太坊的代码规范和框架,博客中的代码是同一个程序,逐渐增加内容的,这个程序逐渐丰富的过程

As you can see, we use the keyword private after the function name. And as with function parameters, it's convention to start private function names with an underscore (_).

私有函数在参数列表后加 private即可

pragma solidity ^0.4.25;

contract ZombieFactory {

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    function _createZombie (string _name, uint _dna) private {
        zombies.push(Zombie(_name, _dna));
    }

}

 Ethereum has the hash function keccak256 built in, which is a version of SHA3. A hash function basically maps an input into a random 256-bit hexidecimal number. A slight change in the input will cause a large change in the hash.

pragma solidity ^0.4.25;

contract ZombieFactory {

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    function _createZombie(string _name, uint _dna) private {
        zombies.push(Zombie(_name, _dna));
    } 

    function _generateRandomDna(string _str) private view returns (uint) {
        // start here
        uint rand=uint(keccak256(abi.encodePacked(_str)));
        return rand%dnaModulus;
    }

}
  1. Create a public function named createRandomZombie. It will take one parameter named _name (a string). (Note: Declare this function public just as you declared previous functions private)

  2. The first line of the function should run the _generateRandomDna function on _name, and store it in a uint named randDna.

  3. The second line should run the _createZombie function and pass it _name and randDna.

  4. The solution should be 4 lines of code (including the closing } of the function).

    pragma solidity ^0.4.25;
    
    contract ZombieFactory {
    
        uint dnaDigits = 16;
        uint dnaModulus = 10 ** dnaDigits;
    
        struct Zombie {
            string name;
            uint dna;
        }
    
        Zombie[] public zombies;
    
        function _createZombie(string _name, uint _dna) private {
            zombies.push(Zombie(_name, _dna));
        } 
    
        function _generateRandomDna(string _str) private view returns (uint) {
            uint rand = uint(keccak256(abi.encodePacked(_str)));
            return rand % dnaModulus;
        }
    
        // start here
        function createRandomZombie(string _name) public{
            uint randDna=_generateRandomDna(_name);
            _createZombie(_name,randDna);
        }
    }
    

    We want an event to let our front-end know every time a new zombie was created, so the app can display it.

  5. Declare an event called NewZombie. It should pass zombieId (a uint), name (a string), and dna (a uint).

  6. Modify the _createZombie function to fire the NewZombie event after adding the new Zombie to our zombies array.

  7. You're going to need the zombie's id. array.push() returns a uint of the new length of the array - and since the first item in an array has index 0, array.push() - 1 will be the index of the zombie we just added. Store the result of zombies.push() - 1 in a uint called id, so you can use this in the NewZombie event in the next line

pragma solidity ^0.4.25;

contract ZombieFactory {

    // declare our event here
    event NewZombie(uint zombieId,string name,uint dna);
    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    function _createZombie(string _name, uint _dna) private {
        
        // and fire it here
        uint id=zombies.push(Zombie(_name, _dna))-1;
        emit NewZombie(id,_name,_dna);
    } 

    function _generateRandomDna(string _str) private view returns (uint) {
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;
    }

    function createRandomZombie(string _name) public {
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randDna);
    }

}

最后结果如下:

To store zombie ownership, we're going to use two mappings: one that keeps track of the address that owns a zombie, and another that keeps track of how many zombies an owner has.

  1. Create a mapping called zombieToOwner. The key will be a uint (we'll store and look up the zombie based on its id) and the value an address. Let's make this mapping public.

  2. Create a mapping called ownerZombieCount, where the key is an address and the value a uint.

pragma solidity ^0.4.25;

contract ZombieFactory {

    event NewZombie(uint zombieId, string name, uint dna);

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    // declare mappings here
    mapping (uint => address) public zombieToOwner;
    mapping (address => uint)  ownerZombieCount;
    function _createZombie(string _name, uint _dna) private {
        uint id = zombies.push(Zombie(_name, _dna)) - 1;
        emit NewZombie(id, _name, _dna);
    } 

    function _generateRandomDna(string _str) private view returns (uint) {
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;
    }

    function createRandomZombie(string _name) public {
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randDna);
    }

}

Let's update our _createZombie method from lesson 1 to assign ownership of the zombie to whoever called the function.

  1. First, after we get back the new zombie's id, let's update our zombieToOwner mapping to store msg.sender under that id.

  2. Second, let's increase ownerZombieCount for this msg.sender.

In Solidity, you can increase a uint with ++, just like in javascript:

uint number = 0;
number++;
// `number` is now `1`
pragma solidity ^0.4.25;

contract ZombieFactory {

    event NewZombie(uint zombieId, string name, uint dna);

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

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

    function _createZombie(string _name, uint _dna) private {
        uint id = zombies.push(Zombie(_name, _dna)) - 1;
        // start here
        zombieToOwner[id]=msg.sender;
        ownerZombieCount[msg.sender]++;
        emit NewZombie(id, _name, _dna);
    }

    function _generateRandomDna(string _str) private view returns (uint) {
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;
    }

    function createRandomZombie(string _name) public {
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randDna);
    }

}

In our zombie game, we don't want the user to be able to create unlimited zombies in their army by repeatedly calling createRandomZombie — it would make the game not very fun.

Let's use require to make sure this function only gets executed one time per user, when they create their first zombie.

  1. Put a require statement at the beginning of createRandomZombie. The function should check to make sure ownerZombieCount[msg.sender] is equal to 0, and throw an error otherwise.
pragma solidity ^0.4.25;

contract ZombieFactory {

    event NewZombie(uint zombieId, string name, uint dna);

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

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

    function _createZombie(string _name, uint _dna) private {
        uint id = zombies.push(Zombie(_name, _dna)) - 1;
        zombieToOwner[id] = msg.sender;
        ownerZombieCount[msg.sender]++;
        emit NewZombie(id, _name, _dna);
    }

    function _generateRandomDna(string _str) private view returns (uint) {
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;
    }

    function createRandomZombie(string _name) public {
        // start here
        require(ownerZombieCount[msg.sender]==0);
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randDna);
    }

}

In the next chapters, we're going to be implementing the functionality for our zombies to feed and multiply. Let's put this logic into its own contract that inherits all the methods from ZombieFactory.

  1. Make a contract called ZombieFeeding below ZombieFactory. This contract should inherit from our ZombieFactory contract.
pragma solidity ^0.4.25;

contract ZombieFactory {

    event NewZombie(uint zombieId, string name, uint dna);

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

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

    function _createZombie(string _name, uint _dna) private {
        uint id = zombies.push(Zombie(_name, _dna)) - 1;
        zombieToOwner[id] = msg.sender;
        ownerZombieCount[msg.sender]++;
        emit NewZombie(id, _name, _dna);
    }

    function _generateRandomDna(string _str) private view returns (uint) {
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;
    }

    function createRandomZombie(string _name) public {
        require(ownerZombieCount[msg.sender] == 0);
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randDna);
    }

}

// Start here
contract ZombieFeeding is ZombieFactory{
    
}

Now that we've set up a multi-file structure, we need to use import to read the contents of the other file:

pragma solidity ^0.4.25;

// put import statement here
import "./zombiefactory.sol";
contract ZombieFeeding is ZombieFactory {

}
pragma solidity ^0.4.25;

import "./zombiefactory.sol";

contract ZombieFeeding is ZombieFactory {

  // Start here
    function feedAndMultiply(uint _zombieId,uint _targetDna) public {
        require(msg.sender==zombieToOwner[_zombieId]);
        Zombie storage myZombie=zombies[_zombieId];
    }
}
pragma solidity ^0.4.25;

import "./zombiefactory.sol";

contract ZombieFeeding is ZombieFactory {

  function feedAndMultiply(uint _zombieId, uint _targetDna) public {
    require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    // start here
    _targetDna=_targetDna % dnaModulus;
    uint newDna=(myZombie.dna + _targetDna)/2;
    _createZombie("NoName",newDna);
  }

}
pragma solidity ^0.4.25;

import "./zombiefactory.sol";

// Create KittyInterface here
contract KittyInterface{
  function getKitty (uint256 _id) external view returns (
    bool isGestating,
    bool isReady,
    uint256 cooldownIndex,
    uint256 nextActionAt,
    uint256 siringWithId,
    uint256 birthTime,
    uint256 matronId,
    uint256 sireId,
    uint256 generation,
    uint256 genes
);
}

contract ZombieFeeding is ZombieFactory {

  function feedAndMultiply(uint _zombieId, uint _targetDna) public {
    require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna) / 2;
    _createZombie("NoName", newDna);
  }

}
  • Constructors: function Ownable() is a constructor, which is an optional special function that has the same name as the contract. It will get executed only one time, when the contract is first created.
  • Function Modifiers: modifier onlyOwner(). Modifiers are kind of half-functions that are used to modify other functions, usually to check some requirements prior to execution. In this case, onlyOwner can be used to limit access so only the owner of the contract can run this function. We'll talk more about function modifiers in the next chapter, and what that weird _; does.
  • indexed keyword: don't worry about this one, we don't need it yet.
    pragma solidity ^0.4.25;
    
    /**
    * @title Ownable
    * @dev The Ownable contract has an owner address, and provides basic authorization control
    * functions, this simplifies the implementation of "user permissions".
    */
    contract Ownable {
      address private _owner;
    
      event OwnershipTransferred(
        address indexed previousOwner,
        address indexed newOwner
      );
    
      /**
      * @dev The Ownable constructor sets the original `owner` of the contract to the sender
      * account.
      */
      constructor() internal {
        _owner = msg.sender;
        emit OwnershipTransferred(address(0), _owner);
      }
    
      /**
      * @return the address of the owner.
      */
      function owner() public view returns(address) {
        return _owner;
      }
    
      /**
      * @dev Throws if called by any account other than the owner.
      */
      modifier onlyOwner() {
        require(isOwner());
        _;
      }
    
      /**
      * @return true if `msg.sender` is the owner of the contract.
      */
      function isOwner() public view returns(bool) {
        return msg.sender == _owner;
      }
    
      /**
      * @dev Allows the current owner to relinquish control of the contract.
      * @notice Renouncing to ownership will leave the contract without an owner.
      * It will not be possible to call the functions with the `onlyOwner`
      * modifier anymore.
      */
      function renounceOwnership() public onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
      }
    
      /**
      * @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 {
        _transferOwnership(newOwner);
      }
    
      /**
      * @dev Transfers control of the contract to a newOwner.
      * @param newOwner The address to transfer ownership to.
      */
      function _transferOwnership(address newOwner) internal {
        require(newOwner != address(0));
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
      }
    }
    

    函数修饰符

    函数修饰符看起来跟函数没什么不同,不过关键字modifier 告诉编译器,这是个modifier(修饰符),而不是个function(函数)。它不能像函数那样被直接调用,只能被添加到函数定义的末尾,用以改变函数的行为。

    咱们仔细读读 onlyOwner:

  • /**
     * @dev 调用者不是‘主人’,就会抛出异常
     */
    modifier onlyOwner() {
      require(msg.sender == owner);
      _;
    }

     onlyOwner 函数修饰符是这么用的:

  • contract MyContract is Ownable {
      event LaughManiacally(string laughter);
    
      //注意! `onlyOwner`上场 :
      function likeABoss() external onlyOwner {
        LaughManiacally("Muahahahaha");
      }
    }

    注意 likeABoss 函数上的 onlyOwner 修饰符。 当你调用 likeABoss 时,首先执行 onlyOwner 中的代码, 执行到 onlyOwner 中的 _; 语句时,程序再返回并执行 likeABoss 中的代码。

    可见,尽管函数修饰符也可以应用到各种场合,但最常见的还是放在函数执行之前添加快速的 require检查。

    因为给函数添加了修饰符 onlyOwner,使得唯有合约的主人(也就是部署者)才能调用它。

  • 注意:主人对合约享有的特权当然是正当的,不过也可能被恶意使用。比如,万一,主人添加了个后门,

    允许他偷走别人的僵尸呢?

    所以非常重要的是,部署在以太坊上的 DApp,并不能保证它真正做到去中心,你需要阅读并理解它的源代码,才能防止其中没有被部署者恶意植入后门;作为开发人员,如何做到既要给自己留下修复 bug 的余地,又要尽量地放权给使用者,以便让他们放心你,从而愿意把数据放在你的 DApp 中,这确实需要个微妙的平衡。

  • 记住,修饰符的最后一行为 _;,表示修饰符调用结束后返回,并执行调用函数余下的部分。

  • We have visibility modifiers that control when and where the function can be called from: private means it's only callable from other functions inside the contract; internal is like private but can also be called by contracts that inherit from this one; external can only be called outside the contract; and finally public can be called anywhere, both internally and externally.

  • We also have state modifiers, which tell us how the function interacts with the BlockChain: view tells us that by running the function, no data will be saved/changed. pure tells us that not only does the function not save any data to the blockchain, but it also doesn't read any data from the blockchain. Both of these don't cost any gas to call if they're called externally from outside the contract (but they do cost gas if called internally by another function).

  • Then we have custom modifiers, which we learned about in Lesson 3: onlyOwner and aboveLevel, for example. For these we can define custom logic to determine how they affect a function.

猜你喜欢

转载自blog.csdn.net/salmonwilliam/article/details/84698579