전 회사에서 STO(Secutiry Token Offering)에 관해서 프로젝트를 진행했는데, 아직 모르고 있는 점이 많아서 따로 정리를 해 둬야겠다는 생각이 들었다. 그래서 이 문서를 작성한다.
Security Token Offering (ERC-1400)
ERC-1400은 ERC-20이나 ERC-777과 같은 기존 토큰 표준들과 호환성을 가지고 있음 ERC-1400의 하위 구성 ERC표준 4가지는 아래와 같다.
-
ERC-1410(Partial Fungible Token) 소유자의 토큰을 Partition 세트로 구성하기 위한 표준 인터페이스이다.
/// @title ERC-1410 Partially Fungible Token Standard /// @dev See https://github.com/SecurityTokenStandard/EIP-Spec interface IERC1410 { // Token Information function balanceOf(address _tokenHolder) external view returns (uint256); function balanceOfByPartition(bytes32 _partition, address _tokenHolder) external view returns (uint256); function partitionsOf(address _tokenHolder) external view returns (bytes32[]); function totalSupply() external view returns (uint256); // Token Transfers function transferByPartition(bytes32 _partition, address _to, uint256 _value, bytes _data) external returns (bytes32); function operatorTransferByPartition(bytes32 _partition, address _from, address _to, uint256 _value, bytes _data, bytes _operatorData) external returns (bytes32); function canTransferByPartition(address _from, address _to, bytes32 _partition, uint256 _value, bytes _data) external view returns (byte, bytes32, bytes32); // Operator Information function isOperator(address _operator, address _tokenHolder) external view returns (bool); function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) external view returns (bool); // Operator Management function authorizeOperator(address _operator) external; function revokeOperator(address _operator) external; function authorizeOperatorByPartition(bytes32 _partition, address _operator) external; function revokeOperatorByPartition(bytes32 _partition, address _operator) external; // Issuance / Redemption function issueByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _data) external; function redeemByPartition(bytes32 _partition, uint256 _value, bytes _data) external; function operatorRedeemByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _operatorData) external; // Transfer Events event TransferByPartition( bytes32 indexed _fromPartition, address _operator, address indexed _from, address indexed _to, uint256 _value, bytes _data, bytes _operatorData ); // Operator Events event AuthorizedOperator(address indexed operator, address indexed tokenHolder); event RevokedOperator(address indexed operator, address indexed tokenHolder); event AuthorizedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); event RevokedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder); // Issuance / Redemption Events event IssuedByPartition(bytes32 indexed partition, address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData); event RedeemedByPartition(bytes32 indexed partition, address indexed operator, address indexed from, uint256 amount, bytes operatorData); }- 토큰이 partition 단위로 그룹화 된다. -> 각 partiton 단위별로 어떤 행위가 이루어지는지 좀 더 가시적으로 열람 할 수 있음
-
ERC-1594(Core Security Token) 전송/발행/상환 시 데이터의 오프체인의 추가적인 데이터를 입력받기 위한 기능과 전송 시 유효성을 확인할 수 있는 기능을 제공하고 있음.
전송 유효성을 확인하는 기능은 꼭 전송할 때 뿐만 아니라 다른 기능에서 필요로 하면 해당 기능에서 사용 가능하다.
/// @title IERC1594 Security Token Standard /// @dev See https://github.com/SecurityTokenStandard/EIP-Spec interface IERC1594 is IERC20 { // Transfers function transferWithData(address _to, uint256 _value, bytes _data) external; function transferFromWithData(address _from, address _to, uint256 _value, bytes _data) external; // Token Issuance function isIssuable() external view returns (bool); function issue(address _tokenHolder, uint256 _value, bytes _data) external; // Token Redemption function redeem(uint256 _value, bytes _data) external; function redeemFrom(address _tokenHolder, uint256 _value, bytes _data) external; // Transfer Validity function canTransfer(address _to, uint256 _value, bytes _data) external view returns (bool, byte, bytes32); function canTransferFrom(address _from, address _to, uint256 _value, bytes _data) external view returns (bool, byte, bytes32); // Issuance / Redemption Events event Issued(address indexed _operator, address indexed _to, uint256 _value, bytes _data); event Redeemed(address indexed _operator, address indexed _from, uint256 _value, bytes _data); }-
ERC-20에서는 transfer 시에 balanceOf, allowance 함수를 통해서 sender가 현재 충분한 잔액을 가지고 있는지 체크를 한 이후에 거래가 이루어지는데, ERC-1594의 경우 sender나 receiver의 KYC여부 등 특수한 검증절차가 추가적으로 필요함* 추가적인 체크 함수는 can canTransferByPartition 두 가지의 함수가 추가되었음, 해당 두개의 함수는 boolean을 Return하는 것이 아니라 byte32값을 리턴하고 값은 ERC-1066을 사용할수 있고 ERC-1066 Result Code 컨트랙트를 구현하는 사람이 임의적인 값을 지정 할 수도 있음
// Transfer Validity function canTransfer(address _to, uint256 _value, bytes _data) external view returns (byte, bytes32); function canTransferFrom(address _from, address _to, uint256 _value, bytes _data) external view returns (byte, bytes32); function canTransferByPartition(address _from, address _to, bytes32 _partition, uint256 _value, bytes _data) external view returns (byte, bytes32, bytes32); -
기존의 일반적인 ERC-20의 transfer 확장되어서 transferWithData, transferFromData내에서 “bytes _data” 필드가 추가되었음, 해당 필드는 오프체인(Off-chain)에서 추가적인 인자를 지원해주기 위한 필드로 data에 입력되는 값의 형태는 ERC-1400의 표준에 벗어나는 부분이다. 따라서 구현자가 임의적으로 값을 정해서 입력해도 된다.
// Transfers function transferWithData(address _to, uint256 _value, bytes _data) external; function transferFromWithData(address _from, address _to, uint256 _value, bytes _data) external;
-
-
ERC-1643(Document Management)
/// @title IERC1643 Document Management (part of the ERC1400 Security Token Standards) /// @dev See https://github.com/SecurityTokenStandard/EIP-Spec interface IERC1643 { // Document Management function getDocument(bytes32 _name) external view returns (string, bytes32, uint256); function setDocument(bytes32 _name, string _uri, bytes32 _documentHash) external; function removeDocument(bytes32 _name) external; function getAllDocuments() external view returns (bytes32[]); // Document Events event DocumentRemoved(bytes32 indexed _name, string _uri, bytes32 _documentHash); event DocumentUpdated(bytes32 indexed _name, string _uri, bytes32 _documentHash); }-
증권형 토큰에는 KYC에 관한 내용 등 다양한 문서들이 필요함, ERC-1643는 이러한 문서들을 스마트 컨트랙트를 통해 성성, 제거, 수정 등의 동작을 수행 할 수 있도록 정의한 표준임.
-
DocumentUpdated, DocumentRemoved 등의 event를 통해 투자자들이 투자와 관련된 문서의 변동사항에 대해서 알 수 있도록 구현되어 있음
-
-
ERC-1644(Controller Operation Token) 토큰에 대한 강제 전송과 관련된 작업에 관련된 표준이다.
/// @title IERC1644 Controller Token Operation (part of the ERC1400 Security Token Standards) /// @dev See https://github.com/SecurityTokenStandard/EIP-Spec interface IERC1644 is IERC20 { // Controller Operation function isControllable() external view returns (bool); function controllerTransfer(address _from, address _to, uint256 _value, bytes _data, bytes _operatorData) external; function controllerRedeem(address _tokenHolder, uint256 _value, bytes _data, bytes _operatorData) external; // Controller Events event ControllerTransfer( address _controller, address indexed _from, address indexed _to, uint256 _value, bytes _data, bytes _operatorData ); event ControllerRedemption( address _controller, address indexed _tokenHolder, uint256 _value, bytes _data, bytes _operatorData ); }-
증권형 토큰은 규제 및 법적 감독의 대상이기 때문에 특정한 주체가 토큰의 전송을 강제 할 수 권한이 필요함, ERC-1644는 해당 기능을 정의하고 있음.
-
컨트롤러(Controller)라는 주체를 생성 해당 계정에 강제전송 할 수 있는권한을 부여해서 특정 거래가 부정적으로 이루어지거나 사용자가 개인키를 분실한 경우 컨트롤러 계정을 통해서 강제적으로 수량을 보내거나 할 수 있음
-
해당 기능을 구현한 함수는 controllerTransfer와 controllerRedeem 두 가지가 있음, 각 각읳 함수에서는 ControllerTransfer, ControllerRedemption event를 발생시킨다. 컨트롤러의 권한을 원하지 않는 함수의 경우 isControllable함수를 구현 항상 False를 return하도록 하면 함수 실행 시에 revert가되어버림
-
아래는 ERC 1400가 정의하고 있는 전체 인터페이스이다.
/// @title IERC1400 Security Token Standard
/// @dev See https://github.com/SecurityTokenStandard/EIP-Spec
interface IERC1400 is IERC20 {
// Document Management
function getDocument(bytes32 _name) external view returns (string, bytes32);
function setDocument(bytes32 _name, string _uri, bytes32 _documentHash) external;
// Token Information
function balanceOfByPartition(bytes32 _partition, address _tokenHolder) external view returns (uint256);
function partitionsOf(address _tokenHolder) external view returns (bytes32[]);
// Transfers
function transferWithData(address _to, uint256 _value, bytes _data) external;
function transferFromWithData(address _from, address _to, uint256 _value, bytes _data) external;
// Partition Token Transfers
function transferByPartition(bytes32 _partition, address _to, uint256 _value, bytes _data) external returns (bytes32);
function operatorTransferByPartition(bytes32 _partition, address _from, address _to, uint256 _value, bytes _data, bytes _operatorData) external returns (bytes32);
// Controller Operation
function isControllable() external view returns (bool);
function controllerTransfer(address _from, address _to, uint256 _value, bytes _data, bytes _operatorData) external;
function controllerRedeem(address _tokenHolder, uint256 _value, bytes _data, bytes _operatorData) external;
// Operator Management
function authorizeOperator(address _operator) external;
function revokeOperator(address _operator) external;
function authorizeOperatorByPartition(bytes32 _partition, address _operator) external;
function revokeOperatorByPartition(bytes32 _partition, address _operator) external;
// Operator Information
function isOperator(address _operator, address _tokenHolder) external view returns (bool);
function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) external view returns (bool);
// Token Issuance
function isIssuable() external view returns (bool);
function issue(address _tokenHolder, uint256 _value, bytes _data) external;
function issueByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _data) external;
// Token Redemption
function redeem(uint256 _value, bytes _data) external;
function redeemFrom(address _tokenHolder, uint256 _value, bytes _data) external;
function redeemByPartition(bytes32 _partition, uint256 _value, bytes _data) external;
function operatorRedeemByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _operatorData) external;
// Transfer Validity
function canTransfer(address _to, uint256 _value, bytes _data) external view returns (byte, bytes32);
function canTransferFrom(address _from, address _to, uint256 _value, bytes _data) external view returns (byte, bytes32);
function canTransferByPartition(address _from, address _to, bytes32 _partition, uint256 _value, bytes _data) external view returns (byte, bytes32, bytes32);
// Controller Events
event ControllerTransfer(
address _controller,
address indexed _from,
address indexed _to,
uint256 _value,
bytes _data,
bytes _operatorData
);
event ControllerRedemption(
address _controller,
address indexed _tokenHolder,
uint256 _value,
bytes _data,
bytes _operatorData
);
// Document Events
event Document(bytes32 indexed _name, string _uri, bytes32 _documentHash);
// 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 AuthorizedOperator(address indexed _operator, address indexed _tokenHolder);
event RevokedOperator(address indexed _operator, address indexed _tokenHolder);
event AuthorizedOperatorByPartition(bytes32 indexed _partition, address indexed _operator, address indexed _tokenHolder);
event RevokedOperatorByPartition(bytes32 indexed _partition, address indexed _operator, address indexed _tokenHolder);
// Issuance / Redemption Events
event Issued(address indexed _operator, address indexed _to, uint256 _value, bytes _data);
event Redeemed(address indexed _operator, address indexed _from, uint256 _value, bytes _data);
event IssuedByPartition(bytes32 indexed _partition, address indexed _operator, address indexed _to, uint256 _value, bytes _data, bytes _operatorData);
event RedeemedByPartition(bytes32 indexed _partition, address indexed _operator, address indexed _from, uint256 _value, bytes _operatorData);
}