摘要:從這節開始,我們將學習代幣標準以及加密收集資產等知識。聲明一個繼承的新合約,命名為。注意目前是一個草稿,還沒有正式商定的實現。所以把這一個可能的實現當作考慮,但不要把它作為代幣的官方標準。
從這節開始,我們將學習代幣, ERC721標準, 以及加密收集資產等知識。一、代幣 代幣
讓我們來聊聊以太坊上的代幣。
如果你對以太坊的世界有一些了解,你很可能聽過人們聊到代幣——尤其是 ERC20 代幣。
一個 代幣 在以太坊基本上就是一個遵循一些共同規則的智能合約——即它實現了所有其他代幣合約共享的一組標準函數,例如 transfer(address _to, uint256 _value) 和 balanceOf(address _owner).
在智能合約內部,通常有一個映射, mapping(address => uint256) balances,用于追蹤每個地址還有多少余額。
所以基本上一個代幣只是一個追蹤誰擁有多少該代幣的合約,和一些可以讓那些用戶將他們的代幣轉移到其他地址的函數。
它為什么重要呢?由于所有 ERC20 代幣共享具有相同名稱的同一組函數,它們都可以以相同的方式進行交互。
這意味著如果你構建的應用程序能夠與一個 ERC20 代幣進行交互,那么它就也能夠與任何 ERC20 代幣進行交互。 這樣一來,將來你就可以輕松地將更多的代幣添加到你的應用中,而無需進行自定義編碼。 你可以簡單地插入新的代幣合約地址,然后嘩啦,你的應用程序有另一個它可以使用的代幣了。
其中一個例子就是交易所。 當交易所添加一個新的 ERC20 代幣時,實際上它只需要添加與之對話的另一個智能合約。 用戶可以讓那個合約將代幣發送到交易所的錢包地址,然后交易所可以讓合約在用戶要求取款時將代幣發送回給他們。
交易所只需要實現這種轉移邏輯一次,然后當它想要添加一個新的 ERC20 代幣時,只需將新的合約地址添加到它的數據庫即可。
其他代幣標準對于像貨幣一樣的代幣來說,ERC20 代幣非常酷。 但是要在我們僵尸游戲中代表僵尸就并不是特別有用。
首先,僵尸不像貨幣可以分割 —— 我可以發給你 0.237 以太,但是轉移給你 0.237 的僵尸聽起來就有些搞笑。
其次,并不是所有僵尸都是平等的。 你的2級僵尸"Steve"完全不能等同于我732級的僵尸"H4XF13LD MORRIS"。(你差得遠呢,Steve)。
有另一個代幣標準更適合如 CryptoZombies 這樣的加密收藏品——它們被稱為ERC721 代幣.
ERC721代幣是不能互換的,因為每個代幣都被認為是唯一且不可分割的。 你只能以整個單位交易它們,并且每個單位都有唯一的 ID。 這些特性正好讓我們的僵尸可以用來交易。
請注意,使用像 ERC721 這樣的標準的優勢就是,我們不必在我們的合約中實現拍賣或托管邏輯,這決定了玩家能夠如何交易/出售我們的僵尸。 如果我們符合規范,其他人可以為加密可交易的 ERC721 資產搭建一個交易所平臺,我們的 ERC721 僵尸將可以在該平臺上使用。 所以使用代幣標準相較于使用你自己的交易邏輯有明顯的好處。實戰演練
我們將在下一章深入討論ERC721的實現。 但首先,讓我們為本課設置我們的文件結構。
我們將把所有ERC721邏輯存儲在一個叫ZombieOwnership的合約中。
1、在文件頂部聲明我們pragma的版本(格式參考之前的課程)。
2、將 zombieattack.sol import 進來。
3、聲明一個繼承 ZombieAttack 的新合約, 命名為ZombieOwnership。合約的其他部分先留空。
zombieownership.sol
// 從這里開始 pragma solidity ^0.4.19; import "./zombieattack.sol"; contract ZombieOwnership is ZombieAttack { }二、ERC721標準與多重繼承
讓我們來看一看 ERC721 標準:
contract ERC721 { event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); function balanceOf(address _owner) public view returns (uint256 _balance); function ownerOf(uint256 _tokenId) public view returns (address _owner); function transfer(address _to, uint256 _tokenId) public; function approve(address _to, uint256 _tokenId) public; function takeOwnership(uint256 _tokenId) public; }
這是我們需要實現的方法列表,我們將在接下來的章節中逐個學習。
雖然看起來很多,但不要被嚇到了!我們在這里就是準備帶著你一步一步了解它們的。
注意: ERC721目前是一個 草稿,還沒有正式商定的實現。在本教程中,我們使用的是 OpenZeppelin 庫中的當前版本,但在未來正式發布之前它可能會有更改。 所以把這 一個 可能的實現當作考慮,但不要把它作為 ERC721 代幣的官方標準。實現一個代幣合約
在實現一個代幣合約的時候,我們首先要做的是將接口復制到它自己的 Solidity 文件并導入它,import ./erc721.sol。 接著,讓我們的合約繼承它,然后我們用一個函數定義來重寫每個方法。
但等一下—— ZombieOwnership 已經繼承自 ZombieAttack 了 —— 它如何能夠也繼承于 ERC721 呢?
幸運的是在Solidity,你的合約可以繼承自多個合約,參考如下:
contract SatoshiNakamoto is NickSzabo, HalFinney { // 嘖嘖嘖,宇宙的奧秘泄露了 }
正如你所見,當使用多重繼承的時候,你只需要用逗號 , 來隔開幾個你想要繼承的合約。在上面的例子中,我們的合約繼承自 NickSzabo 和 HalFinney。
來試試吧。
實戰演練我們已經在上面為你創建了帶著接口的 erc721.sol 。
1、將 erc721.sol 導入到 zombieownership.sol
2、聲明 ZombieOwnership 繼承自 ZombieAttack 和 ERC721
zombieownership.sol
pragma solidity ^0.4.19; import "./zombieattack.sol"; // 在這里引入文件 import "./erc721.sol"; // 在這里聲明 ERC721 的繼承 contract ZombieOwnership is ZombieAttack, ERC721 { }三、 balanceOf和ownerOf
現在,我們來深入討論一下 ERC721 的實現。
我們已經把所有你需要在本課中實現的函數的空殼復制好了。
在本章節,我們將實現頭兩個方法: balanceOf 和 ownerOf。
balanceOffunction balanceOf(address _owner) public view returns (uint256 _balance);
這個函數只需要一個傳入 address 參數,然后返回這個 address 擁有多少代幣。
在我們的例子中,我們的“代幣”是僵尸。你還記得在我們 DApp 的哪里存儲了一個主人擁有多少只僵尸嗎?
ownerOffunction ownerOf(uint256 _tokenId) public view returns (address _owner);
這個函數需要傳入一個代幣 ID 作為參數 (我們的情況就是一個僵尸 ID),然后返回該代幣擁有者的 address。
同樣的,因為在我們的 DApp 里已經有一個 mapping (映射) 存儲了這個信息,所以對我們來說這個實現非常直接清晰。我們可以只用一行 return 語句來實現這個函數。
注意:要記得, uint256 等同于uint。我們從課程的開始一直在代碼中使用 uint,但從現在開始我們將在這里用 uint256,因為我們直接從規范中復制粘貼。實戰演練
我將讓你來決定如何實現這兩個函數。
每個函數的代碼都應該只有1行 return 語句。看看我們在之前課程中寫的代碼,想想我們都把這個數據存儲在哪。如果你覺得有困難,你可以點“我要看答案”的按鈕來獲得幫助。
1、實現 balanceOf 來返回 _owner 擁有的僵尸數量。
2、實現 ownerOf 來返回擁有 ID 為 _tokenId 僵尸的所有者的地址。
zombieownership.sol
pragma solidity ^0.4.19; import "./zombieattack.sol"; import "./erc721.sol"; contract ZombieOwnership is ZombieAttack, ERC721 { function balanceOf(address _owner) public view returns (uint256 _balance) { // 1. 在這里返回 `_owner` 擁有的僵尸數 return ownerZombieCount[_owner]; } function ownerOf(uint256 _tokenId) public view returns (address _owner) { // 2. 在這里返回 `_tokenId` 的所有者 return zombieToOwner[_tokenId]; } function transfer(address _to, uint256 _tokenId) public { } function approve(address _to, uint256 _tokenId) public { } function takeOwnership(uint256 _tokenId) public { } }四、重構
Hey!我們剛剛的代碼中其實有個錯誤,以至于其根本無法通過編譯,你發現了沒?
在前一個章節我們定義了一個叫 ownerOf 的函數。但如果你還記得第4課的內容,我們同樣在zombiefeeding.sol 里以 ownerOf 命名創建了一個 modifier(修飾符)。
如果你嘗試編譯這段代碼,編譯器會給你一個錯誤說你不能有相同名稱的修飾符和函數。
所以我們應該把在 ZombieOwnership 里的函數名稱改成別的嗎?
不,我們不能那樣做!!!要記得,我們正在用 ERC721 代幣標準,意味著其他合約將期望我們的合約以這些確切的名稱來定義函數。這就是這些標準實用的原因——如果另一個合約知道我們的合約符合 ERC721 標準,它可以直接與我們交互,而無需了解任何關于我們內部如何實現的細節。
所以,那意味著我們將必須重構我們第4課中的代碼,將 modifier 的名稱換成別的。
實戰演練我們回到了 zombiefeeding.sol 。我們將把 modifier 的名稱從 ownerOf 改成 onlyOwnerOf。
1、把修飾符定義中的名稱改成 onlyOwnerOf
2、往下滑到使用此修飾符的函數 feedAndMultiply 。我們也需要改這里的名稱。
注意:我們在 zombiehelper.sol 和 zombieattack.sol 里也使用了這個修飾符,所以這兩個文件也必須把名字改了。
zombiefeeding.sol
pragma solidity ^0.4.19; import "./zombiefactory.sol"; 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 { KittyInterface kittyContract; // 1. 把修飾符名稱改成 `onlyOwnerOf` modifier onlyOwnerOf(uint _zombieId) { require(msg.sender == zombieToOwner[_zombieId]); _; } function setKittyContractAddress(address _address) external onlyOwner { kittyContract = KittyInterface(_address); } function _triggerCooldown(Zombie storage _zombie) internal { _zombie.readyTime = uint32(now + cooldownTime); } function _isReady(Zombie storage _zombie) internal view returns (bool) { return (_zombie.readyTime <= now); } // 2. 這里也要修改修飾符的名稱 function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal onlyOwnerOf(_zombieId) { Zombie storage myZombie = zombies[_zombieId]; require(_isReady(myZombie)); _targetDna = _targetDna % dnaModulus; uint newDna = (myZombie.dna + _targetDna) / 2; if (keccak256(_species) == keccak256("kitty")) { newDna = newDna - newDna % 100 + 99; } _createZombie("NoName", newDna); _triggerCooldown(myZombie); } function feedOnKitty(uint _zombieId, uint _kittyId) public { uint kittyDna; (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId); feedAndMultiply(_zombieId, kittyDna, "kitty"); } }五、ERC721轉移標準
現在我們將通過學習把所有權從一個人轉移給另一個人來繼續我們的 ERC721 規范的實現。
注意 ERC721 規范有兩種不同的方法來轉移代幣:
function transfer(address _to, uint256 _tokenId) public; function approve(address _to, uint256 _tokenId) public; function takeOwnership(uint256 _tokenId) public;
1、第一種方法是代幣的擁有者調用transfer 方法,傳入他想轉移到的 address 和他想轉移的代幣的 _tokenId。
2、第二種方法是代幣擁有者首先調用 approve,然后傳入與以上相同的參數。接著,該合約會存儲誰被允許提取代幣,通常存儲到一個 mapping (uint256 => address) 里。然后,當有人調用 takeOwnership 時,合約會檢查 msg.sender 是否得到擁有者的批準來提取代幣,如果是,則將代幣轉移給他。
你注意到了嗎,transfer 和 takeOwnership 都將包含相同的轉移邏輯,只是以相反的順序。 (一種情況是代幣的發送者調用函數;另一種情況是代幣的接收者調用它)。
所以我們把這個邏輯抽象成它自己的私有函數 _transfer,然后由這兩個函數來調用它。 這樣我們就不用寫重復的代碼了。
實戰演練讓我們來定義 _transfer 的邏輯。
1、定義一個名為 _transfer的函數。它會需要3個參數:address _from、address _to和uint256 _tokenId。它應該是一個 私有 函數。
2、我們有2個映射會在所有權改變的時候改變: ownerZombieCount (記錄一個所有者有多少只僵尸)和 zombieToOwner (記錄什么人擁有什么)。
我們的函數需要做的第一件事是為 接收 僵尸的人(address _to)增 加ownerZombieCount。使用 ++ 來增加。
3、接下來,我們將需要為 發送 僵尸的人(address _from)減少ownerZombieCount。使用 -- 來扣減。
4、最后,我們將改變這個 _tokenId 的 zombieToOwner 映射,這樣它現在就會指向 _to。
5、騙你的,那不是最后一步。我們還需要再做一件事情。
ERC721規范包含了一個 Transfer 事件。這個函數的最后一行應該用正確的參數觸發Transfer ——查看 erc721.sol 看它期望傳入的參數并在這里實現。
zombieownership.zol
pragma solidity ^0.4.19; import "./zombieattack.sol"; import "./erc721.sol"; contract ZombieOwnership is ZombieAttack, ERC721 { function balanceOf(address _owner) public view returns (uint256 _balance) { return ownerZombieCount[_owner]; } function ownerOf(uint256 _tokenId) public view returns (address _owner) { return zombieToOwner[_tokenId]; } // 在這里定義 _transfer() function _transfer(address _from, address _to, uint256 _tokenId) private { /*錯誤的寫法 balanceOf(_to)++; balanceOf(_from)--; ownerOf(_tokenId); */ ownerZombieCount[_to]++; ownerZombieCount[_from]--; zombieToOwner[_tokenId] = _to; Transfer(_from, _to, _tokenId); } function transfer(address _to, uint256 _tokenId) public { } function approve(address _to, uint256 _tokenId) public { } function takeOwnership(uint256 _tokenId) public { } }
剛才那是最難的部分——現在實現公共的 transfer 函數應該十分容易,因為我們的 _transfer 函數幾乎已經把所有的重活都干完了。
實戰演練1、我們想確保只有代幣或僵尸的所有者可以轉移它。還記得我們如何限制只有所有者才能訪問某個功能嗎?
沒錯,我們已經有一個修飾符能夠完成這個任務了。所以將修飾符 onlyOwnerOf 添加到這個函數中。
2、現在該函數的正文只需要一行代碼。它只需要調用 _transfer。
記得把 msg.sender 作為參數傳遞進 address _from。
zombieownership.zol
pragma solidity ^0.4.19; import "./zombieattack.sol"; import "./erc721.sol"; contract ZombieOwnership is ZombieAttack, ERC721 { 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[_from]--; zombieToOwner[_tokenId] = _to; Transfer(_from, _to, _tokenId); } // 1. 在這里添加修飾符 function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { // 2. 在這里定義方法 _transfer(msg.sender, _to, _tokenId); } function approve(address _to, uint256 _tokenId) public { } function takeOwnership(uint256 _tokenId) public { } }六、ERC721之批準
現在,讓我們來實現 approve。
記住,使用 approve 或者 takeOwnership 的時候,轉移有2個步驟:
1、你,作為所有者,用新主人的 address 和你希望他獲取的 _tokenId 來調用 approve
2、新主人用 _tokenId 來調用 takeOwnership,合約會檢查確保他獲得了批準,然后把代幣轉移給他。
因為這發生在2個函數的調用中,所以在函數調用之間,我們需要一個數據結構來存儲什么人被批準獲取什么。
實戰演練1、首先,讓我們來定義一個映射 zombieApprovals。它應該將一個 uint 映射到一個 address。
這樣一來,當有人用一個 _tokenId 調用 takeOwnership 時,我們可以用這個映射來快速查找誰被批準獲取那個代幣。
2、在函數 approve 上, 我們想要確保只有代幣所有者可以批準某人來獲取代幣。所以我們需要添加修飾符 onlyOwnerOf 到 approve。
3、函數的正文部分,將 _tokenId 的 zombieApprovals 設置為和 _to 相等。
4、最后,在 ERC721 規范里有一個 Approval 事件。所以我們應該在這個函數的最后觸發這個事件。(參考 erc721.sol 來確認傳入的參數,并確保 _owner 是 msg.sender)
zombieownership.zol
pragma solidity ^0.4.19; import "./zombieattack.sol"; import "./erc721.sol"; contract ZombieOwnership is ZombieAttack, ERC721 { // 1. 在這里定義映射 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[_from]--; zombieToOwner[_tokenId] = _to; Transfer(_from, _to, _tokenId); } function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { _transfer(msg.sender, _to, _tokenId); } // 2. 在這里添加方法修飾符 function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { // 3. 在這里定義方法 zombieApprovals[_tokenId] = _to; Approval(msg.sender, _to, _tokenId); // 協議事件 } function takeOwnership(uint256 _tokenId) public { } }七、ERC721之takeOwnership
現在讓我們完成最后一個函數來結束 ERC721 的實現。
最后一個函數 takeOwnership, 應該只是簡單地檢查以確保 msg.sender 已經被批準來提取這個代幣或者僵尸。若確認,就調用 _transfer;
實戰演練1、首先,我們要用一個 require 句式來檢查 _tokenId 的 zombieApprovals 和 msg.sender 相等。
這樣如果 msg.sender 未被授權來提取這個代幣,將拋出一個錯誤。
2、為了調用 _transfer,我們需要知道代幣所有者的地址(它需要一個 _from 來作為參數)。幸運的是我們可以在我們的 ownerOf 函數中來找到這個參數。
所以,定義一個名為 owner 的 address 變量,并使其等于 ownerOf(_tokenId)。
3、最后,調用 _transfer, 并傳入所有必須的參數。(在這里你可以用 msg.sender 作為 _to, 因為代幣正是要發送給調用這個函數的人)。
注意: 我們完全可以用一行代碼來實現第2、3兩步。但是分開寫會讓代碼更易讀。一點個人建議 :)
zombieownership.zol
pragma solidity ^0.4.19; import "./zombieattack.sol"; import "./erc721.sol"; contract ZombieOwnership is ZombieAttack, ERC721 { 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[_from]--; 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); } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/24129.html
摘要:是企業與區塊鏈相遇的地方。的框架旨在成為開發區塊鏈解決方案的支柱。以太坊,主要是針對工程師使用進行區塊鏈以太坊開發的詳解。 如果你想將區塊鏈合并到一個Java項目中,現在我們來看看就是這個細分領域中三個最大的OSS玩家。 好的伙計們,我們都聽說過比特幣,以太坊或其他加密貨幣,其中有一些時髦的名字圍繞著我們常見的新聞,但我們作為Java開發人員知道如何輕松地與這些區塊鏈技術進行交互嗎?以...
摘要:我們目前正處于一個新興的區塊鏈開發行業中。,一種在以太坊開發人員中流行的新的簡單編程語言,因為它是用于開發以太坊智能合約的語言。它是全球至少萬開發人員使用的世界上最流行的編程語言之一。以太坊,主要是針對工程師使用進行區塊鏈以太坊開發的詳解。 我們目前正處于一個新興的區塊鏈開發行業中。區塊鏈技術處于初期階段,然而這種顛覆性技術已經成功地風靡全球,并且最近經歷了一場與眾不同的繁榮。由于許多...
摘要:目前市面上,凡是基于以太坊的令牌,在交易所上線交易的均是令牌,那么今天我們就來聊聊令牌的標準方案吧。 0x00 寫在前面 眾所周知,以太坊在現階段最大的應用就是令牌發行,而在以太坊中有很多類型的令牌,最著名的當屬ERC20了,但是對于其他幾種令牌類型,可能還有一些朋友不知道,所以最近規劃了一個系列,就是以太坊標準令牌系列。 目前市面上,凡是基于以太坊的令牌,在交易所上線交易的均是ERC...
摘要:合約安全增強溢出和下溢我們將來學習你在編寫智能合約的時候需要注意的一個主要的安全特性防止溢出和下溢。實戰演練給加上一些標簽把這里變成標準的注釋把一個管理轉移僵尸所有權的合約符合對標準草案的實現 通過上一節的學習,我們完成了 ERC721 的實現。并不是很復雜,對吧?很多類似的以太坊概念,當你只聽人們談論它們的時候,會覺得很復雜。所以最簡單的理解方式就是你自己來實現它。 一、預防溢出 不...
閱讀 2424·2021-11-23 10:04
閱讀 1494·2021-09-02 15:21
閱讀 892·2019-08-30 15:44
閱讀 1061·2019-08-30 10:48
閱讀 707·2019-08-29 17:21
閱讀 3554·2019-08-29 13:13
閱讀 1983·2019-08-23 17:17
閱讀 1784·2019-08-23 17:04