2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

EthereumAdvent Calendar 2019

Day 21

ConsenSys製 セキュリティートークン ERC1400 の動作解説

Posted at

要旨

ConsenSysが作成したセキュリティートークン(ST)のフレームワークであるERC1400 (https://github.com/ConsenSys/ERC1400) の解説を行ないます。
なお、STO周りの法律に関する内容の正しさは保証できません。間違いの指摘や、関連して知っておくと良いことなどありましたら、コメント頂けると幸いです。

セキュリティートークンって?

そもそもセキュリティートークンって何なんでしょう?
ここでのセキュリティーとは、株や不動産などの証券を指します。それをブロックチェーン上でトークンという形で管理されたものがセキュリティートークンです。

では、株などを表現するなら、ERC20のFT(Fungible Token)でできそうじゃない? なぜST用のフレームワークがあるのでしょう?
理由は、株などの金融商品を取り扱う上で必要な、強制譲渡や償還などの法的措置に対応する必要があるからです。
従うべき法律は国によって異なりますが、ERC1400では以下の内容を必要事項として上げています。
https://github.com/ethereum/eips/issues/1411

MUST have a standard interface to query if a transfer would be successful and return a reason for failure.
譲渡が成功するか、また失敗時の理由を返す、照会インターフェースを持っていること。

MUST be able to perform forced transfer for legal action or fund recovery.
法的措置または返金のための強制譲渡が行えること。

MUST emit standard events for issuance and redemption.
発行と償還のためのイベントを発行すること。

MUST be able to attach metadata to a subset of a token holder's balance such as special shareholder rights or data for transfer restrictions.
特別株主権限(?)や譲渡制限のデータといった、トークン保有者の残高の一部にメタデータを埋め込むことができること。
(ちょっとよくわからないです...)

MUST be able to modify metadata at time of transfer based on off-chain data, on-chain data and the parameters of the transfer.
譲渡に関するパラメータや、オフチェーン、オンチェーン上での譲渡時にメタデータを改変できること。

MUST support querying and subscribing to updates on any relevant documentation for the security.
金融商品に関する書類の更新を照会、または通知登録をサポートすること。

(MUSTのみ抜粋)

また、同じ株を例にしても異なる種類があるようで、トークン毎に任意の種類を割り当てられるPartition機能(ERC1410)も実装されています。

要するに、従来のICOのように自由に取引ができるわけではなく、KYCやAMLを確認した者同士でのみ取引を許可する機能であったり、強制的な譲渡が可能になっているERC20のようなイメージになると思います。

それでは、どのようにそれらを実装したのか、見てみましょう。

コントラクト構成

以下の図が、ERC1400ERC20コントラクトの継承を表したものです。

ERC1400.jpg

オレンジ色がERC1400関連のファイルで、青色がOpenzeppelinのユーティリティーです。
ERC1820Clientについては、こちらを参照してください。

ERC1400ERC20

これはERC1400をERC20との互換性を持たせるためのコントラクトです。
ERC20のインターフェースに基づいて(IERC20)、ERC1400内の関数を適宜呼び出しています。

このコントラクトのデプロイ時に渡す引数は、

  1. string memory name (ST名)
  2. string memory symbol (STのシンボル)
  3. uint256 granularity (トークンの最小単位)
  4. address[] memory controllers (Controllerリスト)
  5. address certificateSigner (トークン譲渡や償還時に必要な所有証明にサインするオフチェーンサービスのアドレス(ちょっとわからないです..)
  6. bytes32[] memory tokenDefaultPartitions (Partitionのインデックス)

Partitionのインデックスとは、各パーティションにbytes32でIDを与えるようなものと思います。これを参照することで指定のパーティション間での譲渡などの操作が行なえます(ERC1400Parititon)。

特徴的なのは、_whitelisedで譲渡相手を制限しているところや、譲渡時の送り主が指定したOperatorであれば関数の実行が可能であったりするところでしょうか。


// Mapping from (tokenHolder) to whitelisted status.
mapping (address => bool) internal _whitelisted;

  /**
   * @dev Modifier to verify if sender and recipient are whitelisted.
   */
 modifier areWhitelisted(address sender, address recipient) {
    require(_whitelisted[sender], "A5"); // Transfer Blocked - Sender not eligible
    require(_whitelisted[recipient], "A6"); // Transfer Blocked - Receiver not eligible
    _;
 }

function transferFrom(address from, address to, uint256 value) external areWhitelisted(from, to) returns (bool) {
    require( _isOperator(msg.sender, from)
      || (value <= _allowed[from][msg.sender]), "A7"); // Transfer Blocked - Identity restriction

    if(_allowed[from][msg.sender] >= value) {
      _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value);
    } else {
      _allowed[from][msg.sender] = 0;
    }

    _transferByDefaultPartitions(msg.sender, from, to, value, "", "", false);
    return true;
}

ERC1400

ここでは主に、

  • 関連書類のgetとset
  • ERC1400Partition内の関数への適宜マッピング
  • 譲渡可能かどうかのチェック(canTransferByPartition)
    を行っていると言えます。

ERC20とは異なり、mintissueburnredeemとセマンティックなネーミングに変更されています。
そして、ERC1400の必要事項として挙げられている

MUST have a standard interface to query if a transfer would be successful and return a reason for failure.

canTransferByPartitionによって満たされています。

interface IERC1400  {

    // Document Management
    function getDocument(bytes32 name) external view returns (string memory, bytes32); // 1/9
    function setDocument(bytes32 name, string calldata uri, bytes32 documentHash) external; // 2/9
    event Document(bytes32 indexed name, string uri, bytes32 documentHash);

    // Controller Operation
    function isControllable() external view returns (bool); // 3/9

    // Token Issuance
    function isIssuable() external view returns (bool); // 4/9
    function issueByPartition(bytes32 partition, address tokenHolder, uint256 value, bytes calldata data) external; // 5/9
    event IssuedByPartition(bytes32 indexed partition, address indexed operator, address indexed to, uint256 value, bytes data, bytes operatorData);

    // Token Redemption
    function redeemByPartition(bytes32 partition, uint256 value, bytes calldata data) external; // 6/9
    function operatorRedeemByPartition(bytes32 partition, address tokenHolder, uint256 value, bytes calldata data, bytes calldata operatorData) external; // 7/9
    event RedeemedByPartition(bytes32 indexed partition, address indexed operator, address indexed from, uint256 value, bytes data, bytes operatorData);

    // Transfer Validity
    function canTransferByPartition(bytes32 partition, address to, uint256 value, bytes calldata data) external view returns (byte, bytes32, bytes32); // 8/9
    function canOperatorTransferByPartition(bytes32 partition, address from, address to, uint256 value, bytes calldata data, bytes calldata operatorData) external view returns (byte, bytes32, bytes32); // 9/9
}

ERC1400Partition

恐らくここが一番、複雑なロジックになっていると思います。
理由は、トークンを種類別に分割するPartition機能と、Partition毎にtransferやredeem等の操作を行う必要があるからです。


interface IERC1400Partition {

    // Token Information
    function balanceOfByPartition(bytes32 partition, address tokenHolder) external view returns (uint256); // 1/10
    function partitionsOf(address tokenHolder) external view returns (bytes32[] memory); // 2/10

    // Token Transfers
    function transferByPartition(bytes32 partition, address to, uint256 value, bytes calldata data) external returns (bytes32); // 3/10
    function operatorTransferByPartition(bytes32 partition, address from, address to, uint256 value, bytes calldata data, bytes calldata operatorData) external returns (bytes32); // 4/10

    // Default Partition Management
    function getDefaultPartitions() external view returns (bytes32[] memory); // 5/10
    function setDefaultPartitions(bytes32[] calldata partitions) external; // 6/10

    // Operators
    function controllersByPartition(bytes32 partition) external view returns (address[] memory); // 7/10
    function authorizeOperatorByPartition(bytes32 partition, address operator) external; // 8/10
    function revokeOperatorByPartition(bytes32 partition, address operator) external; // 9/10
    function isOperatorForPartition(bytes32 partition, address operator, address tokenHolder) external view returns (bool); // 10/10

    // Transfer Events
    event TransferByPartition(
        bytes32 indexed fromPartition,
        address operator,
        address indexed from,
        address indexed to,
        uint256 value,
        bytes data,
        bytes operatorData
    );

    event ChangedPartition(
        bytes32 indexed fromPartition,
        bytes32 indexed toPartition,
        uint256 value
    );

    // Operator Events
    event AuthorizedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder);
    event RevokedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder);

}

