イーサリアムスマートコントラクト:トークンを株式として構築

現在、仮想通貨の誇大広告の波が広がり、非常に疑わしいプロジェクトや分散化やその他の基本的なブロックチェーンの原則とは関係のないプロジェクトなど、さまざまなプロジェクトの一連の成功したICOがあります。 ICOの期間中、一部の仮想エンティティ(トークン)が一般に販売されます。 原則として、これらのトークンに実際の「値」を入力することは、プロジェクトごとに一意です。 この記事のフレームワークでは、これらのトークンの所有者が、発行全体からのトークンの割合に比例してプロジェクトから配当を受け取ると主張する場合、トークンを「株式」として構成することを検討します。 これにより多くの法的矛盾と不確実性が生じるため、今日、投資家向けにこの論理的で理解可能なモデルに基づいて構築された単一の大規模なプロジェクトはありませんが、法的側面を箱から取り出して技術的な実装のみに焦点を当てます。







トークンの構造化について説明するには、まず、トークンの少なくともいくつかの基本的な実装が必要です。 Solidity言語のフリルのない平均トークンの契約リストを以下に示します。



pragma solidity ^0.4.0; contract Token { string public standard = 'Token 0.1'; string public name; //!< name for display purporses string public symbol; //!< symbol for display purporses uint8 public decimals; //!< amount of decimals for display purporses mapping (address => uint256) public balanceOf; //!< array of all balances mapping (address => mapping (address => uint256)) public allowed; uint256 totalTokens; function Token( uint256 initialSupply, string tokenName, uint8 decimalUnits, string tokenSymbol) { totalTokens = initialSupply; balanceOf[msg.sender] = initialSupply; name = tokenName; symbol = tokenSymbol; decimals = decimalUnits; } // @brief Send coins // @param _to recipient of coins // @param _value amount of coins for send function transfer(address _to, uint256 _value) { if (balanceOf[msg.sender] < _value || _value <= 0) throw; balanceOf[msg.sender] -= _value; balanceOf[_to] += _value; } // @brief Send coins // @param _from source of coins // @param _to recipient of coins // @param _value amount of coins for send function transferFrom(address _from, address _to, uint256 _value) { if (balanceOf[_from] < _value || _value <= 0) throw; if (allowed[_from][msg.sender] < _value) throw; balanceOf[_from] -= _value; balanceOf[_to] += _value; allowed[_from][msg.sender] -= _value; } // @brief Allow another contract to spend some tokens in your behalf // @param _spender another contract address // @param _value amount of approved tokens function approve(address _spender, uint256 _value) { allowed[msg.sender][_spender] = _value; } // @brief Get allowed amount of tokens // @param _owner owner of allowance // @param _spender spender contract // @return the rest of allowed tokens function allowance(address _owner, address _spender) constant returns (uint256 remaining) { return allowed[_owner][_spender]; } }
      
      





契約コードからわかるように、すべてのトークンの発行は、契約がブロックチェーンにアップロードされるときに実行され、発行されたすべてのトークンは、このダウンロードを実行したアドレスの残りに記録されます。 次に、所有者間でトークンを移動する標準機能、transferとtransferFromが実装され、現在の残高がbalanceOfカードに保存されます。



お金(エーテル、エーテル)がこの契約のアカウントにどのように入金するかは考えません。契約のアドレスに直接送金されるか、トークンアプリケーションに特定性と機能性を与えるために追加実装できる機能を介してそこに到達するかは関係ありません。 契約this.balanceのゼロ以外の残高があることが重要です。これは、各保有者の発行全体のトークンの割合に比例して、トークンの保有者間で完全に分配する必要があります。



古典的なアルゴリズムプログラミングの観点から見ると、タスクは初歩的なように見え、擬似コードの形式では次のように見えます。



 function divideUpReward() { for (holder in balanceOf) { uint256 reward = this.balance * holder.value / totalTokens; holder.key.send(reward); } }
      
      





残念ながら、この擬似コードはSolidity言語で実装できません。 マッピングデータ構造は反復可能ではなく、そのすべての要素を調べる方法はありません。 この問題はイーサリアムのフォーラムで繰り返し議論されてきましたが、これが行われる主な論点は、それが非常に高価だということです。 分散EVM仮想マシンでスマートコントラクトが実行されることを思い出してください。 各フルノードで実行され、他の人のコンピューティングリソースを費やすため、私たちはそれにお金を払わなければなりません、そして私たちがより多くの操作をするほど、これらの操作を引き起こす人に支払われる手数料は大きくなります。 この場合、divideUpReward()を呼び出す人が支払います。



引き続き固執してこの擬似コードをSolidityに実装しようとすると、独自の「バイク」を発明して、マッピングの反復可能なアナログを作成できます。 このような実装の例は公開されています( こちら )が、divideUpReward()関数の実行コストが高いという問題は解決しません。 さらに、ethers holder.key.send(報酬)をすべてのトークン所有者に送信するトランザクションに対して支払うすべての費用も負担します。



あなた自身の配当を受け取るためのすべてのコミッションをトークン所有者に直接転送したいという論理的な欲求があります-最後に、これは彼がコミッションを支払っても彼の報酬です。



こんなふうにしてみませんか?



 mapping (address => uint256) balanceOfOld; uint totalReward; uint lastDivideRewardTime; function divideUpReward() public { // prevent call if less than 30 days passed from previous one if (lastDivideRewardTime + 30 days > now) throw; // make copy of mapping balanceOfOld = balanceOf; // reward is contract's balance totalReward = this.balance; lastDivideRewardTime = now; } // this can be called by token's holders function withdrawReward() public returns(uint) { uint256 tokens = balanceOfOld[msg.sender]; if (tokens > 0) { uint amount = totalReward * tokens / totalTokens; if (!msg.sender.send(amount)) return 0; balanceOfOld[msg.sender] = 0; return amount; } return 0; }
      
      





このコードはすでにはるかに良く見えます、なぜなら サイクルが含まれていません! 契約の所有者または他のイーサリアムユーザーとして、パブリック関数splitUpReward()を呼び出します。これは、呼び出し時に配当を取得し、トークンの現在の配布でコンテナーのコピーを作成し、契約の現在の残高を記憶し、トークン所有者間の配布にすべてを提供します。 同時に、divideUpReward()への最後の呼び出しの時刻を記憶し、30日間の繰り返し呼び出しを防ぐことにより、トークン所有者がパブリック報酬()関数を介して配当を撤回できるようにします。 保有者が指定された期間内に配当を撤回しなかった場合、それらは共通バスケットに戻され、次の期間内にすべての保有者間で分配できるようになります。



withdrawReward()関数はすでにトークン所有者によって直接呼び出されているため、アドレスへの資金の送信に関連するすべての料金を支払うのはトークン所有者です。 Solidity言語の観点から受け入れられない構造、つまり「balanceOfOld = balanceOf」が含まれていない場合に見つかった解決策を喜んでいただけます。マッピングのコピーの作成はSolidityでは提供されません。 しかし、そうなると仮定したとしても、そのようなコピーのコストは限界において非常に高価になると予想することは論理的です。 それでも同じように、隠されているがマップのすべての要素を循環していると想定していました。



特定のトークン所有者のアクションに応じて動的に満たされる追加のコンテナを導入することにより、明示的なコピーの操作を排除しようとします。



  uint totalReward; uint lastDivideRewardTime; uint restReward; struct TokenHolder { uint256 balance; uint balanceUpdateTime; uint rewardWithdrawTime; } mapping(address => TokenHolder) holders; function reward() constant public returns(uint) { if (holders[msg.sender].rewardWithdrawTime >= lastDivideRewardTime) { return 0; } uint256 balance; if (holders[msg.sender].balanceUpdateTime <= lastDivideRewardTime) { balance = balanceOf[msg.sender]; } else { balance = holders[msg.sender].balance; } return totalReward * balance / totalTokens; } function withdrawReward() public returns(uint) { uint value = reward(); if (value == 0) { return 0; } if (!msg.sender.send(value)) { return 0; } if (balanceOf[msg.sender] == 0) { // garbage collector delete holders[msg.sender]; } else { holders[msg.sender].rewardWithdrawTime = now; } return value; } // Divide up reward and make it accessible for withdraw function divideUpReward() public { if (lastDivideRewardTime + 30 days > now) throw; lastDivideRewardTime = now; totalReward = this.balance; restReward = this.balance; } function beforeBalanceChanges(address _who) public { if (holders[_who].balanceUpdateTime <= lastDivideRewardTime) { holders[_who].balanceUpdateTime = now; holders[_who].balance = balanceOf[_who]; } }
      
      





関数beforeBalanceChanged(アドレス_who)に注意する必要があります。これは、マップマッピングをコピーするだけの機能に取って代わります。 特定の住所の残高を変更する前に、この関数の呼び出しをソース関数transferおよびtransferFromのコントラクトに追加する必要があります。 この機能は、配当の引き出し期間を確定した後にトークンの移動が実行されることを確認し、報酬の分配期間中、特定のトークン保有者の残高を維持します。 特定の所有者の残高が変更された場合にのみ、アイテムごとにbalanceOfのコピーを作成します。



上記のすべてを組み合わせると、トークンを発行するスマートコントラクトの次のテキストを取得し、その後の配当計算でトークンを株式として構成します。



 pragma solidity ^0.4.0; contract Token { string public standard = 'Token 0.1'; string public name; //!< name for display purporses string public symbol; //!< symbol for display purporses uint8 public decimals; //!< amount of decimals for display purporses mapping (address => uint256) public balanceOf; //!< array of all balances mapping (address => mapping (address => uint256)) public allowed; uint256 totalTokens; uint totalReward; uint lastDivideRewardTime; uint restReward; struct TokenHolder { uint256 balance; uint balanceUpdateTime; uint rewardWithdrawTime; } mapping(address => TokenHolder) holders; function Token( uint256 initialSupply, string tokenName, uint8 decimalUnits, string tokenSymbol) { totalTokens = initialSupply; balanceOf[msg.sender] = initialSupply; name = tokenName; symbol = tokenSymbol; decimals = decimalUnits; } // @brief Send coins // @param _to recipient of coins // @param _value amount of coins for send function transfer(address _to, uint256 _value) { if (balanceOf[msg.sender] < _value || _value <= 0) throw; beforeBalanceChanges(msg.sender); beforeBalanceChanges(_to); balanceOf[msg.sender] -= _value; balanceOf[_to] += _value; } // @brief Send coins // @param _from source of coins // @param _to recipient of coins // @param _value amount of coins for send function transferFrom(address _from, address _to, uint256 _value) { if (balanceOf[_from] < _value || _value <= 0) throw; if (allowed[_from][msg.sender] < _value) throw; beforeBalanceChanges(_from); beforeBalanceChanges(_to); balanceOf[_from] -= _value; balanceOf[_to] += _value; allowed[_from][msg.sender] -= _value; } // @brief Allow another contract to spend some tokens in your behalf // @param _spender another contract address // @param _value amount of approved tokens function approve(address _spender, uint256 _value) { allowed[msg.sender][_spender] = _value; } // @brief Get allowed amount of tokens // @param _owner owner of allowance // @param _spender spender contract // @return the rest of allowed tokens function allowance(address _owner, address _spender) constant returns (uint256 remaining) { return allowed[_owner][_spender]; } function reward() constant public returns(uint) { if (holders[msg.sender].rewardWithdrawTime >= lastDivideRewardTime) { return 0; } uint256 balance; if (holders[msg.sender].balanceUpdateTime <= lastDivideRewardTime) { balance = balanceOf[msg.sender]; } else { balance = holders[msg.sender].balance; } return totalReward * balance / totalTokens; } function withdrawReward() public returns(uint) { uint value = reward(); if (value == 0) { return 0; } if (!msg.sender.send(value)) { return 0; } if (balanceOf[msg.sender] == 0) { // garbage collector delete holders[msg.sender]; } else { holders[msg.sender].rewardWithdrawTime = now; } return value; } // Divide up reward and make it accesible for withdraw function divideUpReward() public { if (lastDivideRewardTime + 30 days > now) throw; lastDivideRewardTime = now; totalReward = this.balance; restReward = this.balance; } function beforeBalanceChanges(address _who) public { if (holders[_who].balanceUpdateTime <= lastDivideRewardTime) { holders[_who].balanceUpdateTime = now; holders[_who].balance = balanceOf[_who]; } } }
      
      





株式として構造化されたトークンの発行の法的側面は議論の範囲を超えているため、示された技術的解決策の適用は、ICOのそのような契約を撤回する人の完全かつ完全な責任であることに留意してください。



Ethereumのスマートコントラクトの開発に関するトピックの役立つリンク:






All Articles