CAP: 0032
Title: Trustline Preauthorization
Author: Jonathan Jove
Status: Draft
Created: 2020-04-28
Discussion: https://github.com/stellar/stellar-protocol/issues/303
Protocol version: TBD
This proposal makes it possible to authorize a trust line before it is created.
This proposal seeks to solve the following problem: an issuer should be able to authorize a trust line without waiting for the trust line to be created.
Consider, for example, an issuer that wants to issue AUTH_REQUIRED
assets to
an account that does not have a trust line for the asset. With the current
version of the protocol, the issuer will need to wait until the account has
created a trust line before authorizing the trust line. This presents friction
to the issuer, which must now employ additional machinery to either monitor the
ledger or use pre-signed/pre-authorized transactions. In either case, complexity
is increased for an operation fundamental to the network.
This proposal is aligned with several Stellar Network Goals, among them:
- The Stellar Network should make it easy for developers of Stellar projects to create highly usable products.
- The Stellar Network should enable cross-border payments, i.e. payments via
exchange of assets, throughout the globe, enabling users to make payments
between assets in a manner that is fast, cheap, and highly usable.
- In support of this, the Stellar Network should enable asset issuance, but as a means of enabling cross-border payments.
We introduce PreauthorizationEntry
as a new type of LedgerEntry
which
represents authorization to hold an asset, potentially before the corresponding
trust line has been created. The operation CreatePreauthorizationOp
makes it
possible to create a PreauthorizationEntry
whereas the operation
RemovePreauthorizationOp
makes it possible to remove a PreauthorizationEntry
.
When creating a trust line with ChangeTrustOp
, the flags
will now be set
from a corresponding PreauthorizationEntry
if it exists. AllowTrustOp
will
also be updated to modify just a trust line, just a PreauthorizationEntry
, or
both depending on which exist.
union NonNativeAssetCode switch (AssetType type)
{
// ASSET_TYPE_NATIVE is not allowed
case ASSET_TYPE_CREDIT_ALPHANUM4:
AssetCode4 assetCode4;
case ASSET_TYPE_CREDIT_ALPHANUM12:
AssetCode12 assetCode12;
};
struct AllowTrustOp
{
AccountID trustor;
NonNativeAssetCode asset;
// 0, or any bitwise combination of TrustLineFlags
uint32 authorize;
};
enum LedgerEntryType
{
// ... ACCOUNT, TRUSTLINE, OFFER, unchanged ...
DATA = 3,
PREAUTHORIZATION = 4
};
struct PreauthorizationEntry
{
// Account for which the preauthorization applies
AccountID accountID;
// Asset for which the preauthorization applies
Asset asset;
// See TrustLineFlags
uint32 flags;
// Amount of native asset to pay the reserve
int64 reserve;
// reserved for future use
union switch (int v)
{
case 0:
void;
}
ext;
};
struct LedgerEntry
{
uint32 lastModifiedLedgerSeq; // ledger the LedgerEntry was last changed
union switch (LedgerEntryType type)
{
// ... ACCOUNT, TRUSTLINE, OFFER, DATA unchanged ...
case PREAUTHORIZATION:
PreauthorizationEntry preauthorization;
}
data;
// reserved for future use
union switch (int v)
{
case 0:
void;
}
ext;
};
struct LedgerKey
{
// ... ACCOUNT, TRUSTLINE, OFFER, DATA unchanged ...
case PREAUTHORIZATION:
struct
{
AccountID accountID;
Asset asset;
} preauthorization;
};
enum OperationType
{
// ... CREATE_ACCOUNT, ..., MANAGE_BUY_OFFER unchanged ...
PATH_PAYMENT_STRICT_SEND = 13,
CREATE_PREAUTHORIZATION = 14,
REMOVE_PREAUTHORIZATION = 15
};
struct CreatePreauthorizationOp
{
// Account to preauthorize
AccountID accountID;
// Preauthorization asset
NonNativeAssetCode asset;
};
struct RemovePreauthorizationOp
{
// Account from which to remove preauthorization
AccountID accountID;
// Preauthorization asset
NonNativeAssetCode asset;
};
struct Operation
{
// sourceAccount is the account used to run the operation
// if not set, the runtime defaults to "sourceAccount" specified at
// the transaction level
AccountID* sourceAccount;
union switch (OperationType type)
{
// ... CREATE_ACOUNT, ..., PATH_PAYMENT_STRICT_SEND unchanged ...
case CREATE_PREAUTHORIZATION:
CreatePreauthorizationOp createPreauthorizationOp;
case REMOVE_PREAUTHORIZATION:
RemovePreauthorizationOp removePreauthorizationOp;
}
body;
};
enum CreatePreauthorizationResultCode
{
CREATE_PREAUTHORIZATION_SUCCESS = 0,
CREATE_PREAUTHORIZATION_MALFORMED = -1,
CREATE_PREAUTHORIZATION_ALREADY_EXISTS = -2,
CREATE_PREAUTHORIZATION_LOW_RESERVE = -3
};
union CreatePreauthorizationResult switch (CreatePreauthorizationResultCode code)
{
case CREATE_PREAUTHORIZATION_SUCCESS:
void;
default:
void;
};
enum RemovePreauthorizationResultCode
{
REMOVE_PREAUTHORIZATION_SUCCESS = 0,
REMOVE_PREAUTHORIZATION_DOES_NOT_EXIST = -1,
REMOVE_PREAUTHORIZATION_LINE_FULL = -2
};
union RemovePreauthorizationResult switch (RemovePreauthorizationResultCode code)
{
case REMOVE_PREAUTHORIZATION_SUCCESS:
void;
default:
void;
};
struct OperationResult
{
// sourceAccount is the account used to run the operation
// if not set, the runtime defaults to "sourceAccount" specified at
// the transaction level
AccountID* sourceAccount;
union switch (OperationType type)
{
// ... CREATE_ACOUNT, ..., REMOVE_SPONSORSHIP unchanged ...
case CREATE_PREAUTHORIZATION:
CreatePreauthorizationResult createPreauthorizationResult;
case REMOVE_PREAUTHORIZATIOn:
RemovePreauthorizationResult removePreauthorizationResult;
}
body;
};
A PreauthorizationEntry
can only be created by the CreatePreauthorizationOp
operation. CreatePreauthorizationOp
is invalid with
CREATE_PREAUTHORIZATION_MALFORMED
if asset
is invalid.
The behavior of CreatePreauthorizationOp
is as follows:
- Fail with
CREATE_PREAUTHORIZATION_ALREADY_EXISTS
if aPreauthorizationEntry
with the specifiedaccountID
andasset
already exists - Fail with
CREATE_PREAUTHORIZATION_LOW_RESERVE
if thesourceAccount
does not have at leastbaseReserve
available balance of native asset - Deduct
baseReserve
of native asset fromsourceAccount
- Create a
PreauthorizationEntry
aspreauthorization
with the following properties:preauthorization.accountID = accountID
preauthorization.asset = asset
preauthorization.flags = 0
ifsourceAccount.flags & AUTH_REQUIRED_FLAG
andpreauthorization.flags = AUTHORIZED_FLAG
otherwise
- If a trust line
tl
with the specifiedaccountID
andasset
exists, then setpreauthorization.flags = tl.flags
- Succeed with
CREATE_PREAUTHORIZATION_SUCCESS
CreatePreauthorizationOp
requires low threshold because it cannot be used to
send funds.
A PreauthorizationEntry
can only be removed by the RemovePreauthorizationOp
operation. RemovePreauthorizationOp
is invalid with
REMOVE_PREAUTHORIZATION_MALFORMED
if asset
is invalid.
The behavior of RemovePreauthorizationOp
is as follows:
- Fail with
REMOVE_PREAUTHORIZATION_DOES_NOT_EXIST
if there does not exist aPreauthorizationEntry
with the specifiedaccountID
andasset
- Fail with
REMOVE_PREAUTHORIZATION_LINE_FULL
if thesourceAccount
does not have at leastreserve
available limit of native asset - Add
reserve
of native asset tosourceAccount
- Remove the specified
PreauthorizationEntry
- Succeed with
REMOVE_PREAUTHORIZATION_SUCCESS
RemovePreauthorizationOp
requires low threshold because it cannot be used to
send funds.
The behavior of ChangeTrustOp
is unchanged except when creating a trust line.
In this case, we add one additional step immediately before success:
- ...
- If a preauthorization entry
preauthorization
with thesourceAccount
and specifiedasset
exists, then setflags = preauthorization.flags
- Succeed with
CHANGE_TRUST_SUCCESS
The behavior of AllowTrustOp
must be modified to maintain the invariant that
a trust line and a preauthorization entry for the same accountID
and asset
must have the same flags
.
First, we now fail with ALLOW_TRUST_NO_TRUST_LINE
only if both the trust line
and preauthorization entry do not exist:
- ...
- Load the trust line and preauthorization entry with the
sourceAccount
and specifiedassetID
- Fail with
ALLOW_TRUST_NO_TRUST_LINE
if neither exists - ...
Second, immediately before success we must set the flags
on the trust line and
the preauthorization entry if they exist:
- ...
- If the trust line exists with the
sourceAccount
and specifiedasset
exists, set the trust lineflags
as specified - If the preauthorization entry with the
sourceAccount
and specifiedasset
exists, set the preauthorization entryflags
as specified - Suceed with
ALLOW_TRUST_SUCCESS
Each PreauthorizationEntry
exists as an independent entity on the ledger. It
is clear that a PreauthorizationEntry
cannot be a sub-entry of accountID
,
because it is a security risk for accounts to be able to add
sub-entries to other accounts. But why should these entries be independent
entities on the ledger rather than sub-entries of the accounts that created
them? The main benefit of this design is that issuers are not limited in the
number of preauthorization entries they can create.
This proposal makes it possible for a trust line and a preauthorization entry
with the same accountID
and asset
to exist simultaneously. We avoid any
ambiguity in the authorization state by guaranteeing that if both do exist
simultaneously, then they must have the same flags
. It is possible to maintain
this property because there is at most one trust line and one preauthorization
entry for a given accountID
and asset
.
All downstream systems will need updated XDR in order to recognize the new operations and ledger entries.
This proposal will slightly reduce the efficacy of base reserve changes, because
a PreauthorizationEntry
that has insufficient reserve is still usable.
None yet.
None yet.