本篇博客中记录以太坊的代码规范和框架,博客中的代码是同一个程序,逐渐增加内容的,这个程序逐渐丰富的过程
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;
}
}
-
Create a
public
function namedcreateRandomZombie
. It will take one parameter named_name
(astring
). (Note: Declare this functionpublic
just as you declared previous functionsprivate
) -
The first line of the function should run the
_generateRandomDna
function on_name
, and store it in auint
namedrandDna
. -
The second line should run the
_createZombie
function and pass it_name
andrandDna
. -
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.
-
Declare an
event
calledNewZombie
. It should passzombieId
(auint
),name
(astring
), anddna
(auint
). -
Modify the
_createZombie
function to fire theNewZombie
event after adding the new Zombie to ourzombies
array. -
You're going to need the zombie's
id
.array.push()
returns auint
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 ofzombies.push() - 1
in auint
calledid
, so you can use this in theNewZombie
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.
-
Create a mapping called
zombieToOwner
. The key will be auint
(we'll store and look up the zombie based on its id) and the value anaddress
. Let's make this mappingpublic
. -
Create a mapping called
ownerZombieCount
, where the key is anaddress
and the value auint
.
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.
-
First, after we get back the new zombie's
id
, let's update ourzombieToOwner
mapping to storemsg.sender
under thatid
. -
Second, let's increase
ownerZombieCount
for thismsg.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.
- Put a
require
statement at the beginning ofcreateRandomZombie
. The function should check to make sureownerZombieCount[msg.sender]
is equal to0
, 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
.
- Make a contract called
ZombieFeeding
belowZombieFactory
. This contract should inherit from ourZombieFactory
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 likeprivate
but can also be called by contracts that inherit from this one;external
can only be called outside the contract; and finallypublic
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
andaboveLevel
, for example. For these we can define custom logic to determine how they affect a function.