Ethereumはユーザビリティーが大事だ。


こんにちは、Stakedの渡辺創太です。今回はユーザビリティに関して書きます。

ユーザビリティーにおける課題

ブロックチェーンではスケーリングはプライバシーの問題が多く議論されますが、UIとUXを含めたユーザビリティーも大変大きな問題です。例えば、Ethereum上で発行されたトークンであるERC20 (DAIとかZRXとか)を送る時にgasと言われる手数料が必要です。これはEthereumの独自通貨であるEtherで支払われます。

たとえばZRXを送りたいとしましょう。Metamaskのこの画面に見える通り、トランザクション手数料がEtherで表示されています。

これだとERC20トークンやERC721デジタル・アセットを持っているけど、Etherを持っていない人は残高不足となり取引を行うことができません。VitalikがEthereum Researchでも述べているようにERC20でgasを支払うことは複雑な工数が必要で難しくなっています。

もう1つ分散型取引所のプロトコルを作っている0xを例をあげます。以下はオーダーのフォーマットですが、makerFeetakerFeeはZRXでリレイヤーに支払われます。過去の0xに関する記事は僕の他のアウトプットから参照してください

現在多くのリレイヤーは手数料をとっていないので、ZRXを持っていなくてもトレーディングはできますが、手数料を取る場合、ZRXをもっていないと手数料がとれないモデルはインセンティブ設計として上手くありません。ZRXはガバナンストークンとしての役割があり、ZRXを使っている人、もっている人が0xのガバナンスをするように設計しています。気持ちはわかりますがユーザビリティーとしては良くないと思います。

提案されている解決策

この問題に対してEIP(Ethereum Improvement Proposal)の856番で提案があったので簡単に紹介したいと思います。コミュニティ内で議論されており改善もされているのですがここではオリジナルのアイデアを使いたいと思います。

仕様

  • A: 送信者
  • B: 受信者
  • D: 委任者
  • X: AからBに送られるトークンの量
  • Y: AからDに送られる手数料
  • T: トークン
  • N: ナンス
  1. ERC20トークンを送信したいがETHを持っていないユーザーAはユーザーBに送金するために委託者D(デリゲート)が設定したレートに基づいて送信したいERC20トークンとETH gas料金と同量のERC20を上乗せして委託者Dに送金します。
  2. ユーザーAは自身のプライベートキーを元に楕円暗号曲線で生成した値(V,R,S)とP(ナンス, Snederのアドレス, Recipientのアドレス, Delegateのアドレス, AがDに上乗せするトークン, AからBに送られるトークン, 送るトークン)をDに送ります。
  3. Dはそれらを用いトランザクションが途中で改ざんされていないかを検証します。
  4. Dは自身のアカウントからEthereumネットワークにトランザクションを提出します。具体的にはDはT.delegatedTransfer(N,A,B,X,Y,V,R,S)コマンドを叩く。
  5. Ethereum上でトランザクションが自動執行されることによって、AからB、AからDに自動送金される。

メソッド

委託者によってオンチェーントランザクションが行われる時にcallされる関数

transferPreSigned
function transferPreSigned(
    bytes _signature,
    address _to,
    uint256 _value,
    uint256 _fee,
    uint256 _nonce
)
    public
    returns (bool);

TransactionReceiptのlogsにEventをかきこむ時に使用するevent

event TransferPreSigned(address indexed from, address indexed to, address indexed delegate, uint256 amount, uint256 fee);

オンチェーン上の処理

    function transferPreSigned(
        //オーナーによって生成される署名のバイトキー
        bytes _signature,
        //送信したい相手先のアドレス
        address _to,
        //送金したい値
        uint256 _value,
     //トークンオーナーによって委託者に支払われる値段
        uint256 _fee,
     //ナンス
        uint256 _nonce
    )
        public
        returns (bool)
    {
     //GOXを防ぐ
        require(_to != address(0));
        require(signatures[_signature] == false);
    
        //送信をハッシュ化
        bytes32 hashedTx = transferPreSignedHashing(address(this), _to, _value, _fee, _nonce);
        //ハッシュの復号化から検証
        address from = recover(hashedTx, _signature);
        require(from != address(0));

     //送った後の処理
        balances[from] = balances[from].sub(_value).sub(_fee);
        balances[_to] = balances[_to].add(_value);
        balances[msg.sender] = balances[msg.sender].add(_fee);
        signatures[_signature] = true;

        Transfer(from, _to, _value);
        Transfer(from, msg.sender, _fee);
        TransferPreSigned(from, _to, msg.sender, _value, _fee);
        return true;
    }

    function transferPreSignedHashing(
        address _token,
        address _to,
        uint256 _value,
        uint256 _fee,
        uint256 _nonce
    )
        public
        pure
        returns (bytes32)
    {
        /* "48664c16": transferPreSignedHashing(address,address,address,uint256,uint256,uint256) */
        return keccak256(bytes4(0x48664c16), _token, _to, _value, _fee, _nonce);
    }