はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

今回は、スイスの主要な金融機関法に準拠したERC20トークンの仕組みを提案しているERC2980についてまとめていきます!

以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。

他にも様々なEIPについてまとめています。

概要

ERC2980は、ERC20互換のトークンでありながら、スイスの主要な金融関連法に準拠するように設計されています。 具体的には、以下の法律への対応が含まれます。

  • スイス証券取引法(Stock Exchange Act)
  • 銀行法(Banking Act)
  • 金融市場インフラ法(Financial Market Infrastructure Act)
  • 集団投資スキーム法(Act on Collective Investment Schemes)
  • マネーロンダリング防止法(Anti-Money Laundering Act)

さらに、金融サービス法(Financial Services Act)および金融機関法(Financial Institutions Act)も考慮に含まれており、スイスだけでなくEU法域にも適合しています。

このトークン標準は「アセットトークン(またはセキュリティトークン)」と呼ばれる新しい資産に対応しています。 これらのトークンは、発行および取引の過程で証券の所有権を管理する手段を提供します。 特に、以下のような機能的制限が特徴です。

  • 発行者(Issuer)のみがホワイトリスト管理を行える
  • 発行者のみがフリーズ(凍結)やリボーク(取り消し)処理を実行できる

動機

スイスの金融市場監督機構(FINMA)は、2018年2月16日に発表したICOガイドラインにおいて、「アセットトークン(Asset Token)」を資産または相対的な権利を表すトークンと定義しました。 具体的には、株式・債券・デリバティブに相当または類似するものとして位置づけられています。

この定義に基づき、アセットトークンの発行や運用においては、ペイメントトークン(決済用)やユーティリティトークン(利用権)のような比較的単純な仕組みでは対応できません。 より複雑かつ厳密な規制対応が求められ、多くの金融法制との整合性が必要となります。

そのため、ERC2980は、金融法に準拠しつつブロックチェーン上で証券のように機能するトークンを安全かつ合法的に取り扱えるようにするためのものです。

仕様

ERC2980コントラクトインターフェース

ERC2980.sol
interface ERC2980 extends ERC20 {
  
  /// @dev This emits when funds are reassigned
  event FundsReassigned(address from, address to, uint256 amount);

  /// @dev This emits when funds are revoked
  event FundsRevoked(address from, uint256 amount);

  /// @dev This emits when an address is frozen
  event FundsFrozen(address target);

  /**
  * @dev getter to determine if address is in frozenlist
  */
  function frozenlist(address _operator) external view returns (bool);

  /**
  * @dev getter to determine if address is in whitelist
  */
  function whitelist(address _operator) external view returns (bool);

}

FundsReassigned

javascript
event FundsReassigned(address from, address to, uint256 amount);

資産が再割り当てされた時に発行されるイベント。 このイベントは、資産の所有者が変更された場合、つまり特定のアドレスから別のアドレスへと資産を移転させたときに発行されます。 これは一般のtransferとは異なり、発行者による強制的な再割当て操作です。

パラメータ

  • from
    • 元の資産保有者のアドレス。
  • to
    • 新しい資産保有者のアドレス。
  • amount
    • 割り当てられたトークンの数量。

FundsRevoked

javascript
event FundsRevoked(address from, uint256 amount);

資産が没収(取り消し)された時に発行されるイベント。 このイベントは、ホワイトリスト外の不正使用や規制違反などによって、特定のアドレスからトークンが取り消された場合に発行されます。 発行体による強制的な取り消しに使用される処理です。

パラメータ

  • from
    • 没収された資産保有者のアドレス。
  • amount
    • 没収されたトークンの数量。

FundsFrozen

javascript
event FundsFrozen(address target);

アドレスが凍結されたときに発行されるイベント。 このイベントは、特定のアドレスに対してトークンの送受信を停止する処理が実行された際に発行されます。 例えば、マネーロンダリング対策や裁判所命令などが原因で凍結されることがあります。

パラメータ

  • target
    • 凍結対象のアドレス。

frozenlist

javascript
function frozenlist(address _operator) external view returns (bool);

指定したアドレスが凍結リストに含まれているかを確認する関数。 アドレスが現在トークン送受信を禁止されている状態かどうかを外部から確認できます。 返り値がtrueなら、そのアドレスは凍結されていることを意味します。

引数

  • _operator
    • 凍結されているか確認したいアドレス。

戻り値

  • bool
    • trueの場合は凍結中、falseは凍結されていない状態。

