Skip to content

Latest commit

 

History

History
796 lines (674 loc) · 26.9 KB

cap-0033.md

File metadata and controls

796 lines (674 loc) · 26.9 KB

Preamble

CAP: 0033
Title: Sponsored Reserve
Author: Jonathan Jove
Status: Accepted
Created: 2020-05-18
Discussion: https://groups.google.com/forum/#!msg/stellar-dev/E_tDs17mkJw/DmGXVY-QBAAJ
Protocol version: 14

Simple Summary

This proposal makes it possible to pay reserves for another account.

Motivation

This proposal seeks to solve the following problem: an entity should be able to provide the reserve for accounts controlled by other parties without giving those parties control of the reserve.

Consider, for example, an issuer that is willing to pay the reserve for trust lines to the asset it issues. With the current version of the protocol, the reserve must be part of the balance of an account. This means the issuer can only pay the reserve by sending native asset to accounts that create a trust line to the asset it issues. But this leaves the issuer vulnerable to attack because an attacker can extract funds from the issuer by creating new accounts, creating the trust line, waiting for the native asset to arrive, then removing the trust line and merging the account.

This proposal is in many ways analogous to CAP-0015:

  • CAP-0015 makes it possible to pay transaction fees for other accounts without giving control of the underlying funds
  • CAP-0033 makes it possible to pay reserves for other accounts without giving control of the underlying funds

The combination of these two proposals should greatly facilitate the development of non-custodial uses of the Stellar Network.

Goals Alignment

This proposal is aligned with the following Stellar Network Goal:

  • The Stellar Network should make it easy for developers of Stellar projects to create highly usable products.

Abstract

We introduce the relation is-sponsoring-future-reserves-for, in which an account (the sponsoring account) pays any reserve that another account (the sponsored account) would have to pay. This relation is initiated by SponsorFutureReservesOp, where the sponsoring account is the source account, and is terminated by ConfirmAndClearSponsorOp, where the sponsored account is the source account. Both operations must appear in a single transaction, which guarantees that both the sponsoring and sponsored accounts agree to every sponsorship. We also introduce UpdateSponsorshipOp, which can be used to modify the sponsorship of existing ledger entries. To support this, we add new extensions to AccountEntry and LedgerEntry which record pertinent information about sponsorships.

Specification

This specification assumes CAP-0023, in order to show how sponsorships would work for claimable balance entries.

XDR

AccountEntry

typedef AccountID* SponsorshipDescriptor;

struct AccountEntryExtensionV2
{
    uint32 numSponsored;  // Number of reserves sponsored for this account
                          // Reduces the reserve requirement for this account
    uint32 numSponsoring; // Number of reserves sponsored by this account
                          // Increases the reserve requirement for this account

    // signerSponsoringIDs are in 1-to-1 correspondence with signers, so
    // the account sponsoring signer[i] is signerSponsoringIDs[i]. If
    // signerSponsoringIDs[i] is empty, then there is no sponsor
    SponsorshipDescriptor signerSponsoringIDs<20>;

    union switch (int v)
    {
    case 0:
        void;
    }
    ext;
};

struct AccountEntryExtensionV1
{
    Liabilities liabilities;

    union switch (int v)
    {
    case 0:
        void;
    case 2:
        AccountEntryExtensionV2 v2;
    }
    ext;
};

struct AccountEntry
{
    // ... unchanged ...

    union switch (int v)
    {
    case 0:
        void;
    case 1:
        AccountEntryExtensionV1 v1;
    }
    ext;
};

ClaimableBalanceEntry

Note that ClaimableBalanceEntry is not in the current protocol, so the XDR can still be modified. reserve has been removed, and sponsoringID has been replaced by sponsoringID in LedgerEntryExtensionV1.

struct ClaimableBalanceEntry
{
    // Unique identifier for this ClaimableBalanceEntry
    ClaimableBalanceID balanceID;

    // List of claimants with associated predicate
    Claimant claimants<10>;

    // Any asset including native
    Asset asset;

    // Amount of asset
    int64 amount;

