2018.01.11

ERC721 トークンとERC20 の交換


こんにちは。次世代システム研究室のT.M. です。

はじめに

CryptoKitties というEthereum 上で動作するサービスが一時期大流行しました。このサービスでは、ERC721 で規格されているトークンが用いて、子猫の所有権を管理している。本投稿では、ERC721 の解説およびその応用として開発したERC721 とERC20 の交換サービスを紹介する。

ERC721

ICO で利用されているERC20 では、どの1トークンも同じ価値であり、主に通貨のように利用されるトークンであるが、ERC721 は1トークンごとに情報を記録することができる、主にモノの所有権を管理するためのトークンである。

ERC20 のインターフェースと比較して説明する。
contract ERC721Interface {
    function totalSupply() public constant returns (uint totalSupply);
    function balanceOf(address _owner) public constant returns (uint balance);
    function tokensOfOwnerByIndex(address _owner, uint _index) public constant returns (uint tokenId);
    function ownerOf(uint _tokenId) public constant returns (address owner);
    function transfer(address _to, uint _tokenId) public returns (bool success);
    function approve(address _to, uint _tokenId) public returns (bool success);
    function transferFrom(address _from, address _to, uint _tokenId) public returns (bool success);
    event Transfer(address indexed _from, address indexed _to, uint _tokenId);
    event Approval(address indexed _owner, address indexed _spender, uint _tokenId);
}
contract ERC20Interface  {
    function totalSupply() public constant returns (uint totalSupply);
    function balanceOf(address _owner) public constant returns (uint balance);
    function transfer(address _to, uint _value) public returns (bool success);
    function transferFrom(address _from, address _to, uint _value) public returns (bool success);
    function approve(address _spender, uint _value) public returns (bool success);
    function allowance(address _owner, address _spender) public constant returns (uint remaining);
    event Transfer(address indexed _from, address indexed _to, uint _value);
    event Approval(address indexed _owner, address indexed _spender, uint _value);
}
ERC721 では、トークン1枚ずつにID があり、所有権のあるトークンのID を計算するtokensOfOwnerByIndex やそのトークンの所有者を計算するownerOf といった関数が追加されている。
また、トークンの送付では、ERC20 では、パラメータに宛先アドレスおよび送付量であったが、ERC721 では、宛先アドレスとトークンID となっている。
totalSupply およびbalanceOf はほぼ同様である。

トレードサービス

Z.com Cloud ブロックチェーンでは、OSS 提供プロジェクトの一つとして、ERC20 のトークン同士の交換をするトークントレーダーを提供致しました。本記事では、トークントレーダーを拡張して、ERC721 トークンとの交換を可能にしました。今回開発したソースコードは、https://github.com/miyao-gmo/oss-token-trader にあります。

トークン


ERC712 では、以下のようなフィールドを持つ。今回作成したトークンは、トークンにHoge という意味のない情報を持たせているが、本来は意味のある情報を持つ構造体を作成し、その配列がトークンとなる。tokenIndexToOwner によりそのトークンID の所有者を、ownershipTokenCount により所有者が所持しているトークン量を、tokenToApproved によりそのトークンのtransferFrom 行う権利のあるアドレスの管理を行っている。
contract ERC721TokenSample is ERC721TokenInterface {
...
    struct Hoge {
        uint fuga;
    }

    Hoge[] public tokens;
    mapping(uint => address) public tokenIndexToOwner;
    mapping(address => uint) public ownershipTokenCount;
    mapping(uint => address) public tokenToApproved;
...
}

トークンの作成は以下のように行う。今回はコントラクトの所有者のみがトークンを作成できるようにし、作成したトークンはコントラクトの所有者に所有権を持たせた。
    function createInternal(address _from, uint _param) private returns (bool success) {
        if (_from != contractOwner) return false;
        Hoge memory h = Hoge(_param);
        tokens.push(h);
        tokenIndexToOwner[tokens.length - 1] = contractOwner;
        ownershipTokenCount[contractOwner]++;
        return true;
    }

送付