whitelist

javascript
function whitelist(address _operator) external view returns (bool);

指定したアドレスがホワイトリストに含まれているかを確認する関数。 この関数は、特定のアドレスが資産の送受信に許可されているか(すなわち発行者により承認されているか)を確認するために使われます。 ホワイトリストにないアドレスは、トークンの受け取りや送信など一部の操作が制限されます。

引数

  • _operator
    • ホワイトリストに登録されているかを確認したいアドレス。

戻り値

  • bool
    • trueの場合はホワイトリストに登録されている、falseは登録されていない状態。

注意点

このERC2980規格では、資産トークンが分割不可能(indivisible)であるという特性に基づき、トークンの小数点以下の単位(decimals)は常に0である必要があります。 これは証券などのアセットの扱いと同様で、例えば株式1.25株のような表現を許容しない構造です。

ホワイトリスト・凍結リスト

ERC2980は、スイスの金融法に準拠するために設計されたもので、アドレス管理の2つのリスト(ホワイトリストと凍結リスト)を利用して、トークンの転送制御を実現しています。 いずれのリストも、発行者(Issuer)という特別なロールを持つオペレーターのみが操作できます。

Whitelist(ホワイトリスト)

ホワイトリストに含まれるアドレスは、他のアドレスからトークンを受け取ることができます。 一方、ホワイトリストから削除されたアドレスは、それ以降の受け取りはできませんが、既に保有しているトークンの送信は可能です。 つまり、受信に制限があるが送信には制限がないリストです。

Frozenlist(凍結リスト)

凍結リストに含まれるアドレスは、送信も受信も不可能です。 完全にトークンの移動がブロックされるため、マネーロンダリング防止や裁判所命令などの法的対応に利用されます。

Whitelistable.sol
Interface Whitelistable {

  /**
   * @dev add an address to the whitelist
   * Throws unless `msg.sender` is an Issuer operator
   * @param _operator address to add
   * @return true if the address was added to the whitelist, false if the address was already in the whitelist
   */
  function addAddressToWhitelist(address _operator) external returns (bool);

  /**
   * @dev remove an address from the whitelist
   * Throws unless `msg.sender` is an Issuer operator
   * @param _operator address to remove
   * @return true if the address was removed from the whitelist, false if the address wasn't in the whitelist in the first place
   */
  function removeAddressFromWhitelist(address _operator) external returns (bool);

}

Interface Freezable {

  /**
   * @dev add an address to the frozenlist
   * Throws unless `msg.sender` is an Issuer operator
   * @param _operator address to add
   * @return true if the address was added to the frozenlist, false if the address was already in the frozenlist
   */
  function addAddressToFrozenlist(address _operator) external returns (bool);

  /**
   * @dev remove an address from the frozenlist
   * Throws unless `msg.sender` is an Issuer operator
   * @param _operator address to remove
   * @return true if the address was removed from the frozenlist, false if the address wasn't in the frozenlist in the first place
   */
  function removeAddressFromFrozenlist(address _operator) external returns (bool);

}

addAddressToWhitelist

javascript
function addAddressToWhitelist(address _operator) external returns (bool);

アドレスをホワイトリストに追加する関数。 この関数は、指定されたアドレスをホワイトリストに追加し、トークンの受信が許可されるようになります。 呼び出しにはIssuerロールを持つ必要があります。

引数

  • _operator
    • ホワイトリストに追加する対象のアドレス。

戻り値

  • bool
    • 追加に成功した場合はtrue、すでにホワイトリストに登録されていた場合はfalse。

removeAddressFromWhitelist

javascript
function removeAddressFromWhitelist(address _operator) external returns (bool);

アドレスをホワイトリストから削除する関数。 この関数は、指定されたアドレスをホワイトリストから削除します。 削除後、そのアドレスはトークンを新たに受け取ることができなくなります。 既に所有しているトークンは送信可能です。

引数

  • _operator
    • ホワイトリストから削除する対象のアドレス。

戻り値

  • bool
    • 削除に成功した場合はtrue、そもそもリストに存在しなかった場合はfalse。

addAddressToFrozenlist

javascript
function addAddressToFrozenlist(address _operator) external returns (bool);

アドレスを凍結リストに追加する関数。 この関数は、指定されたアドレスを完全に凍結状態にします。 トークンの送信も受信も行えなくなります。 呼び出しにはIssuerロールが必要です。

