Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ERC 2258: Custodial Ownership Standard #2258

Closed
adamdossa opened this issue Sep 2, 2019 · 7 comments
Closed

ERC 2258: Custodial Ownership Standard #2258

adamdossa opened this issue Sep 2, 2019 · 7 comments
Labels

Comments

@adamdossa
Copy link

adamdossa commented Sep 2, 2019


eip: ERC-2258
title: Custodial Ownership Standard (part of the ERC-1400 Security Token Standards)
discussions-to: #2258
status: Draft
type: Standards Track
category: ERC
created: 2019-09-01
require: ERC-20 (#20)


Simple Summary

This standard is intended to sit under the ERC-1400 (#1411) umbrella set of standards related to security tokens.

Provides a standard for separating the concepts of beneficial and custodial ownership of tokens.

Abstract

Security tokens represent a way to record ownership of some underlying asset. ERC20 and other token standards represent this through a token balance associated with an Ethereum address representing the asset owner.

Many use-cases require a more granular concept of ownership, and specifically security tokens may be held in custody by an entity on behalf of the token owner. In this case the token owner remains the beneficial owner of the security with respect to capital distribution and governance, whilst the custodian has exclusive rights over changing the beneficial owner.

Example 1:

  • a DeFi protocol requires you to provide security tokens as collateral. The DeFi smart contract must be able to transfer away your tokens if there is a liquidation event, so requires custodial ownership. The investor who is posting her tokens as collateral should continue to receive any dividends and be able to vote in any corporate actions.

Example 2:

  • the investor nominates a custodian to safe-guard their tokens. They transfer custodial ownership to the custodian, and retain capital distribution and governance rights. This means that their assets are secured (via the custodian in cold storage) whilst the investor can still use a hot wallet to interact with governance and capital distribution.

Motivation

Within ERC20 the approve and transferFrom functions provide a kind of limited custodial ownership, but the token owner retains full rights to decrease the custody allowance or transferring tokens away entirely to a different address.

In practice this means that entities which need to be able to guarantee control must hold tokens directly and retain a second balance mapping of tokens to owners, for the balance it has under custody.

This ERC aims to make this common pattern simpler as well as differentiating between custodial and beneficial ownership so that owners of tokens do not have to give up these rights (or pass them through the custodian) in order to receive e.g. dividends and vote.

Removing the requirement for custodians (external addresses or contracts) to transfer balances to themselves in order to act as custodians also means that token issuers see a consistent "cap table" of ownership when looking at token balances (e.g. through etherscan.io).

Requirements

See ERC-1400 (#1411) for a full set of requirements across the library of standards.

Rationale

Standardising this simplifies many use-cases and provides additional functionality for token holders in many domains.

Allows DeFi contracts to interact with security tokens in a standardised fashion improving composibility of protocols.

Specification

A token holder can choose to nominate a custodian for a fixed number of tokens.

A token holder cannot transfer away tokens which have been put into custody (across all custodians).

For example, Alice has 100 ACME:

  • she sets a custodyAllowance of 20 ACME to Bob (a custodian)
  • she sets a custodyAllowance of 20 ACME to Charlie (a custodian)

Alice can now only transfer 60 ACME (she has a totalCustodyAllowance of 40).

Alice can only put tokens into custody provided that she has sufficient free (un-custodied) balance. This provides the guarantee that a custodian will always be able to transfer up to their custodyAllowance of tokens. This means that the base transfer function must ensure that the remaining balance of the token holder is at least totalCustodyAllowance and otherwise revert.

As the custodian (external address or smart contract) exercises their right to transfer ACME tokens on behalf of Alice, their custodyAllowance is decreased accordingly.

A custodian transfers tokens by calling transferByCustodian. Calling this function decreases a custodians custodyAllowance appropriately on completion.

If the token also implements ERC-1410, then the totalCustodyAllowance can be presented as a partition of the token holders balance.

When a custodian is transferring tokens on behalf of a token holder, any other transfer rules must be respected (e.g. canTransfer should be true for the tokens being transferred from the current beneficial owner to the new owner irrespective of the fact that they are being transferred by a custodian rather than directly by the beneficial owner).

increaseCustodyAllowance

Used to increase the amount of tokens held in custody by the specified custodian. Note that a token holder can only ever increase this limit and cannot unilaterally decrease it. The only way this limit can be decreased is by the custodian transferring tokens. A custodian can transfer tokens back to the same beneficiary if they wish to remove their custody limit without impacting the beneficial owner.

increaseCustodyAllowance must throw if unsuccessful.
The sum of _amount and totalCustodyAllowance MUST NOT be greater than the token holders balance.
The event CustodyAllowanceChanged MUST be emitted if the custody limit is successfully changed.

function increaseCustodyAllowance(address _custodian, uint256 _amount) external;

increaseCustodyAllowanceOf

As per increaseCustodyAllowance but can be called by someone other than the token holder.

The token holder can provide the caller with signed data to authorise the custody limit being amended, and anyone (e.g. the custodian) can then call this function to update the custody limit.

increaseCustodyAllowanceOf must throw if unsuccessful.
The sum of _amount and totalCustodyAllowance MUST NOT be greater than the token holders balance.
The event CustodyAllowanceChanged MUST be emitted if the custody limit is successfully changed.

function increaseCustodyAllowanceOf(address _tokenHolder, address _custodian, uint256 _amount, uint256 _nonce, bytes _sig) external;

custodyAllowance

Returns the current custody limit associated with a token holder and custodian.

function custodyAllowance(address _tokenHolder, address _custodian) external view returns (uint256);

totalCustodyAllowance

Returns the total amount of tokens that the token holder has assigned under custody to custodians.

The token holder MUST always have a token balance greater than their totalCustodyAllowance.
totalCustodyAllowance MUST be the sum across all custodians of custodyAllowance.

function totalCustodyAllowance(address _tokenHolder) external view returns (uint256);

transferByCustodian

Used by a custodian to transfer tokens over which they have custody.

MUST emit a CustodyTransfer event on successful completion.
MUST emit a CustodyAllowanceChanged event on successful completion.
MUST decrease the custodyAllowance of the custodian by _amount.

function transferByCustodian(address _tokenHolder, address _receiver, uint256 _amount) external;

Interface

/// @title IERCx Custodial Ownership Standard (part of the ERC1400 Security Token Standards Library)
/// @dev See https://github.com/SecurityTokenStandard/EIP-Spec

interface IERCx {

    // Increase the custody limit of a custodian either directly or via signed authorisation
    function increaseCustodyAllowance(address _custodian, uint256 _amount) external;
    function increaseCustodyAllowanceOf(address _tokenHolder, address _custodian, uint256 _amount, uint256 _nonce, bytes _sig) external;

    // Query individual custody limit and total custody limit across all custodians
    function custodyAllowance(address _tokenHolder, address _custodian) external view returns (uint256);
    function totalCustodyAllowance(address _tokenHolder) external view returns (uint256);

    // Allows a custodian to exercise their right to transfer custodied tokens
    function transferByCustodian(address _tokenHolder, address _receiver, uint256 _amount) external;

    // Custody Events
    event CustodyTransfer(address _custodian, address _from, address _to, uint256 _amount);
    event CustodyAllowanceChanged(address _tokenHolder, address _custodian, uint256 _oldAllowance, uint256 _newAllowance);

}

References

@adamdossa adamdossa changed the title [draft] EIP ERC 2258: Custodial Ownersip Standard Sep 2, 2019
@adamdossa adamdossa changed the title ERC 2258: Custodial Ownersip Standard ERC 2258: Custodial Ownership Standard Sep 2, 2019
@iamdefinitelyahuman
Copy link

👍 this looks similar to we're handling custody in SFT - https://sft-protocol.readthedocs.io/en/latest/custodian.html

We're actually transferring the tokens and tracking custody balances with a seperate mapping. This approach of an only-increasable allowance would massively simplify governance and dividend related logic... very clean, I like it.

Some thoughts:

  • How do you identify that an address belongs to a custodian? A view function along the lines of isCustodian(address _addr) would be useful.
  • Private companies typically have legal requirements to prohibit their securities from being transferred or custodied by certain kinds of entities. For example, the issuer may be required to blacklist unregulated lending protocols or exchange addresses, especially when the security is not authorized to trade publicly. There needs to be specific permissioning logic around who can encumber the securities, even while remaining in beneficial owner's account - the custodial equivalent to canTransfer.
  • In the case of a controllable token, the controller needs a way to differentiate whether they're transferring custodied or uncustodied tokens. Similarly, can a controller reduce a custodyAllowance?

@adamdossa
Copy link
Author

Thanks for the comments - great points.

For isCustodian, there is a custodyAllowance function (which takes a token holder and custodian address) - if this returns > 0 then you can assume the custodian address has custody over some tokens - is this the type of functionality you had in mind?

Whitelist / blacklist functionality for allowed custodians sounds like it makes sense - this is something which could be done at the implementation level potentially, although a helper function like canCustody may be helpful here to standardise checking whether an address can act as a custodian (with the logic behind it being outside the scope of the interface).

Good question re. controllers - they can force transfer tokens so it probably makes sense for them to be able to unilaterally reduce custodyAllowance.

@iamdefinitelyahuman
Copy link

My first point was more related to what you mention in response to the second point. In some cases knowing if an address isCustodian is a prerequisite to determining whether it canCustody. Similarly if an address isCustodian it might be blocked from holding tokens in a normal fashion. I agree that the implementation of custodians will have a lot of potential for variance, but it would be great to standardize the getters to know who they are and what they're allowed to custody.

Re: controllers - forceTransferByCustodian? Or something to that effect, in line with whatever is decided in #1644.

@teawaterwire
Copy link

one thought: the custody allowance can only be incremented, which makes sense, but would it be "safer" to add a time delay so that after x days the custody allowance is "released"?

@adamdossa
Copy link
Author

It's an interesting idea - having an optional time limit on the custody sounds a nice idea. Not sure if this is a general enough use case to add to the spec. here or whether it should be on the implementation side?

@github-actions
Copy link

There has been no activity on this issue for two months. It will be closed in a week if no further activity occurs. If you would like to move this EIP forward, please respond to any outstanding feedback or add a comment indicating that you have addressed all required feedback and are ready for a review.

@github-actions github-actions bot added the stale label Nov 20, 2021
@github-actions
Copy link

github-actions bot commented Dec 4, 2021

This issue was closed due to inactivity. If you are still pursuing it, feel free to reopen it and respond to any feedback or request a review in a comment.

@github-actions github-actions bot closed this as completed Dec 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants