Skip to content

Commit

Permalink
CAP-33: Do not move reserves (#634)
Browse files Browse the repository at this point in the history
* Add missing extension to AccountEntryExtensionV2

* CAP-33 do not move reserves
  • Loading branch information
jonjove committed Jun 8, 2020
1 parent 6d62137 commit 725cb17
Showing 1 changed file with 78 additions and 77 deletions.
155 changes: 78 additions & 77 deletions core/cap-0033.md
Expand Up @@ -64,18 +64,20 @@ work for claimable balance entries.

#### AccountEntry
```c++
struct SponsorshipDescriptor
{
AccountID sponsoringID;
uint32 reserve;
};

typedef SponsorshipDescriptor* SignerSponsorship;
typedef AccountID* SponsorshipDescriptor;

struct AccountEntryExtensionV2
{
uint32 numSponsored;
SignerSponsorship signerSponsorships<20>;
uint32 numSponsoring;
SponsorshipDescriptor signerSponsoringIDs<20>;

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

struct AccountEntryExtensionV1
Expand Down Expand Up @@ -109,8 +111,8 @@ struct AccountEntry

#### ClaimableBalanceEntry
Note that `ClaimableBalanceEntry` is not in the current protocol, so the XDR can
still be modified. The only change here is that `createdBy` and `reserve` have
been replaced by `sponsoringID` and `reserve` in `LedgerEntryExtensionV1`.
still be modified. `reserve` has been removed, and `sponsoringID` has been
replaced by `sponsoringID` in `LedgerEntryExtensionV1`.

```c++
struct ClaimableBalanceEntry
Expand Down Expand Up @@ -141,7 +143,7 @@ struct ClaimableBalanceEntry
```c++
struct LedgerEntryExtensionV1
{
SponsorshipDescriptor* sponsorship;
SponsorshipDescriptor sponsoringID;

union switch (int v)
{
Expand Down Expand Up @@ -177,19 +179,9 @@ enum OperationType
UPDATE_SPONSORSHIP = 16
};

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
// sponsoring future reserves
};

struct SponsorFutureReservesOp
{
AccountID sponsoredID;
uint32 maxBaseReserve;
};

enum UpdateSponsorshipType
Expand Down Expand Up @@ -234,15 +226,22 @@ struct Operation

#### Operation Results
```c++
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,
SPONSOR_FUTURE_RESERVES_BASE_RESERVE_TOO_HIGH = -2,
SPONSOR_FUTURE_RESERVES_ALREADY_SPONSORED = -3
SPONSOR_FUTURE_RESERVES_ALREADY_SPONSORED = -2
};

union SponsorFutureReservesResult switch (SponsorFutureReservesResultCode code)
Expand Down Expand Up @@ -279,7 +278,7 @@ enum UpdateSponsorshipResultCode
UPDATE_SPONSORSHIP_DOES_NOT_EXIST = -1,
UPDATE_SPONSORSHIP_NOT_SPONSOR = -2,
UPDATE_SPONSORSHIP_LINE_FULL = -3,
UPDATE_SPONSORSHIP_UNDERFUNDED = -4,
UPDATE_SPONSORSHIP_LOW_RESERVE = -4,
UPDATE_SPONSORSHIP_ONLY_TRANSFERABLE = -5
};

Expand Down Expand Up @@ -340,29 +339,29 @@ struct InnerTransactionResult

#### Reserve Requirement
No operation can take the native balance of an account below
`(2 + numSubEntries - numSponsored) * baseReserve + liabilities.selling`.
`(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`.

