初心者が一からブロックチェーンを活用したアプリケーション(Dapps)を作る(⑤ERC721トークン、セキュリティ続き編)
今回はDappsで運用するサービスによっては重要な要素であるトークンについてと、ユーザーが触るフロントエンドとDappsの連携方法について、最後にセキュリティの補足の説明をして行きます。
他の記事
第一回 環境構築編
第二回 コントラクト基礎編
第三回 コントラクト中級編
第四回 セキュリティ、ガスなど編
イーサリアムではよく知られている通貨としてのトークン(Fungible Tokenと言います。以下FT)であるERC20/223規格トークンがあります。こちらはICOなどでよく使われています。自分の10wei(etherの最小単位)と他の人の10weiは同じです。円とかドルみたいなもんです。
それに対して、2017年9月に新しいトークンの規格としてERC721が誕生しました。こちらのトークンは通貨としてのトークンとは違い、同じERC721規格を使ったCryptomonトークン(例であっても、自分の1Cryptomonと相手の1Cryptomonの価値は違います(Non Fungible Tokenと言います。以下NFT)。なぜかと言うと、ERC721トークンには複数のパラメーターを設定でき、そのパラメーターによって価値を決めるようにできるからです。ポ○モンの個体値のように、同じピカ○ュウでも違いが生まれ、それによって価値が変わります。この複数のパラメーターを持ったトークンが発行できることを利用し、ゲーム内の自分のキャラの権利書であったり、チケット売買サービス内でのチケットであったりをトークンとして取引することができます。
ちなみに2018年6月にERC1155により新しいトークン規格が成立しました。これは、ERC20/223トークンとERC721トークンの両方の利点を兼ね備え、通貨とキャラの権利書を同列に取引することができる規格です。今まではERC721トークンで表現されたキャラクターの権利書をERC20/223トークンで買うという形になっていました。規格の違うトークンを交換する際は一つのコントラクトでトラストレスに処理することはできず、スマートコントラクトの有用性の一つであるトラストレスな特性は利用できませんでした。しかし、ERC1155規格を使うことで、通貨とキャラの権利書を同じトークンで表現できることができ、トラストレスに取引することができます。この規格はDappsゲームで有名なEnjinのCTOであるRadmski氏が提唱しました(Dappsはゲーム界隈の勢いがすごいですね)。
ただ、ERC1155はまだOpenZeppelinなどでもまだサポートされておらず敷居が高いので、今回はERC721規格のトークンを実装していきます。
目次
① ERC721
② 複数継承
③ SafeMath
④ ERC721のメソッド
⑤ view修飾子によるガスの節約
pragma solidity ^0.4.19;
import "./monsterattack.sol";
import "./erc721.sol";
import "./safemath.sol";
contract MonsterOwnership is MonsterAttack, ERC721 { // ① ERC721 ② 複数継承
using SafeMath for uint256; // ③ SafeMath
mapping (uint => address) monsterApprovals;
function balanceOf(address _owner) public view returns (uint256 _balance) { // ④ ERC721のメソッド
return ownerMonsterCount[_owner];
}
function ownerOf(uint256 _tokenId) public view returns (address _owner) { // ④ ERC721のメソッド
return monsterToOwner[_tokenId];
}
function _transfer(address _from, address _to, uint256 _tokenId) private {
ownerMonsterCount[_to] = ownerMonsterCount[_to].add(1); // ③ SafeMath
ownerMonsterCount[msg.sender] = ownerMonsterCount[msg.sender].sub(1); // ③ SafeMath
monsterToOwner[_tokenId] = _to;
Transfer(_from, _to, _tokenId);
}
function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { ④ ERC721のメソッド
_transfer(msg.sender, _to, _tokenId);
}
function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { ④ ERC721のメソッド
monsterApprovals[_tokenId] = _to;
Approval(msg.sender, _to, _tokenId);
}
function takeOwnership(uint256 _tokenId) public { ④ ERC721のメソッド
require(monsterApprovals[_tokenId] == msg.sender);
address owner = ownerOf(_tokenId);
_transfer(owner, msg.sender, _tokenId);
}
}
① ERC721
import "./erc721.sol";
contract MonsterOwnership is MonsterAttack, ERC721 { // ① ERC721
イーサリアム上のトークンとは、スマートコントラクト上に記録されたユーザーのアドレスに紐ついた残高情報と、スマートコントラクト上に実装されているトークンの取引機能によって成り立っています。
同じ関数セット、名前を使用しているため別の種類のトークンでもERC20/223であれば相互に交換できるわけです。この手法はERC721でも有効で、同じERC721の関数セットを実装していれば、自分が作ったトークンを他の人が作ったプラットフォームでも動作させることができます。
そこで、セキュリティ関連のライブラリであるOpenZeppelinのERC721実装コントラクトを継承して、ERC721規格コントラクトを実装しています。
② 複数継承
import "./monsterattack.sol";
import "./erc721.sol";
contract MonsterOwnership is MonsterAttack, ERC721 { // ② 複数継承
簡単に継承と言いましたが、MonsterOwnershipコントラクトは今まで作ってきたコントラクトを多数継承してきたMonsterAttackコントラクトも継承しなければいけません。MonsterAttackコントラクトとERC721コントラクトの両方を継承したいわけですが、実はSolidityではコントラクトの複数継承がサポートされています。(菱形継承問題とかは気をつけてくださいね)上のように書きます。
③ SafeMath
using SafeMath for uint256; // ③ SafeMath
紹介したコントラクトではOpenZeppelinのSafeMathというライブラリを使用しています。このライブラリを使うことによって計算のオーバーフロー対策をすることができます。(対策をしないと1111…1+1が0になってしまいます。お気をつけください)
solidityでのライブラリは特別なタイプのコントラクトとして実装されます。データ型へ関数の付加をすることができます。
ownerMonsterCount[_to] = ownerMonsterCount[_to].add(1); // ③ SafeMath
ownerMonsterCount[msg.sender] = ownerMonsterCount[msg.sender].sub(1); // ③ SafeMath
今回は上のように+1をadd(1),-1をsub(1)というように変更しています。今まで紹介したコントラクトのコードも変更しましょう。
④ ERC721のメソッド
mapping (uint => address) monsterApprovals;
function balanceOf(address _owner) public view returns (uint256 _balance) { // ④ ERC721のメソッド
return ownerMonsterCount[_owner];
}
function ownerOf(uint256 _tokenId) public view returns (address _owner) { // ④ ERC721のメソッド
return monsterToOwner[_tokenId];
}
function _transfer(address _from, address _to, uint256 _tokenId) private {
ownerMonsterCount[_to] = ownerMonsterCount[_to].add(1);
ownerMonsterCount[msg.sender] = ownerMonsterCount[msg.sender].sub(1);
monsterToOwner[_tokenId] = _to;
Transfer(_from, _to, _tokenId);
}
function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { ④ ERC721のメソッド
_transfer(msg.sender, _to, _tokenId);
}
function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { ④ ERC721のメソッド
monsterApprovals[_tokenId] = _to;
Approval(msg.sender, _to, _tokenId);
}
function takeOwnership(uint256 _tokenId) public { ④ ERC721のメソッド
require(monsterApprovals[_tokenId] == msg.sender);
address owner = ownerOf(_tokenId);
_transfer(owner, msg.sender, _tokenId);
}
ERC721規格に乗っ取り、他のコントラクトと連携するためには、ERC721トークン標準の関数を実装していなければいけません。
・balanceOf(アドレスを受け取り保有するトークン量を返す)
・ownerOf(トークンIDを受け取りオーナーのアドレスを返す)
・transfer (送り先のアドレスと送りたいトークンのIDを受け取りトークンを交換しTransferイベントを送る)
・approve(送り先のアドレスと送りたいトークンのIDを受け取りそれをmappingに格納しApprovalイベントを返す)
・takeOwnership(トークンIDを受け取り、approveで格納されたmappingの情報を元にトークンを交換しTransferイベントを送る)
を実装しましょう。transferとtakeownershipは同じトークン送信ロジックなので_transferとして共通化しています。(以前のソースコードでownerOfという関数を実装してしまっているので、以前の方を直しましょう。こちらの方を直すとERC721規格に則らなくなってしまいます。)
今回はトークンの実装とセキュリティの補足の説明をしました。
書いているのは初心者のため、間違い、浅い理解などあるかもしれません。その場合はぜひ指摘してもらえればと思います。喜んで修正します。
参考にしたもの:
↑ゲーム感覚でスマートコントラクトの書き方が学べます。
↑ブロックチェーンで開発する際必要なことはだいたい網羅されていると思います。とても勉強になります。
この記事を書いた人:
株式会社Calano
インターン 島村大
学部4年生で大学では機械学習と数理最適化、CalanoではアプリやGCPなどを担当。最近ブロックチェーン始めました。
この記事が気に入ったらサポートをしてみませんか?