    // reserved for future use
    union switch (int v)
    {
    case 0:
        void;
    }
    ext;
};

LedgerEntry

struct LedgerEntryExtensionV1
{
    // The account sponsoring this ledger entry. If sponsoringID is empty then
    // there is no sponsor
    SponsorshipDescriptor sponsoringID;

    union switch (int v)
    {
    case 0:
        void;
    }
    ext;
};

struct LedgerEntry
{
    // ... unchanged ...

    union switch (int v)
    {
    case 0:
        void;
    case 1:
        LedgerEntryExtensionV1 v1;
    }
    ext;
};

Operations

enum OperationType
{
    // ... CREATE_ACCOUNT, ..., MANAGE_BUY_OFFER unchanged ...
    PATH_PAYMENT_STRICT_SEND = 13,
    SPONSOR_FUTURE_RESERVES = 14,
    CONFIRM_AND_CLEAR_SPONSOR = 15,
    UPDATE_SPONSORSHIP = 16
};

struct SponsorFutureReservesOp
{
    // sponsoredID identifies the account for which future reserves will be
    // sponsored by the source account of this operation
    AccountID sponsoredID;
};

enum UpdateSponsorshipType
{
    UPDATE_SPONSORSHIP_LEDGER_ENTRY = 0,
    UPDATE_SPONSORSHIP_SIGNER = 1
};

union UpdateSponsorshipOp switch (UpdateSponsorshipType type)
{
case UPDATE_SPONSORSHIP_LEDGER_ENTRY:
    // Uniquely identifies a non-signer ledger entry
    LedgerKey ledgerKey;
case UPDATE_SPONSORSHIP_SIGNER:
    // Uniquely identifies a signer on a specific account
    struct
    {
        AccountID accountID;
        SignerKey signerKey;
    }
    signer;
};

struct Operation
{
    // sourceAccount is the account used to run the operation
    // if not set, the runtime defaults to "sourceAccount" specified at
    // the transaction level
    MuxedAccount* sourceAccount;

    union switch (OperationType type)
    {
    // ... CREATE_ACCOUNT, ..., PATH_PAYMENT_STRICT_SEND unchanged ...
    case SPONSOR_FUTURE_RESERVES:
        SponsorFutureReservesOp sponsorFutureReservesOp;
    case CONFIRM_AND_CLEAR_SPONSOR:
        void;
    case UPDATE_SPONSORSHIP:
        UpdateSponsorshipOp updateSponsorshipOp;
    }
    body;
};

Operation Results

enum AccountMergeResultCode
{
    // ... ACCOUNT_MERGE_SUCCESS, ..., ACCOUNT_MERGE_SEQNUM_TOO_FAR unchanged ...
    ACCOUNT_MERGE_DEST_FULL = -6,       // can't add source balance to
                                        // destination balance
    ACCOUNT_MERGE_IS_SPONSOR = -7       // can't merge account that is a sponsor
};

enum SponsorFutureReservesResultCode
{
    // codes considered as "success" for the operation
    SPONSOR_FUTURE_RESERVES_SUCCESS = 0,

    // codes considered as "failure" for the operation
    SPONSOR_FUTURE_RESERVES_MALFORMED = -1,         // can't sponsor self
    SPONSOR_FUTURE_RESERVES_ALREADY_SPONSORED = -2, // can't sponsor an account
                                                    // that is already sponsored
    SPONSOR_FUTURE_RESERVES_RECURSIVE = -3          // can't sponsor an account
                                                    // that is itself sponsoring
                                                    // an account
};

union SponsorFutureReservesResult switch (SponsorFutureReservesResultCode code)
{
case SPONSOR_FUTURE_RESERVES_SUCCESS:
    void;
default:
    void;
};

enum ConfirmAndClearSponsorResultCode
{
    // codes considered as "success" for the operation
    CONFIRM_AND_CLEAR_SPONSOR_SUCCESS = 0,

    // codes considered as "failure" for the operation
    CONFIRM_AND_CLEAR_SPONSOR_NOT_SPONSORED = -1 // must be sponsored to confirm
};

