Skip to content

Latest commit

 

History

History
290 lines (258 loc) · 12.3 KB

cap-0024.md

File metadata and controls

290 lines (258 loc) · 12.3 KB

Preamble

CAP: 0024
Title:  Make PathPayment Symmetrical
Author: Jed McCaleb, Jonathan Jove
Status: Final
Created: 2019-07-08
Discussion: https://github.com/stellar/stellar-protocol/issues/49
Protocol version: 12

Simple Summary

Right now PathPaymentOp lets you specify how much the recipient will receive while the amount sent can vary. There are use cases where you really want to specify the amount send while the amount received can vary.

Motivation

The motivation to add PathPaymentStrictSendOp is analogous to that for adding ManageBuyOfferOp: many financial institutions have an obligation to faithfully carry out customer orders. As a concrete example, suppose that an individual whose native currency is X is selling a house. A potential buyer whose native currency is Y approaches the seller. They agree to a contract where the buyer must deposit a certain amount of currency X with an escrow agent chosen by the seller. The escrowed amount will contribute to the purchase price if the transaction is completed, will be returned to the buyer (in currency Y) in the event that seller defaults, and will be credited to the seller as damages in the event that buyer defaults. The buyer can easily use PathPaymentOp to deposit the agreed amount of currency X with the escrow agent. If the escrow agent needs to transfer the escrowed funds to the seller, this can be done with PaymentOp. But if the escrow agent needs to transfer the escrowed funds to the buyer (in currency Y) then PathPaymentOp will not be effective because the escrow agent would be required to guess the destAmount in order to send the entire escrowed amount. In general, there is no value for the destAmount that guarantees that the entire maxSend will be sent, so the escrow agent will not be able to meet his obligations.

Goals Alignment

This proposal is aligned with the following Stellar Network Goal: 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.

Abstract

This proposal introduces a new operation PathPaymentStrictSendOp that allows a form of path payment with a strict equality constraint on the send amount and an inequality constraint on the destination amount. The existing PathPayment operation is renamed to PathPaymentStrictReceiveOp in order to reflect that it allows a form of path payment with a strict equality constraint on the destination amount and an inequality constraint on the send amount.

Specification

XDR

We first introduce the XDR for operations PathPaymentStrictReceiveOp and PathPaymentStrictSendOp, as well as the corresponding changes to OperationType and Operation.

enum OperationType
{
    CREATE_ACCOUNT = 0,
    PAYMENT = 1,
    PATH_PAYMENT_STRICT_RECEIVE = 2,
    MANAGE_SELL_OFFER = 3,
    CREATE_PASSIVE_SELL_OFFER = 4,
    SET_OPTIONS = 5,
    CHANGE_TRUST = 6,
    ALLOW_TRUST = 7,
    ACCOUNT_MERGE = 8,
    INFLATION = 9,
    MANAGE_DATA = 10,
    BUMP_SEQUENCE = 11,
    MANAGE_BUY_OFFER = 12,
    PATH_PAYMENT_STRICT_SEND = 13
};

struct PathPaymentStrictReceiveOp
{
    Asset sendAsset; // asset we pay with
    int64 sendMax;   // the maximum amount of sendAsset to
                     // send (excluding fees).
                     // The operation will fail if can't be met

    AccountID destination; // recipient of the payment
    Asset destAsset;       // what they end up with
    int64 destAmount;      // amount they end up with

    Asset path<5>; // additional hops it must go through to get there
};

struct PathPaymentStrictSendOp
{
    Asset sendAsset;  // asset we pay with
    int64 sendAmount; // amount of sendAsset to send (excluding fees)

    AccountID destination; // recipient of the payment
    Asset destAsset;       // what they end up with
    int64 destMin;         // the minimum amount of dest asset to
                           // be received
                           // The operation will fail if it can't be met

    Asset path<5>; // additional hops it must go through to get there
};

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, PAYMENT unchanged ...
    case PATH_PAYMENT_STRICT_RECEIVE:
        PathPaymentStrictReceiveOp pathPaymentStrictReceiveOp;
    // ... MANAGE_SELL_OFFER, ..., MANAGE_BUY_OFFER unchanged ...
    case PATH_PAYMENT_STRICT_SEND:
        PathPaymentStrictSendOp pathPaymentStrictSendOp;
    }
    body;
};