引数

  • _operator
    • 凍結リストに追加する対象のアドレス。

戻り値

  • bool
    • 追加に成功した場合はtrue、すでに凍結リストに登録されていた場合はfalse。

removeAddressFromFrozenlist

javascript
function removeAddressFromFrozenlist(address _operator) external returns (bool);

アドレスを凍結リストから解除する関数。 この関数は、指定されたアドレスを凍結リストから除外します。 これにより、トークンの送信・受信が再び可能になります。呼び出しにはIssuerロールが必要です。

引数

  • _operator
    • 凍結リストから削除する対象のアドレス。

戻り値

  • bool
    • 削除に成功した場合はtrue、もともと凍結リストに存在していなかった場合はfalse。

Issuers

Issuer(発行者)は、ホワイトリストおよび凍結リストの管理、トークンの取り消し(revoke)や再割当て(reassign)といった権限を持つ特権的なロールです。 Issuerは複数存在しても構いません。

Issuerの追加・削除はコントラクトのオーナーによって管理されます。 また、Issuerは自身のロールを別のアドレスに譲渡することも可能です。 コントラクトのデプロイ時または直後に、オーナー自身をIssuerとして登録することもできます。

この設計により、スイスの金融規制に適合しつつ、ガバナンスや運用上の柔軟性も確保されています。

Issuable.sol
Interface Issuable {

  /**
   * @dev getter to determine if address has issuer role
   */
  function isIssuer(address _addr) external view returns (bool);

  /**
   * @dev add a new issuer address
   * Throws unless `msg.sender` is the contract owner
   * @param _operator address
   * @return true if the address was not an issuer, false if the address was already an issuer
   */
  function addIssuer(address _operator) external returns (bool);

  /**
   * @dev remove an address from issuers
   * Throws unless `msg.sender` is the contract owner
   * @param _operator address
   * @return true if the address has been removed from issuers, false if the address wasn't in the issuer list in the first place
   */
  function removeIssuer(address _operator) external returns (bool);

  /**
   * @dev Allows the current issuer to transfer its role to a newIssuer
   * Throws unless `msg.sender` is an Issuer operator
   * @param _newIssuer The address to transfer the issuer role to
   */
  function transferIssuer(address _newIssuer) external;

}

isIssuer

javascript
function isIssuer(address _addr) external view returns (bool);

指定したアドレスがIssuerかどうかを確認する関数。 この関数は、指定されたアドレスが現在Issuerとしてのロールを持っているかどうかを確認します。 外部から呼び出すことで、そのアドレスが管理権限を持っているかどうかを判断できます。

引数

  • _addr
    • Issuerかどうかを確認したいアドレス。

戻り値

  • bool
    • true:Issuerである場合。
    • false:Issuerでない場合。

addIssuer

javascript
function addIssuer(address _operator) external returns (bool);

新しいIssuerを追加する関数。 この関数は、コントラクトのオーナーによって新しいIssuerアドレスを追加する際に使用されます。 すでにIssuerに登録されているアドレスを再登録することはできません。

引数

  • _operator
    • Issuerとして追加するアドレス。

戻り値

  • bool
    • true:新規にIssuerとして追加された場合。false:すでにIssuerであった場合。

removeIssuer

javascript
function removeIssuer(address _operator) external returns (bool);

指定したIssuerを取り除く関数。 この関数は、指定されたアドレスをIssuerから除外します。 オーナーが呼び出す必要があり、ロールの管理における重要な手段です。

引数

  • _operator
    • Issuerから削除したいアドレス。

戻り値

  • bool
    • true:Issuerから正常に削除された場合。
    • false:もともとIssuerでなかった場合。

transferIssuer

javascript
function transferIssuer(address _newIssuer) external;

現在のIssuerが、自分のロールを他のアドレスに移す関数。 Issuer自身がこの関数を使って、自分のロールを別のアドレスへ譲渡します。 ガバナンス上の柔軟性を確保しつつ、発行体が責任を持ってロール移行できる仕組みです。

引数

  • _newIssuer
    • Issuerロールを譲渡する新しいアドレス。

RevokeとReassign

RevokeおよびReassignは、Issuer(発行者)が特定のアドレスからトークンを強制的に移動させるための特権的操作です。 これらの操作は、たとえ対象のアドレスが凍結リスト(Frozenlist)に含まれていても実行できます。

Revoke