union ConfirmAndClearSponsorResult switch (ConfirmAndClearSponsorResultCode code)
{
case CONFIRM_AND_CLEAR_SPONSOR_SUCCESS:
    void;
default:
    void;
};

enum UpdateSponsorshipResultCode
{
    // codes considered as "success" for the operation
    UPDATE_SPONSORSHIP_SUCCESS = 0,

    // codes considered as "failure" for the operation
    UPDATE_SPONSORSHIP_DOES_NOT_EXIST = -1,   // specified entry does not exist
    UPDATE_SPONSORSHIP_NOT_SPONSOR = -2,      // not sponsor of specified entry
    UPDATE_SPONSORSHIP_LOW_RESERVE = -3,      // new reserve payor cannot afford
                                              // this entry
    UPDATE_SPONSORSHIP_ONLY_TRANSFERABLE = -4 // sponsorship of ClaimableBalance
                                              // must be transferred to another
                                              // account
};

union UpdateSponsorshipResult switch (UpdateSponsorshipResultCode code)
{
case UPDATE_SPONSORSHIP_SUCCESS:
    void;
default:
    void;
};

union OperationResult switch (OperationResultCode code)
{
case opINNER:
    union switch (OperationType type)
    {
    // ... CREATE_ACCOUNT, ..., PATH_PAYMENT_STRICT_SEND unchanged ...
    case SPONSOR_FUTURE_RESERVES:
        SponsorFutureReservesResult sponsorFutureReservesResult;
    case CONFIRM_AND_CLEAR_SPONSOR:
        ConfirmAndClearSponsorResult confirmAndClearSponsorResult;
    case UPDATE_SPONSORSHIP:
        UpdateSponsorshipResult updateSponsorshipResult;
    }
    tr;
default:
    void;
};

Transaction Results

enum TransactionResultCode
{
    // ... txFEE_BUMP_INNER_SUCCESS, ..., txNOT_SUPPORTED unchanged ...
    txFEE_BUMP_INNER_FAILED = -13, // fee bump inner transaction failed
    txBAD_SPONSORSHIP = -14 // sponsorship not confirmed and cleared
};

struct InnerTransactionResult
{
    // Always 0. Here for binary compatibility.
    int64 feeCharged;

    union switch (TransactionResultCode code)
    {
    // ... txFEE_BUMP_INNER_SUCCESS, ..., txFEE_BUMP_INNER_FAILED unchanged ...
    case txBAD_SPONSORSHIP:
        void;
    }
    result;

    // ext unchanged
};

Semantics

Reserve Requirement

No operation can take the native balance of an account below (2 + numSubEntries + numSponsoring - numSponsored) * baseReserve + liabilities.selling.

Sponsorship Bookkeeping

When account A is-sponsoring-future-reserves-for account B, any reserve requirements that would normally accumulate on B will instead accumulate on A as reflected in numSponsoring. Both accounts are guaranteed to exist, but this can fail. If such a change would cause A to pass below the reserve requirement, then the operation fails. The fact that these reserves are being provided by another account will be reflected on B in numSponsored.

When a sponsored ledger entry or sub-entry is removed, numSponsoring is decreased on the sponsoring account and numSponsored is decreased on the sponsored account. Both accounts are guaranteed to exist, so this cannot fail.

It is an invariant that, after every operation, the sum of all changes in numSponsoring exactly equals the sum of all changes in numSponsored.

Confirming Sponsorship

Within any transaction, every SponsorFutureReservesOp s must be paired with a subsequent ConfirmAndClearSponsorOp c with c.sourceAccount equal to s.sponsoredID. If this constraint is not met, a transaction that would otherwise have succeeded will fail with txBAD_SPONSORSHIP.

SponsorFutureReservesOp

SponsorFutureReservesOp is the only operation that can initiate the is-sponsoring-future-reserves-for relation. To check validity of SponsorFutureReservesOp op:

