摘要:合約安全增強(qiáng)溢出和下溢我們將來(lái)學(xué)習(xí)你在編寫(xiě)智能合約的時(shí)候需要注意的一個(gè)主要的安全特性防止溢出和下溢。實(shí)戰(zhàn)演練給加上一些標(biāo)簽把這里變成標(biāo)準(zhǔn)的注釋把一個(gè)管理轉(zhuǎn)移僵尸所有權(quán)的合約符合對(duì)標(biāo)準(zhǔn)草案的實(shí)現(xiàn)
通過(guò)上一節(jié)的學(xué)習(xí),我們完成了 ERC721 的實(shí)現(xiàn)。并不是很復(fù)雜,對(duì)吧?很多類似的以太坊概念,當(dāng)你只聽(tīng)人們談?wù)撍鼈兊臅r(shí)候,會(huì)覺(jué)得很復(fù)雜。所以最簡(jiǎn)單的理解方式就是你自己來(lái)實(shí)現(xiàn)它。一、預(yù)防溢出
不過(guò)要記住那只是最簡(jiǎn)單的實(shí)現(xiàn)。還有很多的特性我們也許想加入到我們的實(shí)現(xiàn)中來(lái),比如一些額外的檢查,來(lái)確保用戶不會(huì)不小心把他們的僵尸轉(zhuǎn)移給0 地址(這被稱作 “燒幣”, 基本上就是把代幣轉(zhuǎn)移到一個(gè)誰(shuí)也沒(méi)有私鑰的地址,讓這個(gè)代幣永遠(yuǎn)也無(wú)法恢復(fù))。 或者在 DApp 中加入一些基本的拍賣(mài)邏輯。(你能想出一些實(shí)現(xiàn)的方法么?)
但是為了讓我們的課程不至于離題太遠(yuǎn),所以我們只專注于一些基礎(chǔ)實(shí)現(xiàn)。如果你想學(xué)習(xí)一些更深層次的實(shí)現(xiàn),可以在這個(gè)教程結(jié)束后,去看看 OpenZeppelin 的 ERC721 合約。
合約安全增強(qiáng):溢出和下溢我們將來(lái)學(xué)習(xí)你在編寫(xiě)智能合約的時(shí)候需要注意的一個(gè)主要的安全特性:防止溢出和下溢。
什么是溢出(overflow)?假設(shè)我們有一個(gè) uint8, 只能存儲(chǔ)8 bit數(shù)據(jù)。這意味著我們能存儲(chǔ)的最大數(shù)字就是二進(jìn)制 11111111 (或者說(shuō)十進(jìn)制的 2^8 - 1 = 255).
來(lái)看看下面的代碼。最后 number 將會(huì)是什么值?
uint8 number = 255; number++;
在這個(gè)例子中,我們導(dǎo)致了溢出 — 雖然我們加了1, 但是 number 出乎意料地等于 0了。 (如果你給二進(jìn)制 11111111 加1, 它將被重置為 00000000,就像鐘表從 23:59 走向 00:00)。
下溢(underflow)也類似,如果你從一個(gè)等于 0 的 uint8 減去 1, 它將變成 255 (因?yàn)?uint 是無(wú)符號(hào)的,其不能等于負(fù)數(shù))。
雖然我們?cè)谶@里不使用 uint8,而且每次給一個(gè) uint256 加 1 也不太可能溢出 (2^256 真的是一個(gè)很大的數(shù)了),在我們的合約中添加一些保護(hù)機(jī)制依然是非常有必要的,以防我們的 DApp 以后出現(xiàn)什么異常情況。
使用 SafeMath為了防止這些情況,OpenZeppelin 建立了一個(gè)叫做 SafeMath 的 庫(kù)(library),默認(rèn)情況下可以防止這些問(wèn)題。
不過(guò)在我們使用之前…… 什么叫做庫(kù)?
一個(gè)庫(kù)是 Solidity 中一種特殊的合約。其中一個(gè)有用的功能是給原始數(shù)據(jù)類型增加一些方法。
比如,使用 SafeMath 庫(kù)的時(shí)候,我們將使用 using SafeMath for uint256 這樣的語(yǔ)法。 SafeMath 庫(kù)有四個(gè)方法 — add, sub, mul, 以及 div?,F(xiàn)在我們可以這樣來(lái)讓 uint256 調(diào)用這些方法:
using SafeMath for uint256; uint256 a = 5; uint256 b = a.add(3); // 5 + 3 = 8 uint256 c = a.mul(2); // 5 * 2 = 10
我們將在下一章來(lái)學(xué)習(xí)這些方法,不過(guò)現(xiàn)在我們先將 SafeMath 庫(kù)添加進(jìn)我們的合約。
實(shí)戰(zhàn)演練我們已經(jīng)幫你把 OpenZeppelin 的 SafeMath 庫(kù)包含進(jìn) safemath.sol了,如果你想看一下代碼的話,現(xiàn)在可以看看,不過(guò)我們下一節(jié)將深入進(jìn)去。
首先我們來(lái)告訴我們的合約要使用 SafeMath。我們將在我們的 ZombieFactory 里調(diào)用,這是我們的基礎(chǔ)合約 — 這樣其他所有繼承出去的子合約都可以使用這個(gè)庫(kù)了。
1、將 safemath.sol 引入到 zombiefactory.sol.
2、添加定義: using SafeMath for uint256;.
zombiefactory.sol
pragma solidity ^0.4.19; import "./ownable.sol"; // 1. 在這里引入 import "./safemath.sol"; contract ZombieFactory is Ownable { // 2. 在這里定義 using safemath using SafeMath for uint 256; event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; uint cooldownTime = 1 days; struct Zombie { string name; uint dna; uint32 level; uint32 readyTime; uint16 winCount; uint16 lossCount; } Zombie[] public zombies; mapping (uint => address) public zombieToOwner; mapping (address => uint) ownerZombieCount; function _createZombie(string _name, uint _dna) internal { uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1; zombieToOwner[id] = msg.sender; ownerZombieCount[msg.sender]++; NewZombie(id, _name, _dna); } function _generateRandomDna(string _str) private view returns (uint) { uint rand = uint(keccak256(_str)); return rand % dnaModulus; } function createRandomZombie(string _name) public { require(ownerZombieCount[msg.sender] == 0); uint randDna = _generateRandomDna(_name); randDna = randDna - randDna % 100; _createZombie(_name, randDna); } }二、SafeMath
來(lái)看看 SafeMath 的部分代碼:
library SafeMath { function mul(uint256 a, uint256 b) internal pure returns (uint256) { if (a == 0) { return 0; } uint256 c = a * b; assert(c / a == b); return c; } function div(uint256 a, uint256 b) internal pure returns (uint256) { // assert(b > 0); // Solidity automatically throws when dividing by 0 uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn"t hold return c; } function sub(uint256 a, uint256 b) internal pure returns (uint256) { assert(b <= a); return a - b; } function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; assert(c >= a); return c; } }
首先我們有了 library 關(guān)鍵字 — 庫(kù)和 合約很相似,但是又有一些不同。 就我們的目的而言,庫(kù)允許我們使用 using 關(guān)鍵字,它可以自動(dòng)把庫(kù)的所有方法添加給一個(gè)數(shù)據(jù)類型:
using SafeMath for uint; // 這下我們可以為任何 uint 調(diào)用這些方法了 uint test = 2; test = test.mul(3); // test 等于 6 了 test = test.add(5); // test 等于 11 了
注意 mul 和 add 其實(shí)都需要兩個(gè)參數(shù)。 在我們聲明了 using SafeMath for uint 后,我們用來(lái)調(diào)用這些方法的 uint 就自動(dòng)被作為第一個(gè)參數(shù)傳遞進(jìn)去了(在此例中就是 test)
我們來(lái)看看 add 的源代碼看 SafeMath 做了什么:
function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; assert(c >= a); return c; }
基本上 add 只是像 + 一樣對(duì)兩個(gè) uint 相加, 但是它用一個(gè) assert 語(yǔ)句來(lái)確保結(jié)果大于 a。這樣就防止了溢出。
assert和require區(qū)別assert 和 require 相似,若結(jié)果為否它就會(huì)拋出錯(cuò)誤。 assert 和 require 區(qū)別在于,require 若失敗則會(huì)返還給用戶剩下的 gas, assert 則不會(huì)。所以大部分情況下,你寫(xiě)代碼的時(shí)候會(huì)比較喜歡 require,assert 只在代碼可能出現(xiàn)嚴(yán)重錯(cuò)誤的時(shí)候使用,比如 uint 溢出。
所以簡(jiǎn)而言之, SafeMath 的 add, sub, mul, 和 div 方法只做簡(jiǎn)單的四則運(yùn)算,然后在發(fā)生溢出或下溢的時(shí)候拋出錯(cuò)誤。
在我們的代碼里使用SafeMath。為了防止溢出和下溢,我們可以在我們的代碼里找 +, -, *, 或 /,然后替換為 add, sub, mul, div.
比如,與其這樣做:
myUint++;
我們這樣做:
myUint = myUint.add(1);實(shí)戰(zhàn)演練
在 ZombieOwnership 中有兩個(gè)地方用到了數(shù)學(xué)運(yùn)算,來(lái)替換成 SafeMath 方法把。
1、將 ++ 替換成 SafeMath 方法。
2、將 -- 替換成 SafeMath 方法。
ZombieOwnership
pragma solidity ^0.4.19; import "./zombieattack.sol"; import "./erc721.sol"; import "./safemath.sol"; contract ZombieOwnership is ZombieAttack, ERC721 { using SafeMath for uint256; mapping (uint => address) zombieApprovals; function balanceOf(address _owner) public view returns (uint256 _balance) { return ownerZombieCount[_owner]; } function ownerOf(uint256 _tokenId) public view returns (address _owner) { return zombieToOwner[_tokenId]; } function _transfer(address _from, address _to, uint256 _tokenId) private { // 1. 替換成 SafeMath 的 `add` // ownerZombieCount[_to].add(1); // 這種寫(xiě)法錯(cuò)誤,沒(méi)有賦值 ownerZombieCount[_to] = ownerZombieCount[_to].add(1); // 2. 替換成 SafeMath 的 `sub` // ownerZombieCount[_from].sub(1); // 這種寫(xiě)法錯(cuò)誤 ownerZombieCount[_from] = ownerZombieCount[_from].sub(1); zombieToOwner[_tokenId] = _to; Transfer(_from, _to, _tokenId); } function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { _transfer(msg.sender, _to, _tokenId); } function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { zombieApprovals[_tokenId] = _to; Approval(msg.sender, _to, _tokenId); } function takeOwnership(uint256 _tokenId) public { require(zombieApprovals[_tokenId] == msg.sender); address owner = ownerOf(_tokenId); _transfer(owner, msg.sender, _tokenId); } }其他類型
太好了,這下我們的 ERC721 實(shí)現(xiàn)不會(huì)有溢出或者下溢了。
回頭看看我們?cè)谥罢n程寫(xiě)的代碼,還有其他幾個(gè)地方也有可能導(dǎo)致溢出或下溢。
比如, 在 ZombieAttack 里面我們有:
myZombie.winCount++; myZombie.level++; enemyZombie.lossCount++;
我們同樣應(yīng)該在這些地方防止溢出。(通常情況下,總是使用 SafeMath 而不是普通數(shù)學(xué)運(yùn)算是個(gè)好主意,也許在以后 Solidity 的新版本里這點(diǎn)會(huì)被默認(rèn)實(shí)現(xiàn),但是現(xiàn)在我們得自己在代碼里實(shí)現(xiàn)這些額外的安全措施)。
不過(guò)我們遇到個(gè)小問(wèn)題 — winCount 和 lossCount 是 uint16, 而 level 是 uint32。 所以如果我們用這些作為參數(shù)傳入 SafeMath 的 add 方法。 它實(shí)際上并不會(huì)防止溢出,因?yàn)樗鼤?huì)把這些變量都轉(zhuǎn)換成 uint256:
function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; assert(c >= a); return c; } // 如果我們?cè)赻uint8` 上調(diào)用 `.add`。它將會(huì)被轉(zhuǎn)換成 `uint256`. // 所以它不會(huì)在 2^8 時(shí)溢出,因?yàn)?256 是一個(gè)有效的 `uint256`.
這就意味著,我們需要再實(shí)現(xiàn)兩個(gè)庫(kù)來(lái)防止 uint16 和 uint32 溢出或下溢。我們可以將其命名為 SafeMath16 和 SafeMath32。
代碼將和 SafeMath 完全相同,除了所有的 uint256 實(shí)例都將被替換成 uint32 或 uint16。
我們已經(jīng)將這些代碼幫你寫(xiě)好了,打開(kāi) safemath.sol 合約看看代碼吧。
現(xiàn)在我們需要在 ZombieFactory 里使用它們。
safemath.sol
pragma solidity ^0.4.18; /** * @title SafeMath * @dev Math operations with safety checks that throw on error */ library SafeMath { /** * @dev Multiplies two numbers, throws on overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { if (a == 0) { return 0; } uint256 c = a * b; assert(c / a == b); return c; } /** * @dev Integer division of two numbers, truncating the quotient. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { // assert(b > 0); // Solidity automatically throws when dividing by 0 uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn"t hold return c; } /** * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { assert(b <= a); return a - b; } /** * @dev Adds two numbers, throws on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; assert(c >= a); return c; } } /** * @title SafeMath32 * @dev SafeMath library implemented for uint32 */ library SafeMath32 { function mul(uint32 a, uint32 b) internal pure returns (uint32) { if (a == 0) { return 0; } uint32 c = a * b; assert(c / a == b); return c; } function div(uint32 a, uint32 b) internal pure returns (uint32) { // assert(b > 0); // Solidity automatically throws when dividing by 0 uint32 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn"t hold return c; } function sub(uint32 a, uint32 b) internal pure returns (uint32) { assert(b <= a); return a - b; } function add(uint32 a, uint32 b) internal pure returns (uint32) { uint32 c = a + b; assert(c >= a); return c; } } /** * @title SafeMath16 * @dev SafeMath library implemented for uint16 */ library SafeMath16 { function mul(uint16 a, uint16 b) internal pure returns (uint16) { if (a == 0) { return 0; } uint16 c = a * b; assert(c / a == b); return c; } function div(uint16 a, uint16 b) internal pure returns (uint16) { // assert(b > 0); // Solidity automatically throws when dividing by 0 uint16 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn"t hold return c; } function sub(uint16 a, uint16 b) internal pure returns (uint16) { assert(b <= a); return a - b; } function add(uint16 a, uint16 b) internal pure returns (uint16) { uint16 c = a + b; assert(c >= a); return c; } }實(shí)戰(zhàn)演練
分配:
1、聲明我們將為 uint32 使用SafeMath32。
2、聲明我們將為 uint16 使用SafeMath16。
3、在 ZombieFactory 里還有一處我們也應(yīng)該使用 SafeMath 的方法, 我們已經(jīng)在那里留了注釋提醒你。
zombiefactory.sol
pragma solidity ^0.4.19; import "./ownable.sol"; import "./safemath.sol"; contract ZombieFactory is Ownable { using SafeMath for uint256; // 1. 為 uint32 聲明 使用 SafeMath32 using SafeMath32 for uint32; // 2. 為 uint16 聲明 使用 SafeMath16 using SafeMath16 for uint16; event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; uint cooldownTime = 1 days; struct Zombie { string name; uint dna; uint32 level; uint32 readyTime; uint16 winCount; uint16 lossCount; } Zombie[] public zombies; mapping (uint => address) public zombieToOwner; mapping (address => uint) ownerZombieCount; function _createZombie(string _name, uint _dna) internal { // 注意: 我們選擇不處理2038年問(wèn)題,所以不用擔(dān)心 readyTime 的溢出 // 反正在2038年我們的APP早完蛋了 uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1; zombieToOwner[id] = msg.sender; // 3. 在這里使用 SafeMath 的 `add` 方法: // ownerZombieCount[msg.sender]++; ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].add(1); NewZombie(id, _name, _dna); } function _generateRandomDna(string _str) private view returns (uint) { uint rand = uint(keccak256(_str)); return rand % dnaModulus; } function createRandomZombie(string _name) public { require(ownerZombieCount[msg.sender] == 0); uint randDna = _generateRandomDna(_name); randDna = randDna - randDna % 100; _createZombie(_name, randDna); } }
現(xiàn)在,讓我們也順手把zombieattack.sol文件里邊的方法也修改為safeMath 形式。
zombieattack.sol
pragma solidity ^0.4.19; import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { uint randNonce = 0; uint attackVictoryProbability = 70; function randMod(uint _modulus) internal returns(uint) { // 這兒有一個(gè) randNonce = randNonce.add(1); return uint(keccak256(now, msg.sender, randNonce)) % _modulus; } function attack(uint _zombieId, uint _targetId) external onlyOwnerOf(_zombieId) { Zombie storage myZombie = zombies[_zombieId]; Zombie storage enemyZombie = zombies[_targetId]; uint rand = randMod(100); if (rand <= attackVictoryProbability) { // 這里有三個(gè) myZombie.winCount = myZombie.winCount.add(1); myZombie.level = myZombie.level.add(1); enemyZombie.lossCount = enemyZombie.lossCount.add(1); feedAndMultiply(_zombieId, enemyZombie.dna, "zombie"); } else { // 這兒還有倆哦 myZombie.lossCount = myZombie.lossCount.add(1); enemyZombie.winCount = enemyZombie.winCount.add(1); _triggerCooldown(myZombie); } } }三、注釋
尸游戲的 Solidity 代碼終于完成啦。
在以后的課程中,我們將學(xué)習(xí)如何將游戲部署到以太坊,以及如何和 Web3.js 交互。
不過(guò)在你離開(kāi)這節(jié)之前,我們來(lái)談?wù)勅绾?給你的代碼添加注釋.
注釋語(yǔ)法Solidity 里的注釋和 JavaScript 相同。在我們的課程中你已經(jīng)看到了不少單行注釋了:
// 這是一個(gè)單行注釋,可以理解為給自己或者別人看的筆記
只要在任何地方添加一個(gè) // 就意味著你在注釋。如此簡(jiǎn)單所以你應(yīng)該經(jīng)常這么做。
不過(guò)我們也知道你的想法:有時(shí)候單行注釋是不夠的。畢竟你生來(lái)話癆。
contract CryptoZombies { /* 這是一個(gè)多行注釋。我想對(duì)所有花時(shí)間來(lái)嘗試這個(gè)編程課程的人說(shuō)聲謝謝。 它是免費(fèi)的,并將永遠(yuǎn)免費(fèi)。但是我們依然傾注了我們的心血來(lái)讓它變得更好。 要知道這依然只是區(qū)塊鏈開(kāi)發(fā)的開(kāi)始而已,雖然我們已經(jīng)走了很遠(yuǎn), 仍然有很多種方式來(lái)讓我們的社區(qū)變得更好。 如果我們?cè)谀膫€(gè)地方出了錯(cuò),歡迎在我們的 github 提交 PR 或者 issue 來(lái)幫助我們改進(jìn): https://github.com/loomnetwork/cryptozombie-lessons 或者,如果你有任何的想法、建議甚至僅僅想和我們打聲招呼,歡迎來(lái)我們的電報(bào)群: https://t.me/loomnetworkcn */ }
所以我們有了多行注釋:
contract CryptoZombies { /* 這是一個(gè)多行注釋。我想對(duì)所有花時(shí)間來(lái)嘗試這個(gè)編程課程的人說(shuō)聲謝謝。 它是免費(fèi)的,并將永遠(yuǎn)免費(fèi)。但是我們依然傾注了我們的心血來(lái)讓它變得更好。 要知道這依然只是區(qū)塊鏈開(kāi)發(fā)的開(kāi)始而已,雖然我們已經(jīng)走了很遠(yuǎn), 仍然有很多種方式來(lái)讓我們的社區(qū)變得更好。 如果我們?cè)谀膫€(gè)地方出了錯(cuò),歡迎在我們的 github 提交 PR 或者 issue 來(lái)幫助我們改進(jìn): https://github.com/loomnetwork/cryptozombie-lessons 或者,如果你有任何的想法、建議甚至僅僅想和我們打聲招呼,歡迎來(lái)我們的電報(bào)群: https://t.me/loomnetworkcn */ }
特別是,最好為你合約中每個(gè)方法添加注釋來(lái)解釋它的預(yù)期行為。這樣其他開(kāi)發(fā)者(或者你自己,在6個(gè)月以后再回到這個(gè)項(xiàng)目中)可以很快地理解你的代碼而不需要逐行閱讀所有代碼。
Solidity 社區(qū)所使用的一個(gè)標(biāo)準(zhǔn)是使用一種被稱作 natspec 的格式,看起來(lái)像這樣:
/// @title 一個(gè)簡(jiǎn)單的基礎(chǔ)運(yùn)算合約 /// @author H4XF13LD MORRIS /// @notice 現(xiàn)在,這個(gè)合約只添加一個(gè)乘法 contract Math { /// @notice 兩個(gè)數(shù)相乘 /// @param x 第一個(gè) uint /// @param y 第二個(gè) uint /// @return z (x * y) 的結(jié)果 /// @dev 現(xiàn)在這個(gè)方法不檢查溢出 function multiply(uint x, uint y) returns (uint z) { // 這只是個(gè)普通的注釋,不會(huì)被 natspec 解釋 z = x * y; } }
@title(標(biāo)題) 和 @author (作者)很直接了.
@notice (須知)向 用戶 解釋這個(gè)方法或者合約是做什么的。@dev (開(kāi)發(fā)者) 是向開(kāi)發(fā)者解釋更多的細(xì)節(jié)。
@param (參數(shù))和 @return (返回) 用來(lái)描述這個(gè)方法需要傳入什么參數(shù)以及返回什么值。
注意你并不需要每次都用上所有的標(biāo)簽,它們都是可選的。不過(guò)最少,寫(xiě)下一個(gè) @dev 注釋來(lái)解釋每個(gè)方法是做什么的。
實(shí)戰(zhàn)演練給 ZombieOwnership 加上一些 natspec 標(biāo)簽:
zombieownership.sol
pragma solidity ^0.4.19; import "./zombieattack.sol"; import "./erc721.sol"; import "./safemath.sol"; /// TODO: 把這里變成 natspec 標(biāo)準(zhǔn)的注釋把 /// @title 一個(gè)管理轉(zhuǎn)移僵尸所有權(quán)的合約 /// @author Corwien /// @dev 符合 OpenZeppelin 對(duì) ERC721 標(biāo)準(zhǔn)草案的實(shí)現(xiàn) /// @date 2018/06/17 contract ZombieOwnership is ZombieAttack, ERC721 { using SafeMath for uint256; mapping (uint => address) zombieApprovals; function balanceOf(address _owner) public view returns (uint256 _balance) { return ownerZombieCount[_owner]; } function ownerOf(uint256 _tokenId) public view returns (address _owner) { return zombieToOwner[_tokenId]; } function _transfer(address _from, address _to, uint256 _tokenId) private { ownerZombieCount[_to] = ownerZombieCount[_to].add(1); ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1); zombieToOwner[_tokenId] = _to; Transfer(_from, _to, _tokenId); } function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { _transfer(msg.sender, _to, _tokenId); } function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { zombieApprovals[_tokenId] = _to; Approval(msg.sender, _to, _tokenId); } function takeOwnership(uint256 _tokenId) public { require(zombieApprovals[_tokenId] == msg.sender); address owner = ownerOf(_tokenId); _transfer(owner, msg.sender, _tokenId); } }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/24128.html
摘要:以太坊開(kāi)發(fā)高級(jí)語(yǔ)言學(xué)習(xí)。地址以太坊區(qū)塊鏈由賬戶組成,你可以把它想象成銀行賬戶。使用很安全,因?yàn)樗哂幸蕴粎^(qū)塊鏈的安全保障除非竊取與以太坊地址相關(guān)聯(lián)的私鑰,否則是沒(méi)有辦法修改其他人的數(shù)據(jù)的。 以太坊開(kāi)發(fā)高級(jí)語(yǔ)言學(xué)習(xí)。 一、映射(Mapping)和地址(Address) 我們通過(guò)給數(shù)據(jù)庫(kù)中的僵尸指定主人, 來(lái)支持多玩家模式。 如此一來(lái),我們需要引入2個(gè)新的數(shù)據(jù)類型:mapping(映射)...
摘要:是企業(yè)與區(qū)塊鏈相遇的地方。的框架旨在成為開(kāi)發(fā)區(qū)塊鏈解決方案的支柱。以太坊,主要是針對(duì)工程師使用進(jìn)行區(qū)塊鏈以太坊開(kāi)發(fā)的詳解。 如果你想將區(qū)塊鏈合并到一個(gè)Java項(xiàng)目中,現(xiàn)在我們來(lái)看看就是這個(gè)細(xì)分領(lǐng)域中三個(gè)最大的OSS玩家。 好的伙計(jì)們,我們都聽(tīng)說(shuō)過(guò)比特幣,以太坊或其他加密貨幣,其中有一些時(shí)髦的名字圍繞著我們常見(jiàn)的新聞,但我們作為Java開(kāi)發(fā)人員知道如何輕松地與這些區(qū)塊鏈技術(shù)進(jìn)行交互嗎?以...
摘要:原文發(fā)表于以太坊智能合約開(kāi)發(fā)第二篇理解以太坊相關(guān)概念很多人都說(shuō)比特幣是區(qū)塊鏈,以太坊是區(qū)塊鏈。它是以太坊智能合約的運(yùn)行環(huán)境。是由以太坊節(jié)點(diǎn)提供。以太坊社區(qū)把基于智能合約的應(yīng)用稱為去中心化的應(yīng)用。 原文發(fā)表于:以太坊智能合約開(kāi)發(fā)第二篇:理解以太坊相關(guān)概念 很多人都說(shuō)比特幣是區(qū)塊鏈1.0,以太坊是區(qū)塊鏈2.0。在以太坊平臺(tái)上,可以開(kāi)發(fā)各種各樣的去中心化應(yīng)用,這些應(yīng)用構(gòu)成了以太坊的整個(gè)生態(tài)...
摘要:本文面向以太坊智能合約應(yīng)用程序開(kāi)發(fā)人員,并討論如何在密碼保護(hù)后,安全地運(yùn)行你的以太坊節(jié)點(diǎn),以便通過(guò)進(jìn)行安全輸出。以太坊,主要是針對(duì)工程師使用進(jìn)行區(qū)塊鏈以太坊開(kāi)發(fā)的詳解。 本文面向以太坊智能合約應(yīng)用程序開(kāi)發(fā)人員,并討論如何在密碼保護(hù)后,安全地運(yùn)行你的以太坊節(jié)點(diǎn),以便通過(guò)Internet進(jìn)行安全輸出。 Go Ethereum(geth)是以太坊節(jié)點(diǎn)最受歡迎的軟件。其他流行的以太坊實(shí)現(xiàn)是Pa...
摘要:本文面向以太坊智能合約應(yīng)用程序開(kāi)發(fā)人員,并討論如何在密碼保護(hù)后,安全地運(yùn)行你的以太坊節(jié)點(diǎn),以便通過(guò)進(jìn)行安全輸出。以太坊,主要是針對(duì)工程師使用進(jìn)行區(qū)塊鏈以太坊開(kāi)發(fā)的詳解。 本文面向以太坊智能合約應(yīng)用程序開(kāi)發(fā)人員,并討論如何在密碼保護(hù)后,安全地運(yùn)行你的以太坊節(jié)點(diǎn),以便通過(guò)Internet進(jìn)行安全輸出。 Go Ethereum(geth)是以太坊節(jié)點(diǎn)最受歡迎的軟件。其他流行的以太坊實(shí)現(xiàn)是Pa...
閱讀 898·2021-11-23 09:51
閱讀 1111·2021-11-15 17:57
閱讀 1680·2021-09-22 15:24
閱讀 823·2021-09-07 09:59
閱讀 2238·2019-08-29 15:10
閱讀 1859·2019-08-29 12:47
閱讀 763·2019-08-29 12:30
閱讀 3386·2019-08-26 13:51