要旨
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関連のファイルで、青色がOpenzeppelinのユーティリティーです。
ERC1820Clientについては、こちらを参照してください。
ERC1400ERC20
これはERC1400をERC20との互換性を持たせるためのコントラクトです。
ERC20のインターフェースに基づいて(IERC20)、ERC1400内の関数を適宜呼び出しています。
このコントラクトのデプロイ時に渡す引数は、
-
string memory name
(ST名) -
string memory symbol
(STのシンボル) -
uint256 granularity
(トークンの最小単位) -
address[] memory controllers
(Controllerリスト) -
address certificateSigner
(トークン譲渡や償還時に必要な所有証明にサインするオフチェーンサービスのアドレス(ちょっとわからないです..) -
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とは異なり、mint
がissue
、burn
がredeem
とセマンティックなネーミングに変更されています。
そして、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
ここが特定のパーティション間での譲渡を行っている重要な関数です。
-
bytes32 fromPartition
パーティションの指定 -
address operator
関数を実行するアドレスが入ります -
address from
送り主 -
address to
送り先 -
uint256 value
送る量 -
bytes memory data
オフチェーン上で埋め込むbytesデータ。各自でCertificateController
を設計しその内部で使用される。 -
bytes memory operatorData
送り主及び送り先がERC1400TokensSender
またはERC1400TokensRecipient
をインターフェースに持つコントラクトをERC1820内に登録してある場合のみ、tokensToTransfer
関数またはtokensReveices
関数に渡される。 -
bool preventLocking
送り先のアドレスがERC1400TokensRecipient
をインターフェースに持つコントラクトをERC1820内に登録してある場合、この引数がERC1400TokensRecipient
のtokensReceived
関数に渡される。
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;
}
主にしていることは、
-
_removeTokenFromPartition
でパーティション内の合計発行数と送り主の残高を引く(_balanceOfByPartition[from][partition].sub(value)
&_totalSupplyByPartition[partition]
) -
_transferWithData
でパーティション内ではない、送り主と送り先の残高の引く(つまり_balances[from].sub(value)
) -
_addTokenToPartition
でパーティション内の合計発行数と送り先の残高を足す(_balanceOfByPartition[to][partition].add(value)
&_totalSupplyByPartition[partition].add(value)
パーティション機能のために、パーティション毎の残高と全体としての残高両方を同時に操作しています。
さいごに
冒頭でも言及したとおり、ERC1400はERC20に強制譲渡やパーティション機能などパワーアップしたものです。それに伴い、コードは少し複雑になっており、ごく一部の関数を抜粋して僭越ながら解説を書いてみました。(担当日を忘れて当日に急いで書いているからではない)
ERC1400をこれからコードリーディングする方のお役に立てれば幸いに思います。
間違い、指摘等ございましたらコメントいただけると嬉しいです。