If op.sponsoredID = op.sourceAccount
    Invalid with SPONSOR_FUTURE_RESERVES_MALFORMED

The behavior of SponsorFutureReservesOp op is:

If an account is sponsoring future reserves for op.sponsoredID
    Fail with SPONSOR_FUTURE_RESERVES_ALREADY_SPONSORED

If an account is sponsoring future reserves for op.sourceAccount
    Fail with SPONSOR_FUTURE_RESERVES_RECURSIVE
If op.sponsoredID is sponsoring future reserves for any account
    Fail with SPONSOR_FUTURE_RESERVES_RECURSIVE

Record that op.sourceAccount is sponsoring future reserves for op.sponsoredID
Succeed with SPONSOR_FUTURE_RESERVES_SUCCESS

SponsorFutureReservesOp requires medium threshold.

ConfirmAndClearSponsorOp

ConfirmAndClearSponsorOp is the only operation that can terminate the is-sponsoring-future-reserves-for relation. ConfirmAndClearSponsorOp is always valid.

The behavior of ConfirmAndClearSponsorOp op is:

If an account is not sponsoring future reserves for op.sourceAccount
    Fail with CONFIRM_AND_CLEAR_SPONSOR_NOT_SPONSORED

Record that no account is sponsoring future reserves for op.sourceAccount
Succeed with CONFIRM_AND_CLEAR_SPONSOR_SUCCESS

ConfirmAndClearSponsorOp requires medium threshold.

UpdateSponsorshipOp

UpdateSponsorshipOp allows the sponsor of existing reserves to transfer sponsorship to another account. If an account is-sponsoring-future-reserves-for the source account, then the new sponsor will be that account. If no account is-sponsoring-future-reserves-for the source account, then there will be no sponsor. Note that UpdateSponsorshipOp does not obey the typical rules of sponsorship. UpdateSponsorshipOp is always valid.

The behavior of UpdateSponsorshipOp op is as follows:

If op.type() == UPDATE_SPONSORSHIP_LEDGER_ENTRY
    mult = op.ledgerKey().type() != ACCOUNT ? 1 : 2
    requiredReserve = mult * baseReserve

    If no LedgerEntry le with LedgerKey(le) == op.ledgerKey() exists
        Fail with UPDATE_SPONSORSHIP_DOES_NOT_EXIST

    wasSponsored = false
    Load LedgerEntry le with LedgerKey(le) == op.ledgerKey()
    If le.ext.v() == 0 || !le.ext.v1().sponsoringID
        If Account(le) != op.sourceAccount
            Fail with UPDATE_SPONSORSHIP_NOT_SPONSOR
    Else
        If le.ext.v1().sponsoringID != op.sourceAccount
            Fail with UPDATE_SPONSORSHIP_NOT_SPONSOR
        wasSponsored = true

        Load AccountEntry source with source.accountID == op.sourceAccount
        source.ext.v1().ext.v2().numSponsoring -= mult

    If an account sID is sponsoring future reserves for op.sourceAccount
        Load AccountEntry acc with acc.accountID == sID
        If AvailableBalance(acc, NATIVE) < requiredReserve
            Fail with UPDATE_SPONSORSHIP_LOW_RESERVE
        acc.ext.v1().ext.v2().numSponsoring += mult

        le.ext.v(1)
        le.ext.v1().sponsoringID = sID
    Else
        If op.ledgerKey().type() == CLAIMABLE_BALANCE
            Fail with UPDATE_SPONSOR_ONLY_TRANSFERABLE

        Load AccountEntry acc with acc.accountID == Account(le)
        If AvailableBalance(acc, NATIVE) < requiredReserve
            Fail with UPDATE_SPONSORSHIP_LOW_RESERVE

        If wasSponsored
            acc.ext.v1().ext.v2().numSponsored -= mult
        Clear le.ext.v1().sponsoringID

