## Preamble ``` CAP: 0023 Title: Two-Part Payments with ClaimableBalanceEntry Author: Jonathan Jove Status: Final Created: 2019-06-04 Updated: 2020-02-05 Discussion: https://github.com/stellar/stellar-protocol/issues/303 Protocol version: 14 ``` ## Simple Summary Payments can fail depending on the state of the destination account. This proposal introduces new operations that separate sending a payment from receiving the payment. Then the success of sending depends only on the state of the sending account and success of receiving depends only on the state of the receiving account. ## Motivation This proposal seeks to solve the following problem: it should be easy to send a payment to an account that is not necessarily prepared to receive the payment. There are several manifestations of this problem, the two most important being 1. it should be easy for protocols (like an implementation of payment channels) to pay out to participants, and 2. it should be easy for issuers to issue assets non-interactively. ### Goals Alignment This proposal is aligned with several Stellar Network Goals, among them: - The Stellar Network should facilitate simplicity and interoperability with other protocols and networks. - 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. ## Abstract We introduce `ClaimableBalanceEntry` as a new type of `LedgerEntry` which represents the transfer of ownership of some amount of an asset. The operations `CreateClaimableBalanceOp` and `ClaimClaimableBalanceOp` allow the creation and consumption of claimable balance entries, which permits temporal separation of initiating and reciving a payment. Existing proposals, such as those for deterministic accounts, can provide a similar mechanism but are not able to handle authorization restricted assets as easily. A specific and simple protocol that will be facilitated is the asset issuance protocol that issues an asset to a given account, regardless of whether it exists or is prepared to receive the funds. ## Specification ### XDR First, we introduce `ClaimableBalanceEntry`(Note that the XDR was updated in https://github.com/stellar/stellar-protocol/blob/master/core/cap-0033.md#claimablebalanceentry) and the corresponding changes for `LedgerEntryType` and `LedgerEntry`. ```c++ enum LedgerEntryType { // ... ACCOUNT, TRUSTLINE, OFFER, unchanged ... DATA = 3, CLAIMABLE_BALANCE = 4 }; enum ClaimPredicateType { CLAIM_PREDICATE_UNCONDITIONAL = 0, CLAIM_PREDICATE_AND = 1, CLAIM_PREDICATE_OR = 2, CLAIM_PREDICATE_NOT = 3, CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME = 4, CLAIM_PREDICATE_BEFORE_RELATIVE_TIME = 5 }; union ClaimPredicate switch (ClaimPredicateType type) { case CLAIM_PREDICATE_UNCONDITIONAL: void; case CLAIM_PREDICATE_AND: ClaimPredicate andPredicates<2>; case CLAIM_PREDICATE_OR: ClaimPredicate orPredicates<2>; case CLAIM_PREDICATE_NOT: ClaimPredicate* notPredicate; case CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME: int64 absBefore; // Will return true if closeTime < absBefore case CLAIM_PREDICATE_BEFORE_RELATIVE_TIME: int64 relBefore; // Seconds since closeTime of the ledger in which the // ClaimableBalanceEntry was created }; enum ClaimantType { CLAIMANT_TYPE_V0 = 0 }; union Claimant switch (ClaimantType type) { case CLAIMANT_TYPE_V0: struct { AccountID destination; // The account that can use this condition ClaimPredicate predicate; // Claimable if predicate is true } v0; }; enum ClaimableBalanceIDType { CLAIMABLE_BALANCE_ID_TYPE_V0 = 0 }; union ClaimableBalanceID switch (ClaimableBalanceIDType type) { case CLAIMABLE_BALANCE_ID_TYPE_V0: Hash v0; }; struct ClaimableBalanceEntry { // Unique identifier for this ClaimableBalanceEntry ClaimableBalanceID balanceID; // Account that created this ClaimableBalanceEntry AccountID createdBy; // List of claimants with associated predicate Claimant claimants<10>; // Any asset including native Asset asset; // Amount of asset int64 amount; // 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 CLAIMABLE_BALANCE: ClaimableBalanceEntry claimableBalance; } data; // reserved for future use union switch (int v) { case 0: void; } ext; }; struct LedgerKey { // ... ACCOUNT, TRUSTLINE, OFFER, DATA unchanged ... case CLAIMABLE_BALANCE: struct { ClaimableBalanceID balanceID; } claimableBalance; }; ``` Second, we introduce the new operations `CreateClaimableBalanceOp` and `ClaimClaimableBalanceOp` as well as the corresponding changes to `OperationType` and `Operation`. We also introduce the type `OperationID` to represent the hash preimage for `ClaimableBalanceID`, along with a new `EnvelopeType`. ```c++ enum OperationType { // ... CREATE_ACCOUNT, ..., MANAGE_BUY_OFFER unchanged ... PATH_PAYMENT_STRICT_SEND = 13, CREATE_CLAIMABLE_BALANCE = 14, CLAIM_CLAIMABLE_BALANCE = 15 }; struct CreateClaimableBalanceOp { Asset asset; int64 amount; Claimant claimants<10>; }; struct ClaimClaimableBalanceOp { ClaimableBalanceID balanceID; }; 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_CLAIMABLE_BALANCE: CreateClaimableBalanceOp createClaimableBalanceOp; case CLAIM_CLAIMABLE_BALANCE: ClaimClaimableBalanceOp claimClaimableBalanceOp; } body; }; union OperationID switch (EnvelopeType type) { case ENVELOPE_TYPE_OP_ID: struct { AccountID sourceAccount; SequenceNumber seqNum; uint32 opNum; } id; }; enum EnvelopeType { // ... ENVELOPE_TYPE_TX_V0, ..., ENVELOPE_TYPE_TX_FEE_BUMP unchanged ... ENVELOPE_TYPE_OP_ID = 6 }; ``` Third, we introduce the result types `CreateClaimableBalanceResult` and `ClaimClaimableBalanceResult` as well as the corresponding changes to `OperationResult`. ```c++ enum CreateClaimableBalanceResultCode { CREATE_CLAIMABLE_BALANCE_SUCCESS = 0, CREATE_CLAIMABLE_BALANCE_MALFORMED = -1, CREATE_CLAIMABLE_BALANCE_LOW_RESERVE = -2, CREATE_CLAIMABLE_BALANCE_NO_TRUST = -3, CREATE_CLAIMABLE_BALANCE_NOT_AUTHORIZED = -4, CREATE_CLAIMABLE_BALANCE_UNDERFUNDED = -5 }; union CreateClaimableBalanceResult switch (CreateClaimableBalanceResultCode code) { case CREATE_CLAIMABLE_BALANCE_SUCCESS: ClaimableBalanceID balanceID; default: void; }; enum ClaimClaimableBalanceResultCode { CLAIM_CLAIMABLE_BALANCE_SUCCESS = 0, CLAIM_CLAIMABLE_BALANCE_DOES_NOT_EXIST = -1, CLAIM_CLAIMABLE_BALANCE_CANNOT_CLAIM = -2, CLAIM_CLAIMABLE_BALANCE_LINE_FULL = -3, CLAIM_CLAIMABLE_BALANCE_NO_TRUST = -4, CLAIM_CLAIMABLE_BALANCE_NOT_AUTHORIZED = -5 }; union ClaimClaimableBalanceResult switch (ClaimClaimableBalanceResultCode code) { case CLAIM_CLAIMABLE_BALANCE_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, ..., PATH_PAYMENT_STRICT_SEND unchanged ... case CREATE_CLAIMABLE_BALANCE: CreateClaimableBalanceResult createClaimableBalanceResult; case CLAIM_CLAIMABLE_BALANCE: ClaimClaimableBalanceResult claimClaimableBalanceResult; } body; }; ``` ### Semantics #### CreateClaimableBalanceOp A `ClaimableBalanceEntry` can only be created by the `CreateClaimableBalanceOp` operation. `CreateClaimableBalanceOp` is invalid with `CREATE_CLAIMABLE_BALANCE_MALFORMED` if - `asset` is invalid - `amount <= 0` - `claimants` has length 0 - `claimants[i].destination = claimants[j].destination` (for any `i != j`) - `claimants[i].predicate` has depth greater than 4 (for any `i`) - `claimants[i].predicate` contains a predicate of type `CLAIM_PREDICATE_AND` with `andPredicates.size() != 2`, `CLAIM_PREDICATE_OR` with `orPredicates.size() != 2`, or `CLAIM_PREDICATE_NOT` with a null `notPredicate` (for any `i`) - `claimants[i].predicate` contains a predicate of type `CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME` or `CLAIM_PREDICATE_BEFORE_RELATIVE_TIME` with `absBefore < 0` or `relBefore < 0` (for any `i`) The behavior of `CreateClaimableBalanceOp` is as follows: 1. Fail with `CREATE_CLAIMABLE_BALANCE_LOW_RESERVE` if the `sourceAccount` does not have at least `claimants.size() * baseReserve` available balance of native asset 2. Deduct `claimants.size() * baseReserve` of native asset from `sourceAccount` 3. Fail with `CREATE_CLAIMABLE_BALANCE_NO_TRUST` if the `sourceAccount` does not have a trust line for `asset` 4. Fail with `CREATE_CLAIMABLE_BALANCE_NOT_AUTHORIZED` if the `sourceAccount` trust line for `asset` does not have `AUTHORIZED_FLAG` set 5. Fail with `CREATE_CLAIMABLE_BALANCE_UNDERFUNDED` if the `sourceAccount` does not have at least `amount` available balance of `asset` 6. Deduct `amount` of `asset` from `sourceAccount` 7. Create a claimable balance entry with the following properties: - `balanceID` of type `CLAIMABLE_BALANCE_ID_TYPE_V0`.[^id-arithmatic] - `createdBy = sourceAccount` (of the transaction, not the operation) - `claimants` as specified, with the exception that - `CLAIM_PREDICATE_BEFORE_RELATIVE_TIME` will be converted to `CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME` by adding `relBefore` to the `closeTime` in the `LedgerHeader`. If this addition exceeds `INT64_MAX` then use `INT64_MAX`. - `asset` as specified in the operation - `amount` as specified in the operation - `reserve` equal to `claimants.size() * baseReserve` [^id-arithmatic]: ```javascript HashIDPreimage = ( sourceAccount; seqNum; opNum; ) OperationID = sha256(HashIDPreimage) balanceID.v0() = OperationID // Hash clientDisplayID = hex(HashIDPreimage) ``` - `HashIDPreimage`: a switch within the new `type` `ENVELOPE_TYPE_OP_ID` - `OperationID`: hash of `ENVELOPE_TYPE_OP_ID` precomputation data - `sourceAccount`: unmuxed public key of the transaction's source - `seqNum`: the transaction source account's sequence number - `opNum`: position index of this operation in the transaction 8. Succeed with `CREATE_CLAIMABLE_BALANCE_SUCCESS` and the `balanceID` from the previous step. `CreateClaimableBalanceOp` requires medium threshold because it can be used to send funds. #### ClaimClaimableBalanceOp A `ClaimableBalanceEntry` can only be deleted by the `ClaimClaimableBalanceOp` operation. `ClaimClaimableBalanceOp` cannot be invalid. The behavior of `ClaimClaimableBalanceOp` is as follows: 1. Fail with `CLAIM_CLAIMABLE_BALANCE_DOES_NOT_EXIST` if there is no `ClaimableBalanceEntry` matching `balanceID`. 2. Fail with `CLAIM_CLAIMABLE_BALANCE_CANNOT_CLAIM` if there is no `i` such that `claimants[i].destination = sourceAccount` or if `claimants[i].predicate` is not satisfied 3. Skip to step 7 if `createdBy` does not exist 4. Skip to step 7 if `createdBy` does not have at least `reserve` available limit of native asset 5. Add `reserve` of native asset to `createdBy` 6. Skip to step 9 7. Fail with `CLAIM_CLAIMABLE_BALANCE_LINE_FULL` if the `sourceAccount` does not have at least `reserve` available limit of native asset 8. Add `reserve` of native asset to `sourceAccount` 9. Fail with `CLAIM_CLAIMABLE_BALANCE_NO_TRUST` if `asset` is not of type `ASSET_TYPE_NATIVE` and the `sourceAccount` trust line for `asset` does not exist 10. Fail with `CLAIM_CLAIMABLE_BALANCE_NOT_AUTHORIZED` if `asset` is not of type `ASSET_TYPE_NATIVE` and the `sourceAccount` trust line for `asset` does not have the `AUTHORIZED_FLAG` flag set 11. Fail with `CLAIM_CLAIMABLE_BALANCE_LINE_FULL` if the `sourceAccount` does not have at least `amount` available limit of `asset` 12. Add `amount` of `asset` to the `sourceAccount` 13. Delete the `ClaimableBalanceEntry` 14. Succeed with `CLAIM_CLAIMABLE_BALANCE_SUCCESS` `ClaimClaimableBalanceOp` requires low threshold because it can only be used to transfer funds from a `ClaimableBalanceEntry` to a trust line. ## Design Rationale ### ClaimableBalanceEntry is not a sub-entry Each `ClaimableBalanceEntry` exists as an independent entity on the ledger. It is clear that a `ClaimableBalanceEntry` cannot be a sub-entry of any its `claimants`, 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? There are two main benefits of this design: 1. Sending accounts are not limited in the number of claimable balance entries they can create 2. Sending accounts can be merged even if they created claimable balance entries that have not yet been claimed ### ClaimableBalanceEntry claimants are accounts For each `ClaimableBalanceEntry`, `claimants` contains a finite and immutable list of accounts that could potentially claim the `ClaimableBalanceEntry`. Even if the conditions are satisfiable (which is not guaranteed), it is still possible for the `ClaimableBalanceEntry` to become stranded. If all of the accounts listed in `claimants` are merged and none of the private keys are known, then the `ClaimableBalanceEntry` will no longer be claimable. Suppose that we try to relax this requirement in order to avoid this downside. We could instead make `claimants` contain a finite and immutable list of public keys. The operation to claim the `ClaimableBalanceEntry` could then contain a signature over the tuple `(sourceAccount, balanceID)`. If the signature was not from one of the public keys that satisfy the conditions, then the operation would fail. This would allow the appropriate party to claim the `ClaimableBalanceEntry` into any account that they control. But this would also make it considerably easier to circumvent authorization restrictions on assets. For instance, an authorized account could create a `ClaimableBalanceEntry` with a recipient public key whose private key is known only to some other party. That party would then control the funds in the `ClaimableBalanceEntry` and could claim them into any account that is authorized. A similar scheme could be executed today by changing the signers on an account, but this would only be possible once per authorized account and cannot separate out a fraction of the funds. In summary, an approach that could allow `ClaimableBalanceEntry` to be claimable into any account would significantly weaken the strength of authorization restrictions. ### Should it be possible to increase the amount of a ClaimableBalanceEntry? One issue which has been discussed during the development of this proposal is the absence of a mechanism to increase the `amount` of a `ClaimableBalanceEntry`. The specific scenario which would warrant this functionality is when a single account sends many identical payments to a single account that is not prepared to receive them and does not claim any of the payments. However, this case is sufficiently specific that we recommend pursuing it in a separate proposal once this proposal has been implemented. Delaying this feature presents minimal additional difficulty because `ClaimableBalanceEntry` has an extension point. This issue has also been slightly mitigated relative to earlier versions of this proposal because `ClaimableBalanceEntry` now returns the reserve to the sending account, whenever possible. ### Memo Everything proposed in this document takes the same stance as existing features of the protocol with regard to memo: memo is a property of a transaction, not of an operation or a ledger entry. ## Backwards Incompatibilities All downstream systems will need updated XDR in order to recognize the new operations and ledger entries. ## Security Concerns This proposal will slightly reduce the efficacy of base reserve changes, because a `ClaimableBalanceEntry` that has insufficient reserve is still usable. ## Test Cases None yet. ## Implementation https://github.com/stellar/stellar-core/pull/2591