#### Sponsoring Future Reserves
When account `A` is-sponsoring-future-reserves-for account `B`, balance will be
deducted from `A` and "stored" in any ledger entry/sub-entry for which `B` would
need to provide reserve. Sponsoring account creation (if `B` does not yet exist)
would deduct `2 * baseReserve` whereas sponsoring any other sub-entry would
deduct just `baseReserve`. If `A` does not have sufficient available balance of
native asset, then the operation fails.
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`.

#### Removing a Sponsored Ledger Entry or Sub-Entry
When a sponsored ledger entry or sub-entry is removed, the `reserve` is returned
to the sponsoring account as specified in `sponsoringID`. It is possible that
the sponsoring account does not exist or does not have sufficient available
limit to receive the `reserve`, in which case the `reserve` will be sent to the
fee pool. If this happens, then the `reserve` is lost forever.

#### SponsorFutureReservesOp
`SponsorFutureReservesOp` is the only operation that can initiate the
is-sponsoring-future-reserves-for relation. To check validity of
Expand All @@ -371,8 +370,6 @@ is-sponsoring-future-reserves-for relation. To check validity of
```
If op.sponsoredID = op.sourceAccount
Invalid with SPONSOR_FUTURE_RESERVES_MALFORMED
If op.maxBaseReserve < baseReserve
Invalid with SPONSOR_FUTURE_RESERVES_BASE_RESERVE_TOO_HIGH
```

The behavior of `SponsorFutureReservesOp op` is:
Expand Down Expand Up @@ -411,47 +408,44 @@ account). `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
If le.ext.v() == 0 || !le.ext.v1().sponsoringID
If Account(le) != op.sourceAccount
Fail with UPDATE_SPONSORSHIP_NOT_SPONSOR
Else if le.ext.v() == 1
Else
If le.ext.v1().sponsorship.sponsoringID != op.sourceAccount
Fail with UPDATE_SPONSORSHIP_NOT_SPONSOR
wasSponsored = true
Load AccountEntry source with source.accountID == op.sourceAccount
If AvailableLimit(source, NATIVE) < le.ext.v1().sponsorship.reserve
Fail with UPDATE_SPONSORSHIP_LINE_FULL
source.balance += le.ext.v1().sponsorship.reserve
mult = op.ledgerKey().type() != ACCOUNT ? 1 : 2
requiredReserve = mult * baseReserve
source.ext.v1().ext.v2().numSponsoring -= mult
If an account sID is sponsoring future reserves for Account(le)
Load AccountEntry acc with acc.accountID == sID
If AvailableBalance(acc, NATIVE) < requiredReserve
Fail with UPDATE_SPONSORSHIP_UNDERFUNDED
acc.balance -= requiredReserve
Fail with UPDATE_SPONSORSHIP_LOW_RESERVE
acc.ext.v1().ext.v2().numSponsoring += mult
le.ext.v(1)
le.ext.v1().sponsorship.sponsoringID = sID
le.ext.v1().sponsorship.reserve = requiredReserve
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_UNDERFUNDED
Fail with UPDATE_SPONSORSHIP_LOW_RESERVE
If wasSponsored
acc.numSponsored -= mult
Set le.ext.v(0)
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
Expand All @@ -467,51 +461,44 @@ Else if op.type() == UPDATE_SPONSORSHIP_SIGNER
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().signerSponsorships[i] != op.sourceAccount
If a.ext.v1().ext.v2().signerSponsoringIDs[i] != op.sourceAccount
Fail with UPDATE_SPONSORSHIP_NOT_SPONSOR
wasSponsored = true
reserve = a.ext.v1().ext.v2().signerSponsorships[i].reserve
Load AccountEntry source with source.accountID == op.sourceAccount
If AvailableLimit(source, NATIVE) < reserve
Fail with UPDATE_SPONSORSHIP_LINE_FULL
source.balance += reserve
source.ext.v1().ext.v2().numSponsoring--
If an account sID is sponsoring future reserves for Account(le)
Load AccountEntry acc with acc.accountID == sID
If AvailableBalance(acc, NATIVE) < baseReserve
Fail with UPDATE_SPONSORSHIP_UNDERFUNDED
acc.balance -= baseReserve
Fail with UPDATE_SPONSORSHIP_LOW_RESERVE
acc.ext.v1().ext.v2().numSponsoring++
Set acc.ext.v(1)
Set acc.ext.v1().ext.v(2)
acc.ext.v1().ext.v2().signerSponsorships[i].sponsoringID = s.sponsoringID
acc.ext.v1().ext.v2().signerSponsorships[i].reserve = baseReserve
Set a.ext.v(1)
Set a.ext.v1().ext.v(2)
a.ext.v1().ext.v2().signerSponsoringIDs[i].sponsoringID = s.sponsoringID
Else
Load AccountEntry acc with acc.accountID == Account(le)
If AvailableBalance(acc, NATIVE) < requiredReserve
Fail with UPDATE_SPONSORSHIP_UNDERFUNDED
If AvailableBalance(a, NATIVE) < requiredReserve
Fail with UPDATE_SPONSORSHIP_LOW_RESERVE
If wasSponsored
acc.numSponsored -= mult
Set acc.ext.v(1)
Set acc.ext.v1().ext.v(0)
a.ext.v1().ext.v2().numSponsoring--
Clear a.ext.v1().ext.v2().signerSponsoringIDs[i].sponsoringID
Succeed with UPDATE_SPONSORSHIP_SUCCESS
```

`UpdateSponsorshipOp` requires medium threshold.

#### ClaimableBalanceEntry
`ClaimableBalanceEntry` has semantics equivalent to what is described in
CAP-0023, except that `createdBy` and `reserve` are everywhere replaced by
`sponsoringID` and `reserve` in the `LedgerEntry` extension.

#### 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.

#### Other Operations
Other operations will need updated semantics in order to behave correctly with
this proposal. Specifically, changes will be required to handle sponsorship
Expand Down Expand Up @@ -549,6 +536,20 @@ 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
Expand Down

0 comments on commit 725cb17

Please sign in to comment.