Else if op.type() == UPDATE_SPONSORSHIP_SIGNER
    If no AccountEntry acc with acc.accountID == op.signer().accountID exists
        Fail with UPDATE_SPONSORSHIP_DOES_NOT_EXIST

    Load AccountEntry a with acc.accountID == op.signer().accountID
    If op.signer().signerKey not in acc.signers
        Fail with UPDATE_SPONSORSHIP_DOES_NOT_EXIST
    Let i be such that a.signers[i].signerKey == op.signer().signerKey

    wasSponsored = false
    If a.ext.v() == 0 || a.ext.v1().ext.v() == 0
        If op.signer().accountID != op.sourceAccount
            Fail with UPDATE_SPONSORSHIP_NOT_SPONSOR
    Else if a.ext.v1().ext.v() == 2
        If a.ext.v1().ext.v2().signerSponsoringIDs[i] != op.sourceAccount
            Fail with UPDATE_SPONSORSHIP_NOT_SPONSOR
        wasSponsored = true

        Load AccountEntry source with source.accountID == op.sourceAccount
        source.ext.v1().ext.v2().numSponsoring--

    If an account sID is sponsoring future reserves for op.sourceAccount
        Load AccountEntry acc with acc.accountID == sID
        If AvailableBalance(acc, NATIVE) < baseReserve
            Fail with UPDATE_SPONSORSHIP_LOW_RESERVE
        acc.ext.v1().ext.v2().numSponsoring++

        Set a.ext.v(1)
        Set a.ext.v1().ext.v(2)
        a.ext.v1().ext.v2().signerSponsoringIDs[i].sponsoringID = sID
    Else
        If AvailableBalance(a, NATIVE) < requiredReserve
            Fail with UPDATE_SPONSORSHIP_LOW_RESERVE

        If wasSponsored
            a.ext.v1().ext.v2().numSponsoring--
        Clear a.ext.v1().ext.v2().signerSponsoringIDs[i].sponsoringID

Succeed with UPDATE_SPONSORSHIP_SUCCESS

UpdateSponsorshipOp requires medium threshold.

AccountMergeOp

AccountMergeOp will fail with ACCOUNT_MERGE_IS_SPONSOR if attempting to merge an account that is-sponsoring-future-reserves-for another account. This guarantees that the sponsoring account always exists.

Similarly, AccountMergeOp will fail with ACCOUNT_MERGE_IS_SPONSOR if attempting to merge an account that has non-zero numSponsoring. This is required for sponsorship bookkeeping.

CreateAccountOp

CreateAccountOp will now be valid if startingBalance >= 0, whereas prior to this proposal it was valid if startingBalance > 0.

Other Operations

Other operations will need updated semantics in order to behave correctly with this proposal. Specifically, changes will be required to handle sponsorship wherever ledger entries are created or removed.

Design Rationale

Sponsorship Logic is Off-Chain

In CAP-0031, an alternative approach to sponsorship, the logic for determining what can be sponsored is stored on the ledger. Not only is this complicated to implement and reason about, but it also introduces a variety of limitations in terms of what logic is supported. Moving sponsorship logic off-chain through the "sandwich approach", analogous to what is done in CAP-0018, eliminates all of these disadvantages.

Why Should Sponsorship be Ephemeral?

There are a variety of reasons that the is-sponsoring-future-reserves-for relation is ephemeral (by which I mean contained within a single transaction). From a practical perspective, it would be deeply unwise to delegate to another party the right to make decisions about how your funds can be used to pay reserves. If you were to do this, then the other party could drain your entire balance.

But the technical reasons are far more compelling. Ephemeral sponsorship guarantees that both the sponsoring and sponsored accounts must sign any transaction that establishes a sponsorship relation. This applies even in the case of sponsorship for CreateAccountOp, whereas CreateAccountOp can usually be used without a signature from the created account. As a consequence, sponsorship introduces absolutely no backwards compatibility issues with regard to pre-signed transactions or pre-authorized transactions.

Because every sponsorship requires agreement from the sponsoring and sponsored accounts, it is safe to allow sponsorship revocation when the sponsored account can afford the reserve itself. That is part of the contract of the sponsorship relation, and if you didn't want revocation to occur then you shouldn't have accepted the sponsorship in the first place.

