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
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.
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.
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.
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.
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;
};PathPaymentStrictSendOp returns
PATH_PAYMENT_STRICT_SEND_NO_DESTINATIONif issuer checks are not bypassed anddestAccountdoes not existPATH_PAYMENT_STRICT_SEND_NO_ISSUERif issuer checks are not bypassed and the issuer ofsendAssetdoes not existPATH_PAYMENT_STRICT_SEND_SRC_NO_TRUSTifsendAssetis not native andsourceAccountdoes not own a trust line forsendAssetPATH_PAYMENT_STRICT_SEND_SRC_NOT_AUTHORIZEDifsendAssetis not native and the trust line forsendAssetowned bysourceAccountis not authorizedPATH_PAYMENT_STRICT_SEND_UNDERFUNDEDifsourceAccountdoes not have sufficient available balance ofsendAssetafter accounting for selling liabilities and base reserve (ifsendAssetis native)- For each
(S, R)in(sendAsset, path[0]), (path[0], path[1]), ..., (path[n-1], path[n]), (path[n], destAsset)withS != RPATH_PAYMENT_STRICT_SEND_NO_ISSUERifRis not native and the issuer ofRdoes not existPATH_PAYMENT_STRICT_SEND_OFFER_CROSS_SELFif an offer owned bysourceAccountwas encountered betweenSandRbefore sending the required amount ofSPATH_PAYMENT_STRICT_SEND_TOO_FEW_OFFERSif less than the required amount ofSwas sent before exhausting all offers betweenSandR
PATH_PAYMENT_STRICT_SEND_UNDER_DESTMINif the amount sent to the destination does not exceeddestMinPATH_PAYMENT_STRICT_SEND_NO_ISSUERif issuer checks are not bypassed and the issuer ofdestAssetdoes not existPATH_PAYMENT_STRICT_SEND_NO_TRUSTifdestAssetis not native anddestAccountdoes not own a trust line fordestAssetPATH_PAYMENT_STRICT_SEND_NOT_AUTHORIZEDifdestAssetis not native and the trust line fordestAssetowned bydestAccountis not authorizedPATH_PAYMENT_STRICT_SEND_LINE_FULLifdestAccountdoes not have sufficient available limit ofdestAssetafter accounting for buying liabilitiesPATH_PAYMENT_STRICT_SEND_SUCCESSotherwise
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
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
sourceAccountanddestAccountis predictable after the operation executes
All downstream systems will need updated XDR in order to recognize the new operation.
None yet.
None yet.
None yet.