balanceOfByPartition

function balanceOfByPartition(bytes32 partition, address tokenHolder) external view returns (uint256) {
    return _balanceOfByPartition[tokenHolder][partition];
}

ある投資家(tokenHolder)のとあるpartitionの保有トークン数を返す。

partitionsOf

function partitionsOf(address tokenHolder) external view returns (bytes32[] memory) {
    return _partitionsOf[tokenHolder];
}

ある投資家の所有するPartitionのインデックス(bytes32)を返す

_transferByPartition

ここが特定のパーティション間での譲渡を行っている重要な関数です。

  1. bytes32 fromPartition パーティションの指定
  2. address operator 関数を実行するアドレスが入ります
  3. address from 送り主
  4. address to 送り先
  5. uint256 value 送る量
  6. bytes memory data オフチェーン上で埋め込むbytesデータ。各自でCertificateControllerを設計しその内部で使用される。
  7. bytes memory operatorData 送り主及び送り先がERC1400TokensSenderまたはERC1400TokensRecipientをインターフェースに持つコントラクトをERC1820内に登録してある場合のみ、tokensToTransfer関数またはtokensReveices関数に渡される。
  8. bool preventLocking 送り先のアドレスがERC1400TokensRecipientをインターフェースに持つコントラクトをERC1820内に登録してある場合、この引数がERC1400TokensRecipienttokensReceived関数に渡される。