Sponsoring Accounts Cannot be Merged

An account that is-sponsoring-future-reserves-for another account cannot be merged. This does not constrain functionality at all, but simplifies the implementation and reduces the number of possible errors that can be encountered by downstream systems. Any transaction where this requirement would have been the difference between success and failure necessarily contains both a SponsorFutureReservesOp and a ConfirmAndClearSponsorOp. These operations could simply be rearranged to permit the merge.

An account that is the current sponsor for any ledger entry or sub-entry cannot be merged. If you want to merge such an account, then you can use UpdateSponsorshipOp to transfer the sponsorships to another account. Again, this restriction greatly simplifies the implementation and semantics.

Sequence Number Utilization and Sponsoring Account Creation

A typical use case for sponsorship is an enterprise sponsoring the reserves for customers. Because sponsorship requires signatures from both the sponsoring account and the sponsored account, this requires multi-signature coordination. But an enterprise may be coordinating many such signatures at once. Each coordination requires a sequence number. When sponsoring sub-entries such as signers and trust lines, it is possible to use the customer's sequence number. But sponsoring account creation requires the enterprise to provide the sequence number. This will likely require a pool of channel accounts, which is a source of complexity.

Despite the complexity that will be required for large-scale creation of sponsored accounts, we still believe that this approach is justified by its many benefits. Furthermore, the need for channel accounts during multi-signature coordination is an area in which the Stellar protocol can be generally improved. Already there are discussions of ideas which may mitigate some of these issues such as David Mazieres' proposal for Authenticated Operations, which can be found at https://groups.google.com/forum/#!msg/stellar-dev/zpO0Eppn8ks/e1ULbV_lAgAJ.

Why are Signer Sponsoring IDs Stored Separately from Signers?

For ledger entries, the sponsoring ID is stored within the ledger entry. But for signers, the sponsoring ID is stored separately. Why take different approaches? The reason is that LedgerEntry has an extension point but Signer does not. So if we wanted to store the sponsoring ID in the signer, then we would need to extend the SignerKey union. But this would force any downstream system that interacts with signers to update as well, introducing complexity throughout the ecosystem. Storing the sponsoring IDs for signers separately avoids this problem entirely, but it is perhaps slightly less elegant. Still, the trade-off is more than justified.

Why Doesn't UpdateSponsorshipOp Obey Typical Rules of Sponsorship?

The relationship between sponsoring account and sponsored account can only be created by mutual agreement. When this relationship is established, the sponsoring account is endowed with the right to unilaterally end the relationship (ie. revoke the sponsorship) as long as doing so will not leave the sponsored account below the reserve requirement. But revoking the sponsorship is strictly worse for the sponsored account than transferring the sponsorship, because the worst thing that the new sponsoring account can do is revoke the sponsorship. Therefore, the sponsoring account should be able to transfer the sponsorship with the consent of the new sponsoring account but unilaterally with respect to the sponsored account.

So how would a transfer work if UpdateSponsorshipOp were to obey the typical rules of sponsorship? Let S1 be the current sponsor, S2 be the new sponsor, K be the LedgerKey for an entry sponsored by S1, and A be the account which controls the entry identified by K. We would need a transaction with

  1. S2 is source for SponsorFutureReservesOp with sponsoredID = A
  2. S1 is source for UpdateSponsorshipOp with ledgerKey = K
  3. A is source for ConfirmAndClearSponsorshipOp

But this clearly requires a signature from the sponsored account, so if we are to satisfy the "unilateral" semantics described above then this approach cannot work.

This also resolves an issue for ClaimableBalanceEntry, because there is no notion of an account that controls a ClaimableBalanceEntry.

Example: Sponsoring Account Creation

In this example, we demonstrate how an account can be sponsored upon creation. Let S be the sponsoring account, C be the creating account, and A the newly created account (S and C may be the same account). Then the following transaction achieves the desired goal:

sourceAccount: C
fee: <FEE>
seqNum: <SEQ_NUM>
timeBounds: <TIME_BOUNDS>
memo:
    type: MEMO_NONE