We next introduce the result types PathPaymentStrictReceiveResult and PathPaymentStrictSendResult, as well as the corresponding changes to OperationResult.

enum PathPaymentStrictReceiveResultCode
{
    // codes considered as "success" for the operation
    PATH_PAYMENT_STRICT_RECEIVE_SUCCESS = 0, // success

    // codes considered as "failure" for the operation
    PATH_PAYMENT_STRICT_RECEIVE_MALFORMED = -1,          // bad input
    PATH_PAYMENT_STRICT_RECEIVE_UNDERFUNDED = -2,        // not enough funds in source account
    PATH_PAYMENT_STRICT_RECEIVE_SRC_NO_TRUST = -3,       // no trust line on source account
    PATH_PAYMENT_STRICT_RECEIVE_SRC_NOT_AUTHORIZED = -4, // source not authorized to transfer
    PATH_PAYMENT_STRICT_RECEIVE_NO_DESTINATION = -5,     // destination account does not exist
    PATH_PAYMENT_STRICT_RECEIVE_NO_TRUST = -6,           // dest missing a trust line for asset
    PATH_PAYMENT_STRICT_RECEIVE_NOT_AUTHORIZED = -7,     // dest not authorized to hold asset
    PATH_PAYMENT_STRICT_RECEIVE_LINE_FULL = -8,          // dest would go above their limit
    PATH_PAYMENT_STRICT_RECEIVE_NO_ISSUER = -9,          // missing issuer on one asset
    PATH_PAYMENT_STRICT_RECEIVE_TOO_FEW_OFFERS = -10,    // not enough offers to satisfy path
    PATH_PAYMENT_STRICT_RECEIVE_OFFER_CROSS_SELF = -11,  // would cross one of its own offers
    PATH_PAYMENT_STRICT_RECEIVE_OVER_SENDMAX = -12       // could not satisfy sendmax
};

union PathPaymentStrictReceiveResult switch (PathPaymentStrictReceiveResultCode code)
{
case PATH_PAYMENT_STRICT_RECEIVE_SUCCESS:
    struct
    {
        ClaimOfferAtom offers<>;
        SimplePaymentResult last;
    } success;
case PATH_PAYMENT_STRICT_RECEIVE_NO_ISSUER:
    Asset noIssuer; // the asset that caused the error
default:
    void;
};

enum PathPaymentStrictSendResultCode
{
    // codes considered as "success" for the operation
    PATH_PAYMENT_STRICT_SEND_SUCCESS = 0, // success

    // codes considered as "failure" for the operation
    PATH_PAYMENT_STRICT_SEND_MALFORMED = -1,          // bad input
    PATH_PAYMENT_STRICT_SEND_UNDERFUNDED = -2,        // not enough funds in source account
    PATH_PAYMENT_STRICT_SEND_SRC_NO_TRUST = -3,       // no trust line on source account
    PATH_PAYMENT_STRICT_SEND_SRC_NOT_AUTHORIZED = -4, // source not authorized to transfer
    PATH_PAYMENT_STRICT_SEND_NO_DESTINATION = -5,     // destination account does not exist
    PATH_PAYMENT_STRICT_SEND_NO_TRUST = -6,           // dest missing a trust line for asset
    PATH_PAYMENT_STRICT_SEND_NOT_AUTHORIZED = -7,     // dest not authorized to hold asset
    PATH_PAYMENT_STRICT_SEND_LINE_FULL = -8,          // dest would go above their limit
    PATH_PAYMENT_STRICT_SEND_NO_ISSUER = -9,          // missing issuer on one asset
    PATH_PAYMENT_STRICT_SEND_TOO_FEW_OFFERS = -10,    // not enough offers to satisfy path
    PATH_PAYMENT_STRICT_SEND_OFFER_CROSS_SELF = -11,  // would cross one of its own offers
    PATH_PAYMENT_STRICT_SEND_UNDER_DESTMIN = -12      // could not satisfy destMin
};

union PathPaymentStrictSendResult switch (PathPaymentStrictSendResultCode code)
{
case PATH_PAYMENT_STRICT_SEND_SUCCESS:
    struct
    {
        ClaimOfferAtom offers<>;
        SimplePaymentResult last;
    } success;
case PATH_PAYMENT_STRICT_SEND_NO_ISSUER:
    Asset noIssuer; // the asset that caused the error
default:
    void;
};

