Skip to content

Commit

Permalink
Merge pull request CryptozombiesHQ#391 from mamiM/master
Browse files Browse the repository at this point in the history
Added JP L9
  • Loading branch information
hankxdev authored Jan 29, 2019
2 parents 2f60f2a + d24bd1d commit 6d3da69
Show file tree
Hide file tree
Showing 12 changed files with 3,501 additions and 0 deletions.
24 changes: 24 additions & 0 deletions jp/9/00-overview.md
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をゲームで使う方法を学んでいくからな。

準備はいいか?
303 changes: 303 additions & 0 deletions jp/9/01-erc721x.md

Large diffs are not rendered by default.

323 changes: 323 additions & 0 deletions jp/9/02-erc721x.md
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`を返却するようにすること。
Loading

0 comments on commit 6d3da69

Please sign in to comment.