forked from CryptozombiesHQ/cryptozombie-lessons
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request CryptozombiesHQ#391 from mamiM/master
Added JP L9
- Loading branch information
Showing
12 changed files
with
3,501 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
--- | ||
title: "ERC721x: マルチファンジブルトークン" | ||
header: "ERC721x: マルチファンジブルトークン" | ||
roadmap: roadmap.jpg | ||
|
||
--- | ||
|
||
ようこそクリプトゾンビたち! | ||
|
||
この中級レッスンでは、 **ERC721xのマルチファンジブルトークン** を学んでいくぞ。 | ||
|
||
マルチファンジブルトークンとは何かって? | ||
|
||
ERC721のような不代替トークンでは、全アイテムは100%ユニークなものとなっている。しかしマルチファンジブルトークンは、多くのゲームで *同じアイテムを複数コピーしたもの* が必要になると見越したものになっているのだ。 | ||
|
||
例えば、お主のMaster Swordと他の人のMaster Swordが全く同じものだとすると — それらは同じ見た目で、同じダメージを与え、全く同じ機能を持っていることになるな。こういうアイテムは「ファンジブル(代替可能)」と呼ぶことができる。なぜならアイテムに違いはなく、どっちが自分のMaster Swordかを気にせずにトレードをすることができるからな。 | ||
|
||
もちろん常にこれが当てはまるわけではなく、アイテムによってはユニークな存在にしておきたいものもあるだろう。 | ||
|
||
そういうわけで、ERC721xは柔軟にどちらも扱うことができるものになっている — さらにファンジブルも非ファンジブルも、ゲーム内の全アイテムを単一のスマートコントラクト内で管理することができるのだ。しかも最高なことに、ERC721との完全な後方互換性を持っている。 | ||
|
||
このレッスンでは、ERC721xをゲームで使う方法を学んでいくからな。 | ||
|
||
準備はいいか? |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,323 @@ | ||
--- | ||
title: 基本的関数のオーバーライド | ||
actions: ['答え合わせ', 'ヒント'] | ||
requireLogin: true | ||
material: | ||
editor: | ||
language: sol | ||
startingCode: | ||
"ZombieCard.sol": | | ||
pragma solidity ^0.4.25; | ||
import "./ERC721XToken.sol"; | ||
contract ZombieCard is ERC721XToken { | ||
// ここから始めるのだ | ||
} | ||
"ERC721XToken.sol": | | ||
// Full implementation with all included files at https://github.com/loomnetwork/erc721x | ||
pragma solidity ^0.4.25; | ||
import "./../../Interfaces/ERC721X.sol"; | ||
import "./../../Interfaces/ERC721XReceiver.sol"; | ||
import "./ERC721XTokenNFT.sol"; | ||
import "openzeppelin-solidity/contracts/AddressUtils.sol"; | ||
import "./../../Libraries/ObjectsLib.sol"; | ||
// Additional features over NFT token that is compatible with batch transfers | ||
contract ERC721XToken is ERC721X, ERC721XTokenNFT { | ||
using ObjectLib for ObjectLib.Operations; | ||
using AddressUtils for address; | ||
bytes4 internal constant ERC721X_RECEIVED = 0x660b3370; | ||
bytes4 internal constant ERC721X_BATCH_RECEIVE_SIG = 0xe9e5be6a; | ||
event BatchTransfer(address from, address to, uint256[] tokenTypes, uint256[] amounts); | ||
modifier isOperatorOrOwner(address _from) { | ||
require((msg.sender == _from) || operators[_from][msg.sender], "msg.sender is neither _from nor operator"); | ||
_; | ||
} | ||
function implementsERC721X() public pure returns (bool) { | ||
return true; | ||
} | ||
/** | ||
* @dev transfer objects from different tokenIds to specified address | ||
* @param _from The address to BatchTransfer objects from. | ||
* @param _to The address to batchTransfer objects to. | ||
* @param _tokenIds Array of tokenIds to update balance of | ||
* @param _amounts Array of amount of object per type to be transferred. | ||
* Note: Arrays should be sorted so that all tokenIds in a same bin are adjacent (more efficient). | ||
*/ | ||
function _batchTransferFrom(address _from, address _to, uint256[] _tokenIds, uint256[] _amounts) | ||
internal | ||
isOperatorOrOwner(_from) | ||
{ | ||
// Requirements | ||
require(_tokenIds.length == _amounts.length, "Inconsistent array length between args"); | ||
require(_to != address(0), "Invalid recipient"); | ||
if (tokenType[_tokenIds[0]] == NFT) { | ||
tokenOwner[_tokenIds[0]] = _to; | ||
emit Transfer(_from, _to, _tokenIds[0]); | ||
} | ||
// Load first bin and index where the object balance exists | ||
(uint256 bin, uint256 index) = ObjectLib.getTokenBinIndex(_tokenIds[0]); | ||
// Balance for current bin in memory (initialized with first transfer) | ||
// Written with bad library syntax instead of as below to bypass stack limit error | ||
uint256 balFrom = ObjectLib.updateTokenBalance( | ||
packedTokenBalance[_from][bin], index, _amounts[0], ObjectLib.Operations.SUB | ||
); | ||
uint256 balTo = ObjectLib.updateTokenBalance( | ||
packedTokenBalance[_to][bin], index, _amounts[0], ObjectLib.Operations.ADD | ||
); | ||
// Number of transfers to execute | ||
uint256 nTransfer = _tokenIds.length; | ||
// Last bin updated | ||
uint256 lastBin = bin; | ||
for (uint256 i = 1; i < nTransfer; i++) { | ||
// If we're transferring an NFT we additionally should update the tokenOwner and emit the corresponding event | ||
if (tokenType[_tokenIds[i]] == NFT) { | ||
tokenOwner[_tokenIds[i]] = _to; | ||
emit Transfer(_from, _to, _tokenIds[i]); | ||
} | ||
(bin, index) = _tokenIds[i].getTokenBinIndex(); | ||
// If new bin | ||
if (bin != lastBin) { | ||
// Update storage balance of previous bin | ||
packedTokenBalance[_from][lastBin] = balFrom; | ||
packedTokenBalance[_to][lastBin] = balTo; | ||
// Load current bin balance in memory | ||
balFrom = packedTokenBalance[_from][bin]; | ||
balTo = packedTokenBalance[_to][bin]; | ||
// Bin will be the most recent bin | ||
lastBin = bin; | ||
} | ||
// Update memory balance | ||
balFrom = balFrom.updateTokenBalance(index, _amounts[i], ObjectLib.Operations.SUB); | ||
balTo = balTo.updateTokenBalance(index, _amounts[i], ObjectLib.Operations.ADD); | ||
} | ||
// Update storage of the last bin visited | ||
packedTokenBalance[_from][bin] = balFrom; | ||
packedTokenBalance[_to][bin] = balTo; | ||
// Emit batchTransfer event | ||
emit BatchTransfer(_from, _to, _tokenIds, _amounts); | ||
} | ||
function batchTransferFrom(address _from, address _to, uint256[] _tokenIds, uint256[] _amounts) public { | ||
// Batch Transfering | ||
_batchTransferFrom(_from, _to, _tokenIds, _amounts); | ||
} | ||
/** | ||
* @dev transfer objects from different tokenIds to specified address | ||
* @param _from The address to BatchTransfer objects from. | ||
* @param _to The address to batchTransfer objects to. | ||
* @param _tokenIds Array of tokenIds to update balance of | ||
* @param _amounts Array of amount of object per type to be transferred. | ||
* @param _data Data to pass to onERC721XReceived() function if recipient is contract | ||
* Note: Arrays should be sorted so that all tokenIds in a same bin are adjacent (more efficient). | ||
*/ | ||
function safeBatchTransferFrom( | ||
address _from, | ||
address _to, | ||
uint256[] _tokenIds, | ||
uint256[] _amounts, | ||
bytes _data | ||
) | ||
public | ||
{ | ||
// Batch Transfering | ||
_batchTransferFrom(_from, _to, _tokenIds, _amounts); | ||
// Pass data if recipient is contract | ||
if (_to.isContract()) { | ||
bytes4 retval = ERC721XReceiver(_to).onERC721XBatchReceived( | ||
msg.sender, _from, _tokenIds, _amounts, _data | ||
); | ||
require(retval == ERC721X_BATCH_RECEIVE_SIG); | ||
} | ||
} | ||
function transfer(address _to, uint256 _tokenId, uint256 _amount) public { | ||
_transferFrom(msg.sender, _to, _tokenId, _amount); | ||
} | ||
function transferFrom(address _from, address _to, uint256 _tokenId, uint256 _amount) public { | ||
_transferFrom(_from, _to, _tokenId, _amount); | ||
} | ||
function _transferFrom(address _from, address _to, uint256 _tokenId, uint256 _amount) | ||
internal | ||
isOperatorOrOwner(_from) | ||
{ | ||
require(tokenType[_tokenId] == FT); | ||
require(_amount <= balanceOf(_from, _tokenId), "Quantity greater than from balance"); | ||
require(_to != address(0), "Invalid to address"); | ||
_updateTokenBalance(_from, _tokenId, _amount, ObjectLib.Operations.SUB); | ||
_updateTokenBalance(_to, _tokenId, _amount, ObjectLib.Operations.ADD); | ||
emit TransferWithQuantity(_from, _to, _tokenId, _amount); | ||
} | ||
function safeTransferFrom(address _from, address _to, uint256 _tokenId, uint256 _amount) public { | ||
safeTransferFrom(_from, _to, _tokenId, _amount, ""); | ||
} | ||
function safeTransferFrom(address _from, address _to, uint256 _tokenId, uint256 _amount, bytes _data) public { | ||
_transferFrom(_from, _to, _tokenId, _amount); | ||
require( | ||
checkAndCallSafeTransfer(_from, _to, _tokenId, _amount, _data), | ||
"Sent to a contract which is not an ERC721X receiver" | ||
); | ||
} | ||
function _mint(uint256 _tokenId, address _to, uint256 _supply) internal { | ||
// If the token doesn't exist, add it to the tokens array | ||
if (!exists(_tokenId)) { | ||
tokenType[_tokenId] = FT; | ||
allTokens.push(_tokenId); | ||
} else { | ||
// if the token exists, it must be a FT | ||
require(tokenType[_tokenId] == FT, "Not a FT"); | ||
} | ||
_updateTokenBalance(_to, _tokenId, _supply, ObjectLib.Operations.ADD); | ||
emit TransferWithQuantity(address(this), _to, _tokenId, _supply); | ||
} | ||
function checkAndCallSafeTransfer( | ||
address _from, | ||
address _to, | ||
uint256 _tokenId, | ||
uint256 _amount, | ||
bytes _data | ||
) | ||
internal | ||
returns (bool) | ||
{ | ||
if (!_to.isContract()) { | ||
return true; | ||
} | ||
bytes4 retval = ERC721XReceiver(_to).onERC721XReceived( | ||
msg.sender, _from, _tokenId, _amount, _data); | ||
return(retval == ERC721X_RECEIVED); | ||
} | ||
} | ||
answer: > | ||
pragma solidity ^0.4.25; | ||
import "./ERC721XToken.sol"; | ||
contract ZombieCard is ERC721XToken { | ||
function name() external view returns (string) { | ||
return "ZombieCard"; | ||
} | ||
function symbol() external view returns (string) { | ||
return "ZCX"; | ||
} | ||
} | ||
--- | ||
|
||
`ERC721XToken.sol`を継承しているから、`transfer`や`balanceOf`といった必要な基本の関数はすでに定義済みになっている。 | ||
|
||
ERC721xのインターフェースはこんな感じになっている。興味があるなら見てみよう: | ||
|
||
``` | ||
contract ERC721X { | ||
function implementsERC721X() public pure returns (bool); | ||
function ownerOf(uint256 _tokenId) public view returns (address _owner); | ||
function balanceOf(address owner) public view returns (uint256); | ||
function balanceOf(address owner, uint256 tokenId) public view returns (uint256); | ||
function tokensOwned(address owner) public view returns (uint256[], uint256[]); | ||
function transfer(address to, uint256 tokenId, uint256 quantity) public; | ||
function transferFrom(address from, address to, uint256 tokenId, uint256 quantity) public; | ||
// Fungible Safe Transfer From | ||
function safeTransferFrom(address from, address to, uint256 tokenId, uint256 _amount) public; | ||
function safeTransferFrom(address from, address to, uint256 tokenId, uint256 _amount, bytes data) public; | ||
// Batch Safe Transfer From | ||
function safeBatchTransferFrom(address _from, address _to, uint256[] tokenIds, uint256[] _amounts, bytes _data) public; | ||
function name() external view returns (string); | ||
function symbol() external view returns (string); | ||
// Required Events | ||
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); | ||
event TransferWithQuantity(address indexed from, address indexed to, uint256 indexed tokenId, uint256 quantity); | ||
event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); | ||
event BatchTransfer(address indexed from, address indexed to, uint256[] tokenTypes, uint256[] amounts); | ||
} | ||
``` | ||
|
||
ERC721xであると宣言しているトークンは全て、これら全メソッドを実装していることが保証されている。ERC721xトークン向けに作られたウォレットやマーケットプレイスは、それらメソッドの呼び出しが可能なのだ(さらにすでに説明したように、これはERC721トークン基準と完全な後方互換性を持っている)。 | ||
|
||
`ERC721XToken.sol`をインポートしているから、これらすべての関数、つまりトークンの送信やユーザー間の移転を承認するロジックの全てはすでに実装済みとなっている。 | ||
|
||
このレッスンでは、どんなカードがゲームに存在し、それらがどのように生成されユーザーに配られるのか、といったことについてのルールの設定についてフォーカスして行くぞ。 | ||
|
||
だがまずは、作成したゲームアイテムでオーバーライドが必要になるであろう最も基本的な関数から始めていこう。 | ||
|
||
### `name` | ||
|
||
ウォレットや取引所がどのゲーム用のアイテムか判るように、ERC721xトークンに名前をつける必要があるだろう。 | ||
|
||
``` | ||
function name() external view returns (string) { | ||
return "ExampleCard"; | ||
} | ||
``` | ||
|
||
これはトークン名を返すだけのシンプルな関数だ。 | ||
|
||
### `symbol` | ||
|
||
トークンは、ウォレットやマーケットプレイス、取引所で使うことのできるシンボルも持っていなくてはならない。 | ||
|
||
``` | ||
function symbol() external view returns (string) { | ||
return "ECX"; | ||
} | ||
``` | ||
|
||
この関数もシンプルなもので、ティッカーシンボルを返却するだけのものだ。ティッカーシンボルは3文字で、全て大文字となっている。 | ||
|
||
# さあテストだ | ||
|
||
早速この2つの関数をZombieCardで実装してみるのだ。 | ||
|
||
1. `name`という関数を作成せよ。これは`external view`関数で、"ZombieCard"という`string`を返却するようにすること。 | ||
|
||
2. `symbol`という名の関数を作成せよ。`external view`関数で、"ZCX"という`string`を返却するようにすること。 |
Oops, something went wrong.