指定されたアドレスの全トークン残高を、呼び出したIssuerのアドレスに転送します。 主に没収(取り消し)の目的で使用され、違反者や不正利用者から資産を強制的に取り上げるために用いられます。

Reassign

指定されたアドレスの全トークン残高を、別の指定されたアドレスに転送します。 資産の所有権を強制的に移転する手段として使われ、司法的判断や契約違反への対応などに活用されます。

両方の関数は、Issuerロールを持つアドレスのみが実行可能である必要があります。

RevokableAndReassignable.sol
Interface RevokableAndReassignable {

  /**
   * @dev Allows the current Issuer to transfer token from an address to itself
   * Throws unless `msg.sender` is an Issuer operator
   * @param _from The address from which the tokens are withdrawn
   */
  function revoke(address _from) external;

  /**
   * @dev Allows the current Issuer to transfer token from an address to another
   * Throws unless `msg.sender` is an Issuer operator
   * @param _from The address from which the tokens are withdrawn
   * @param _to The address who receives the tokens
   */
  function reassign(address _from, address _to) external;

}

revoke

javascript
function revoke(address _from) external;

対象アドレスの残高をIssuer自身に転送する関数。 この関数は、指定されたアドレスからその保有するトークン全額を、関数を呼び出したIssuer自身のアドレスに移動させます。 凍結状態のアドレスにも適用可能で、制裁措置やトークンの失効処理に利用されます。

引数

  • _from
    • トークン残高を回収する対象のアドレス。

reassign

javascript
function reassign(address _from, address _to) external;

対象アドレスの残高を別のアドレスに転送する関数。 この関数は、指定されたアドレスからその保有するトークン全額を、別の指定先アドレスに移動させます。 契約不履行などの際に、トークンの所有権を強制的に移転するために使用されます。 Issuerによる呼び出しのみが許可されます。

引数

  • _from
    • トークン残高を移動させる元のアドレス。
  • _to
    • トークンを受け取る新しいアドレス。

補足

現在のところ、証券法や関連規制に適合することを明示的にサポートするトークン標準は存在していません。 例えば、ERC1404(Simple Restricted Token Standard)は、スイス金融市場監督機構(FINMA)の要求に完全には応えられません。

スイス法においては、発行者がトークンの移転に対して制限を設け、それを実行できる手段として「フリーズ」機能が必須とされています。 さらに、トークンは「リボーク可能(revocable)」である必要があり、AML/KYC対応のためにホワイトリスト方式の導入も求められています。

そのため、ERC2980では「Issuer」という強力な管理ロールを導入し、法的要件に対応できる柔軟なトークン管理を可能にしています。

互換性

ERC2980は既存のERC20規格との互換性を保っています。 具体的には、以下の標準的なERC20関数をそのまま実装可能です。

  • transfer
  • transferFrom
  • approve
  • allowance

さらに、decimals関数も互換性のために実装できますが、その場合は常に「0」を返す必要があります(アセットトークンは分割不可であるため)。 ERC2980により、従来のERC20ベースのインフラやウォレットともスムーズに連携することが可能です。

セキュリティ

ERC2980のセキュリティ上の最大の焦点は、Issuerの権限の強さにあります。 Issuerは通常のERC20には存在しない特殊なロールであり、以下のような強力な操作を行えます。

  • 任意のアドレスのトークンを所有せずに移転できる。
  • アドレスを凍結(フリーズ)し、トークンの送受信を完全に停止できる。

このような強力な操作権限が悪用されることを防ぐためには、コントラクトのオーナーが適切にIssuerを管理する責任を負う必要があります。

  • Issuerとして指定するアドレスを厳格に選定すること。
  • Issuerのロールを不要になった場合には即時に剥奪すること。

これにより、不正なトークン操作や資産の不当な凍結・移転などを防止することができます。 セキュリティの確保は、技術的制御だけでなく、運用上のガバナンス設計にも強く依存します。

引用

Gianluca Perletti (@Perlets9), Alan Scarpellini (@alanscarpellini), Roberto Gorini (@robertogorini), Manuel Olivi (@manvel79), "ERC-2980: Swiss Compliant Asset Token [DRAFT]," Ethereum Improvement Proposals, no. 2980, September 2020. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-2980.

最後に

今回は「スイスの主要な金融機関法に準拠したERC20トークンの仕組みを提案しているERC2980」についてまとめてきました! いかがだったでしょうか?

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!

他の媒体でも情報発信しているのでぜひ他も見ていってください!