union OperationResult switch (OperationResultCode code)
{
case opINNER:
    union switch (OperationType type)
    {
    // ... CREATE_ACOUNT, PAYMENT unchanged ...
    case PATH_PAYMENT_STRICT_RECEIVE:
        PathPaymentStrictReceiveResult pathPaymentStrictReceiveResult;
    // ... MANAGE_SELL_OFFER, ..., MANAGE_BUY_OFFER unchanged ...
    case PATH_PAYMENT_STRICT_SEND:
        PathPaymentStrictSendResult pathPaymentStrictSendResult;
    }
    tr;
default:
    void;
};

Semantics

PathPaymentStrictSendOp returns

  • PATH_PAYMENT_STRICT_SEND_NO_DESTINATION if issuer checks are not bypassed and destAccount does not exist
  • PATH_PAYMENT_STRICT_SEND_NO_ISSUER if issuer checks are not bypassed and the issuer of sendAsset does not exist
  • PATH_PAYMENT_STRICT_SEND_SRC_NO_TRUST if sendAsset is not native and sourceAccount does not own a trust line for sendAsset
  • PATH_PAYMENT_STRICT_SEND_SRC_NOT_AUTHORIZED if sendAsset is not native and the trust line for sendAsset owned by sourceAccount is not authorized
  • PATH_PAYMENT_STRICT_SEND_UNDERFUNDED if sourceAccount does not have sufficient available balance of sendAsset after accounting for selling liabilities and base reserve (if sendAsset is native)
  • For each (S, R) in (sendAsset, path[0]), (path[0], path[1]), ..., (path[n-1], path[n]), (path[n], destAsset) with S != R
    • PATH_PAYMENT_STRICT_SEND_NO_ISSUER if R is not native and the issuer of R does not exist
    • PATH_PAYMENT_STRICT_SEND_OFFER_CROSS_SELF if an offer owned by sourceAccount was encountered between S and R before sending the required amount of S
    • PATH_PAYMENT_STRICT_SEND_TOO_FEW_OFFERS if less than the required amount of S was sent before exhausting all offers between S and R
  • PATH_PAYMENT_STRICT_SEND_UNDER_DESTMIN if the amount sent to the destination does not exceed destMin
  • PATH_PAYMENT_STRICT_SEND_NO_ISSUER if issuer checks are not bypassed and the issuer of destAsset does not exist
  • PATH_PAYMENT_STRICT_SEND_NO_TRUST if destAsset is not native and destAccount does not own a trust line for destAsset
  • PATH_PAYMENT_STRICT_SEND_NOT_AUTHORIZED if destAsset is not native and the trust line for destAsset owned by destAccount is not authorized
  • PATH_PAYMENT_STRICT_SEND_LINE_FULL if destAccount does not have sufficient available limit of destAsset after accounting for buying liabilities
  • PATH_PAYMENT_STRICT_SEND_SUCCESS otherwise

If PathPaymentStrictSendOp succeeds then the source account is debited exactly sendAmount of sendAsset and the destAccount is credited at least destMin of destAsset. In order to achieve this goal, it was required to introduce a new rounding mode (see CAP-0004 for context). We provide an informal description of what occurs and why; for a detailed proof please refer to the implementation. This rounding mode may only differ from the typical rounding behavior if the offer is larger than the remnants of the conversion (this can only occur for the last offer crossed in each step of the path). In this case, the remaining quantity of S will be converted into the maximum amount of R that is possible (given limits on the conversion and the offer) such that the price remains favorable to the owner of the offer. This is the correct behavior because

  • The entire amount available to be sent is in fact sent if it is possible to do so
  • The received amount is the maximum possible while continuing to meet the requirement that it must favor the owner of the offer

Design Rationale

Every design decision concerning PathPaymentStrictSendOp was decided to make the operation "dual" to PathPaymentStrictReceiveOp in as many ways as possible. With that context in mind, the central design decision for PathPaymentStrictSendOp was enforcing the requirement that it must send exactly the specified sendAmount. This decision has the following advantages, analogous to the advantages of having PathPaymentStrictReceiveOp receive exactly the specified destAmount

  • The semantics of the operation are simple to explain
  • The state of the sourceAccount and destAccount is predictable after the operation executes

Backwards Incompatibilities

All downstream systems will need updated XDR in order to recognize the new operation.

Security Concerns

None yet.

Test Cases

None yet.

Implementation

None yet.