Description
eip: ERC-1594
title: Core Security Token Standard (part of the ERC-1400 Security Token Standards)
author: Adam Dossa (@adamdossa), Pablo Ruiz (@pabloruiz55), Fabian Vogelsteller (@frozeman), Stephane Gosselin (@thegostep)
discussions-to: #1411
status: Draft
type: Standards Track
category: ERC
created: 2018-09-09
require: ERC-20 (#20), ERC-1066 (#1066)
Simple Summary
This standard sits under the ERC-1400 (#1411) umbrella set of standards related to security tokens.
Provides a standard to support off-chain injection of data into transfers / issuance / redemption and the ability to check the validity of a transfer distinct from it's execution.
Abstract
Incorporates error signalling, off-chain data injection and issuance / redemption semantics.
This standard inherits from ERC-20 (#20) and could be easily extended to meet the ERC-777 (#777) standard if needed.
Motivation
Accelerate the issuance and management of securities on the Ethereum blockchain by specifying a standard interface through which security tokens can be operated on and interrogated by all relevant parties.
Security tokens differ materially from other token use-cases, with more complex interactions between off-chain and on-chain actors, and considerable regulatory scrutiny.
The ability to provide data (e.g. signed authorisation) alongside transfer, issuance and redemption functions allows security tokens to more flexibly implement transfer restrictions without depending on on-chain whitelists exclusively.
Using ERC-1066 (#1066) to provide reason codes as to why a transfer would fail, without requiring a user to actually try and execute a transfer, allows for improved UX and potentially saves gas on what would otherwise be failed transfers.
Formalising issuance and redemption semantics (similar to minting / burning) provides visibility into the total supply of the token and how it has changed over time.
Requirements
See ERC-1400 (#1411) for a full set of requirements across the library of standards.
The following requirements have been compiled following discussions with parties across the Security Token ecosystem.
- MUST have a standard interface to query if a transfer would be successful and return a reason for failure.
- MUST emit standard events for issuance and redemption.
- MAY require signed data to be passed into a transfer transaction in order to validate it on-chain.
- SHOULD NOT restrict the range of asset classes across jurisdictions which can be represented.
- MUST be ERC-20 (ERC: Token standard #20) compatible.
- COULD be ERC-777 (ERC777 Token Standard #777) compatible.
Rationale
Transfer Restrictions
Transfers of securities can fail for a variety of reasons in contrast to utility tokens which generally only require the sender to have a sufficient balance.
These conditions could be related to metadata of the securities being transferred (i.e. whether they are subject to a lock-up period), the identity of the sender and receiver of the securities (i.e. whether they have been through a KYC process, whether they are accredited or an affiliate of the issuer) or for reasons unrelated to the specific transfer but instead set at the token level (i.e. the token contract enforces a maximum number of investors or a cap on the percentage held by any single investor).
For ERC-20 / ERC-777 tokens, the balanceOf
and allowance
functions provide a way to check that a transfer is likely to succeed before executing the transfer, which can be executed both on and off-chain.
For tokens representing securities the standard introduces a function canTransfer
which provides a more general purpose way to achieve this when the reasons for failure are more complex; and a function of the whole transfer (i.e. includes any data sent with the transfer and the receiver of the securities).
In order to support off-chain data inputs to transfer functions, transfer functions are extended to transferWithData
/ transferFromWithData
which can optionally take an additional bytes _data
parameter.
In order to provide a richer result than just true or false, a byte return code is returned. This allows us to give a reason for why the transfer failed, or at least which category of reason the failure was in. The ability to query documents and the expected success of a transfer is included in Security Token section.
Specification
Restricted Transfers
canTransfer / canTransferFrom
Transfers of securities may fail for a number of reasons, for example relating to:
- the identity of the sender or receiver of the tokens
- limits placed on the specific tokens being transferred (i.e. lockups on certain quantities of token)
- limits related to the overall state of the token (i.e. total number of investors)
The standard provides an on-chain function to determine whether a transfer will succeed, and return details indicating the reason if the transfer is not valid.
These rules can either be defined using smart contracts and on-chain data, or rely on _data
passed as part of the transferWithData
function which could represent authorisation for the transfer (e.g. a signed message by a transfer agent attesting to the validity of this specific transfer).
The function will return both a ESC (Ethereum Status Code) following the EIP-1066 standard, and an additional bytes32
parameter that can be used to define application specific reason codes with additional details (for example the transfer restriction rule responsible for making the transfer operation invalid).
If bytes _data
is empty, then this corresponds to a check on whether a transfer
(or transferFrom
) request will succeed, if bytes _data
is populated, then this corresponds to a check on transferWithData
(or transferFromWithData
) will succeed.
canTransfer
assumes the sender of tokens is msg.sender
and will be executed via transfer
or transferWithData
whereas canTransferFrom
allows the specification of the sender of tokens and that the transfer will be executed via transferFrom
or transferFromWithData
.
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);
transferWithData
Transfer restrictions can take many forms and typically involve on-chain rules or whitelists. However for many types of approved transfers, maintaining an on-chain list of approved transfers can be cumbersome and expensive. An alternative is the co-signing approach, where in addition to the token holder approving a token transfer, and authorised entity provides signed data which further validates the transfer.
The bytes _data
allows arbitrary data to be submitted alongside the transfer, for the token contract to interpret or record. This could be signed data authorising the transfer (e.g. a dynamic whitelist) but is flexible enough to accomadate other use-cases.
transferWithData
MUST emit a Transfer
event with details of the transfer.
function transferWithData(address _to, uint256 _value, bytes _data) external;
transferFromWithData
This is the analogy to the transferWithData
function.
msg.sender
MUST have a sufficient allowance
set and this allowance
must be debited by the _value
.
function transferFromWithData(address _from, address _to, uint256 _value, bytes _data) external;
Token Issuance
isIssuable
A security token issuer can specify that issuance has finished for the token (i.e. no new tokens can be minted or issued).
If a token returns FALSE for isIssuable()
then it MUST always return FALSE in the future.
If a token returns FALSE for isIssuable()
then it MUST never allow additional tokens to be issued.
function isIssuable() external view returns (bool);
issue
This function must be called to increase the total supply.
The bytes _data
parameter can be used to inject off-chain data (e.g. signed data) to authorise or authenticate the issuance and receiver of issued tokens.
When called, this function MUST emit the Issued
event.
function issue(address _tokenHolder, uint256 _value, bytes _data) external;
Token Redemption
redeem
Allows a token holder to redeem tokens.
The redeemed tokens must be subtracted from the total supply and the balance of the token holder. The token redemption should act like sending tokens and be subject to the same conditions.
The Redeemed
event MUST be emitted every time this function is called.
As with transferWithData
this function has a bytes _data
parameter that can be used in the token contract to authenticate the redemption.
function redeem(uint256 _value, bytes _data) external;
redeemFrom
This is the analogy to the redeem
function.
msg.sender
MUST have a sufficient allowance
set and this allowance
must be debited by the _value
.
The Redeemed
event MUST be emitted every time this function is called.
function redeemFrom(address _tokenHolder, uint256 _value, bytes _data) external;
Interface
/// @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);
}