operations[0]:
    sourceAccount: S
    body:
        type: SPONSOR_FUTURE_RESERVES
        sponsoredID: A
operations[1]:
    sourceAccount: C
    body:
        type: CREATE_ACCOUNT
        destination: A
        startingBalance: <STARTING_BALANCE>
operations[2]:
    sourceAccount: A
    body:
        type: CONFIRM_AND_CLEAR_SPONSOR

where <FEE>, <SEQ_NUM>, <TIME_BOUNDS>, and <STARTING_BALANCE> should all be substituted appropriately. Note that this requires a signature from A even though that account is being created.

Example: Two Trust Lines with Different Sponsors

In this example, we demonstrate how a single account can create two trust lines which are sponsored by different accounts in a single transaction. Let S1 and S2 be the sponsoring accounts. Let A be the sponsored account. Then the following transaction achieves the desired goal:

sourceAccount: A
fee: <FEE>
seqNum: <SEQ_NUM>
timeBounds: <TIME_BOUNDS>
memo:
    type: MEMO_NONE
operations[0]:
    sourceAccount: S1
    body:
        type: SPONSOR_FUTURE_RESERVES
        sponsoredID: A
operations[1]:
    sourceAccount: A
    body:
        type: CHANGE_TRUST
        line: X
        limit: INT64_MAX
operations[2]:
    sourceAccount: A
    body:
        type: CONFIRM_AND_CLEAR_SPONSOR
operations[3]:
    sourceAccount: S2
    body:
        type: SPONSOR_FUTURE_RESERVES
        sponsoredID: A
operations[4]:
    sourceAccount: A
    body:
        type: CHANGE_TRUST
        line: Y
        limit: INT64_MAX
operations[5]:
    sourceAccount: A
    body:
        type: CONFIRM_AND_CLEAR_SPONSOR

where <FEE>, <SEQ_NUM>, and <TIME_BOUNDS> should all be substituted appropriately.

Example: Revoke Sponsorship

In this example, we demonstrate how a sponsorship can be revoked. Let S be the sponsoring account. Let K be the LedgerKey for an entry which is currently sponsored by S. Then the following transaction achieves the desired goal:

sourceAccount: S
fee: <FEE>
seqNum: <SEQ_NUM>
timeBounds: <TIME_BOUNDS>
memo:
    type: MEMO_NONE
operations[0]:
    sourceAccount: S
    body:
        type: UPDATE_SPONSORSHIP
        ledgerKey: K

where <FEE>, <SEQ_NUM>, and <TIME_BOUNDS> should all be substituted appropriately.

Example: Transfer Sponsorship

In this example, we demonstrate how a sponsorship can be transferred. Let S1 and S2 be the sponsoring accounts. Let K be the LedgerKey for an entry which is currently sponsored by S1. Then the following transaction achieves the desired goal:

sourceAccount: S1
fee: <FEE>
seqNum: <SEQ_NUM>
timeBounds: <TIME_BOUNDS>
memo:
    type: MEMO_NONE
operations[0]:
    sourceAccount: S2
    body:
        type: SPONSOR_FUTURE_RESERVES
        sponsoredID: S1
operations[1]:
    sourceAccount: S1
    body:
        type: UPDATE_SPONSORSHIP
        ledgerKey: K
operations[2]:
    sourceAccount: S1
    body:
        type: CONFIRM_AND_CLEAR_SPONSOR

where <FEE>, <SEQ_NUM>, and <TIME_BOUNDS> should all be substituted appropriately.

Backwards Incompatibilities

New XDR

All downstream systems will need updated XDR in order to recognize the new operations and the modified ledger entries.

Operation Validity Changes

Any downstream system relying on the fact that CreateAccountOp is invalid if startingBalance = 0 will be affected.

Security Concerns

This proposal will slightly reduce the efficacy of base reserve changes, because sponsored ledger entries cannot cause an account to pass below the reserve requirement.

Test Cases

None yet.

Implementation

None yet.