function _transferByPartition(
    bytes32 fromPartition,
    address operator,
    address from,
    address to,
    uint256 value,
    bytes memory data,
    bytes memory operatorData,
    bool preventLocking
  )
    internal
    returns (bytes32)
  {
    require(_balanceOfByPartition[from][fromPartition] >= value, "A4"); // Transfer Blocked - Sender balance insufficient

    bytes32 toPartition = fromPartition;

    if(operatorData.length != 0 && data.length >= 64) {
      toPartition = _getDestinationPartition(fromPartition, data);
    }

    _removeTokenFromPartition(from, fromPartition, value);
    _transferWithData(fromPartition, operator, from, to, value, data, operatorData, preventLocking);
    _addTokenToPartition(to, toPartition, value);

    emit TransferByPartition(fromPartition, operator, from, to, value, data, operatorData);

    if(toPartition != fromPartition) {
      emit ChangedPartition(fromPartition, toPartition, value);
    }

    return toPartition;
  }

主にしていることは、

  1. _removeTokenFromPartitionでパーティション内の合計発行数と送り主の残高を引く(_balanceOfByPartition[from][partition].sub(value) & _totalSupplyByPartition[partition])
  2. _transferWithDataでパーティション内ではない、送り主と送り先の残高の引く(つまり_balances[from].sub(value)
  3. _addTokenToPartitionでパーティション内の合計発行数と送り先の残高を足す(_balanceOfByPartition[to][partition].add(value) & _totalSupplyByPartition[partition].add(value)

パーティション機能のために、パーティション毎の残高と全体としての残高両方を同時に操作しています。

さいごに

冒頭でも言及したとおり、ERC1400はERC20に強制譲渡やパーティション機能などパワーアップしたものです。それに伴い、コードは少し複雑になっており、ごく一部の関数を抜粋して僭越ながら解説を書いてみました。(担当日を忘れて当日に急いで書いているからではない)

ERC1400をこれからコードリーディングする方のお役に立てれば幸いに思います。

間違い、指摘等ございましたらコメントいただけると嬉しいです。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?