その所有権の移行により、トークンの送付を行う。ERC20 では、トークン送付の際のallowance の変更は、transferFrom のときだけであり、transfer では不要であるが、ERC721 では、transfer であってもallowance の変更が必要である。allowance の変更をしないと、所有権が移った際、新しい所有者がapprove していなくても、approve された状態になってしまう。
    function transferInternal(address _from, address _to, uint _tokenId) private returns (bool success) {
        if (tokenIndexToOwner[_tokenId] == _from) {
            ownershipTokenCount[_from]--;
            tokenIndexToOwner[_tokenId] = _to;
            ownershipTokenCount[_to]++;
            delete tokenToApproved[_tokenId];
            Transfer(_from, _to, _tokenId);
            return true;
        }
        return false;
    }

トレード


ERC20 同士の交換とほとんど変わらず、事前にapprove をしておき、transferFrom を両トークンで行う。ただし、ERC721 はmaker のトークンにしかなりえない。なぜならば、ERC721 はユニークであるため、maker が交換先のトークンを指定することができないためである。
    function tradeWithERC721(
        address _makerTokenAddr, uint _makerTokenId, address _makerAddress, address _takerTokenAddr, uint _takerAmount,
        uint _expiration, uint _tradeNonce, bytes _takerSign, bytes _makerSign) public returns (bool success) {

        bytes32 hash = calcEnvHash('tradeWithERC721');
        hash = keccak256(hash, _makerTokenAddr);
        hash = keccak256(hash, _makerTokenId);
        hash = keccak256(hash, _makerAddress);
        hash = keccak256(hash, _takerTokenAddr);
        hash = keccak256(hash, _takerAmount);
        hash = keccak256(hash, _expiration);
        hash = keccak256(hash, _tradeNonce);
        address takerAddress = recoverAddress(hash, _takerSign);

        hash = keccak256(hash, _takerSign);
        address makerAddress = recoverAddress(hash, _makerSign);

        if (makerAddress != _makerAddress || _tradeNonce != getNonce(makerAddress, takerAddress) || _expiration < now) return false;
        nonces[makerAddress][takerAddress]++;

        assert(ERC721Interface(_makerTokenAddr).transferFrom(makerAddress, takerAddress, _makerTokenId) && ERC20Interface(_takerTokenAddr).transferFrom(takerAddress, makerAddress, _takerAmount));
        return true;
    }


デモ


ERC20 トークンの作成には、シンボル名、トークン名の他に初期供給量を入力する。ERC721 トークンの作成には、シンボル名とトークン名のみを入力する。

作成されたトークンが表示される。

送付先アカウントおよび送付量を入力して、ERC20 トークンをトレーダーA に送付する。

ERC721 トークンは1枚ずつ発行する必要があり、発行ボタンを押して発行する。

ERC721 トークンを2枚発行されたことが表示されている。

送付先アカウントおよび送付するトークンを選択して、ERC721 トークンをトレーダーB に送付する。

トレーダーB の現在の状態を確認すると、トークンID:0 のERC721 トークンを所有しており、ERC20 トークンが0枚所有していることがわかる。

トレードを許容するトークンID を入力する。

値段を入力して売りに出す。

トレーダーを変更して、トレーダーA の現在の状態を確認する。ERC721 トークンは所有しておらず、ERC20 トークンを1000枚所有していることがわかる。

トレードを許容するトークン量を入力する。

購入を押してトークンを購入すると、トークンの保有中にトークンID:0 が追加され、ERC20 トークンの所有量が10枚減った。


さいごに

ERC721 というモノの所有権を管理する規格の調査・検証のために、所有権の売買をするコントラクトを作成しました。ERC721 はまだドラフトであるため、規格が変更する可能性があるが、ERC20 と共通な箇所が多く、ERC20 を理解している人にとって、とてもわかりやすい規格である。モノの所有権を管理するサービスは様々あるため、とても多くの応用が考えられる。


次世代システム研究室では、グループ全体のインテグレーションを支援してくれるアーキテクトを募集しています。インフラ設計、構築経験者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ募集職種一覧からご応募をお願いします。

皆さんのご応募をお待ちしています。