Add simplified explicit interfaces to handle XRP and IOU transfers:

The new interfaces take into account the different semantics of XRP, which
do not have an issuer or transfer fees, and IOUs which have issuers and
(optional) transfer fees.

For XRP, the new `LedgerEntrySet::transfer_xrp` will transfer the specified
amount of XRP between from a given source to a given destination.

For IOU, two new functions are introduced:
* `LedgerEntrySet::issue_iou` which transfers the specified amount of an
IOU from the IOU's issuer to an account.
* `LedgerEntrySet::redeem_iou` which transfers the specified amount of an
IOU from an account to the IOU's issuer.

A transfer from user-to-user (e.g. to fill an order during offer crossing)
requires the use of `redeem_iou` followed by `issue_iou`. This helps to
enforce the Ripple invariant that IOUs never flow directly from user to
user, but only through a gateway. Additionally, this  allows for the
explicit calculation and application of transfer fees by varying the
amounts redeemed and issued.

The new interfaces promote type safety since you cannot use the issue
and redeem APIs with XRP or the transfer API with IOU, and the issuer
to be used is implied by the currency being issued or redeemed.
This commit is contained in:
Nik Bougalis
2014-12-15 12:11:46 -08:00
parent d1193093ef
commit 5530353eef
2 changed files with 281 additions and 55 deletions

View File

@@ -1267,33 +1267,6 @@ STAmount LedgerEntrySet::accountFunds (
return saFunds;
}
// Calculate transit fee.
STAmount LedgerEntrySet::rippleTransferFee (
Account const& uSenderID,
Account const& uReceiverID,
Account const& issuer,
STAmount const& saAmount)
{
if (uSenderID != issuer && uReceiverID != issuer)
{
std::uint32_t uTransitRate = rippleTransferRate (*this, issuer);
if (QUALITY_ONE != uTransitRate)
{
STAmount saTransferTotal = multiply (
saAmount, amountFromRate (uTransitRate), saAmount.issue ());
STAmount saTransferFee = saTransferTotal - saAmount;
WriteLog (lsDEBUG, LedgerEntrySet) << "rippleTransferFee:" <<
" saTransferFee=" << saTransferFee.getFullText ();
return saTransferFee;
}
}
return saAmount.zeroed();
}
TER LedgerEntrySet::trustCreate (
const bool bSrcHigh,
Account const& uSrcAccountID,
@@ -1564,42 +1537,71 @@ TER LedgerEntrySet::rippleSend (
Account const& uSenderID, Account const& uReceiverID,
STAmount const& saAmount, STAmount& saActual)
{
auto const issuer = saAmount.getIssuer ();
TER terResult;
auto const& issue = saAmount.issue ();
auto const& issuer = saAmount.issue ().account;
assert (!isXRP (uSenderID) && !isXRP (uReceiverID));
assert (uSenderID != uReceiverID);
if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount())
// Issues and redeems are processed directly without any fees.
if (uSenderID == issue.account)
{
// Direct send: redeeming IOUs and/or sending own IOUs.
terResult = rippleCredit (uSenderID, uReceiverID, saAmount, false);
saActual = saAmount;
terResult = tesSUCCESS;
assert (issue.account != noAccount ());
saActual = saAmount;
return issue_iou (uReceiverID, saAmount, issue);
}
else
if (uReceiverID == issue.account)
{
// Sending 3rd party IOUs: transit.
assert (issue.account != noAccount ());
saActual = saAmount;
return redeem_iou (uSenderID, saAmount, issue);
}
STAmount saTransitFee = rippleTransferFee (
uSenderID, uReceiverID, issuer, saAmount);
saActual = !saTransitFee ? saAmount : saAmount + saTransitFee;
saActual.setIssuer (issuer); // XXX Make sure this done in + above.
WriteLog (lsDEBUG, LedgerEntrySet) << "rippleSend> " <<
to_string (uSenderID) <<
// NIKB TODO: What is going on here? Under what circumstances would we try
// to transfer some currency without a specified issuer?
if (issue.account == noAccount())
{
WriteLog (lsDEBUG, LedgerEntrySet) <<
"rippleSend> " << to_string (uSenderID) <<
" - > " << to_string (uReceiverID) <<
" : deliver=" << saAmount.getFullText () <<
" fee=" << saTransitFee.getFullText () <<
" cost=" << saActual.getFullText ();
" : amount with issuer=1: " << saAmount.getFullText ();
terResult = rippleCredit (issuer, uReceiverID, saAmount);
// Direct send: redeeming IOUs and/or sending own IOUs.
TER result = rippleCredit (uSenderID, uReceiverID, saAmount, false);
saActual = saAmount;
if (tesSUCCESS == terResult)
terResult = rippleCredit (uSenderID, issuer, saActual);
// NIKB CHECKME: Why are we setting terResult to tesSUCCESS here?
result = tesSUCCESS;
return result;
}
assert (uSenderID != issue.account && uReceiverID != issue.account);
// Sending IOUs to a third party: we must calculate the transit fee, by
// applying the transfer rate, if any.
saActual = saAmount;
std::uint32_t const transit_rate = rippleTransferRate (*this, issuer);
if (QUALITY_ONE != transit_rate)
{
saActual = multiply (saActual, STAmount::saFromRate (transit_rate),
saActual.issue ());
}
WriteLog (lsDEBUG, LedgerEntrySet) <<
"rippleSend> " << to_string (uSenderID) <<
" - > " << to_string (uReceiverID) <<
" : deliver=" << saAmount.getFullText () <<
" cost=" << saActual.getFullText () <<
" fee=" << (saActual - saAmount).getFullText ();
TER terResult = issue_iou (uReceiverID, saAmount, saAmount.issue ());
if (tesSUCCESS == terResult)
terResult = redeem_iou (uSenderID, saActual, saActual.issue ());
return terResult;
}
@@ -1704,6 +1706,223 @@ TER LedgerEntrySet::accountSend (
return terResult;
}
bool LedgerEntrySet::checkState (
SLE::pointer state,
bool bIssuerHigh,
Account const& sender,
STAmount const& before,
STAmount const& after)
{
std::uint32_t const flags (state->getFieldU32 (sfFlags));
// YYY Could skip this if rippling in reverse.
if (before > zero
// Sender balance was positive.
&& after <= zero
// Sender is zero or negative.
&& (flags & (!bIssuerHigh ? lsfLowReserve : lsfHighReserve))
// Sender reserve is set.
&& !(flags & (!bIssuerHigh ? lsfLowNoRipple : lsfHighNoRipple))
&& !(flags & (!bIssuerHigh ? lsfLowFreeze : lsfHighFreeze))
&& !state->getFieldAmount (
!bIssuerHigh ? sfLowLimit : sfHighLimit)
// Sender trust limit is 0.
&& !state->getFieldU32 (
!bIssuerHigh ? sfLowQualityIn : sfHighQualityIn)
// Sender quality in is 0.
&& !state->getFieldU32 (
!bIssuerHigh ? sfLowQualityOut : sfHighQualityOut))
// Sender quality out is 0.
{
// Clear the reserve of the sender, possibly delete the line!
decrementOwnerCount (sender);
// Clear reserve flag.
state->setFieldU32 (
sfFlags,
flags & (!bIssuerHigh ? ~lsfLowReserve : ~lsfHighReserve));
// Balance is zero, receiver reserve is clear.
if (!after // Balance is zero.
&& !(flags & (bIssuerHigh ? lsfLowReserve : lsfHighReserve)))
return true;
}
return false;
}
TER LedgerEntrySet::issue_iou (
Account const& account,
STAmount const& amount,
Issue const& issue)
{
assert (!isXRP (account) && !isXRP (issue.account));
// Consistency check
assert (issue == amount.issue ());
// Can't send to self!
assert (issue.account != account);
WriteLog (lsTRACE, LedgerEntrySet) << "issue_iou: " <<
to_string (account) << ": " <<
amount.getFullText ();
bool bSenderHigh = issue.account > account;
uint256 const index = getRippleStateIndex (
issue.account, account, issue.currency);
auto state = entryCache (ltRIPPLE_STATE, index);
if (!state)
{
// NIKB TODO: The limit uses the receiver's account as the issuer and
// this is unnecessarily inefficient as copying which could be avoided
// is now required. Consider available options.
STAmount limit({issue.currency, account});
STAmount final_balance = amount;
final_balance.setIssuer (noAccount());
return trustCreate (bSenderHigh, issue.account, account, index,
entryCache (ltACCOUNT_ROOT, getAccountRootIndex (account)),
false, false, false, final_balance, limit);
}
STAmount final_balance = state->getFieldAmount (sfBalance);
if (bSenderHigh)
final_balance.negate (); // Put balance in sender terms.
STAmount const start_balance = final_balance;
final_balance -= amount;
auto const must_delete = checkState (state, bSenderHigh, issue.account,
start_balance, final_balance);
if (bSenderHigh)
final_balance.negate ();
// Adjust the balance on the trust line if necessary. We do this even if we
// are going to delete the line to reflect the correct balance at the time
// of deletion.
state->setFieldAmount (sfBalance, final_balance);
if (must_delete)
{
return trustDelete (state,
bSenderHigh ? account : issue.account,
bSenderHigh ? issue.account : account);
}
entryModify (state);
return tesSUCCESS;
}
TER LedgerEntrySet::redeem_iou (
Account const& account,
STAmount const& amount,
Issue const& issue)
{
assert (!isXRP (account) && !isXRP (issue.account));
// Consistency check
assert (issue == amount.issue ());
// Can't send to self!
assert (issue.account != account);
WriteLog (lsTRACE, LedgerEntrySet) << "redeem_iou: " <<
to_string (account) << ": " <<
amount.getFullText ();
bool bSenderHigh = account > issue.account;
uint256 const index = getRippleStateIndex (
account, issue.account, issue.currency);
auto state = entryCache (ltRIPPLE_STATE, index);
if (!state)
{
STAmount limit(issue);
STAmount final_balance = amount;
final_balance.setIssuer (noAccount());
return trustCreate (bSenderHigh, account, issue.account, index,
entryCache (ltACCOUNT_ROOT, getAccountRootIndex (issue.account)),
false, false, false, final_balance, limit);
}
STAmount final_balance = state->getFieldAmount (sfBalance);
if (bSenderHigh)
final_balance.negate (); // Put balance in sender terms.
STAmount const start_balance = final_balance;
final_balance -= amount;
auto const must_delete = checkState (state, bSenderHigh, account,
start_balance, final_balance);
if (bSenderHigh)
final_balance.negate ();
// Adjust the balance on the trust line if necessary. We do this even if we
// are going to delete the line to reflect the correct balance at the time
// of deletion.
state->setFieldAmount (sfBalance, final_balance);
if (must_delete)
{
return trustDelete (state,
bSenderHigh ? issue.account : account,
bSenderHigh ? account : issue.account);
}
entryModify (state);
return tesSUCCESS;
}
TER LedgerEntrySet::transfer_xrp (
Account const& from,
Account const& to,
STAmount const& amount)
{
assert (from != beast::zero);
assert (to != beast::zero);
assert (from != to);
assert (amount.isNative ());
SLE::pointer sender = entryCache (ltACCOUNT_ROOT,
getAccountRootIndex (from));
SLE::pointer receiver = entryCache (ltACCOUNT_ROOT,
getAccountRootIndex (to));
WriteLog (lsTRACE, LedgerEntrySet) << "transfer_xrp: " <<
to_string (from) << " -> " << to_string (to) <<
") : " << amount.getFullText ();
if (sender->getFieldAmount (sfBalance) < amount)
{
// FIXME: this logic should be moved to callers maybe?
return (mParams & tapOPEN_LEDGER)
? telFAILED_PROCESSING
: tecFAILED_PROCESSING;
}
// Decrement XRP balance.
sender->setFieldAmount (sfBalance,
sender->getFieldAmount (sfBalance) - amount);
entryModify (sender);
receiver->setFieldAmount (sfBalance,
receiver->getFieldAmount (sfBalance) + amount);
entryModify (receiver);
return tesSUCCESS;
}
std::uint32_t
rippleTransferRate (LedgerEntrySet& ledger, Account const& issuer)
{

View File

@@ -213,10 +213,6 @@ public:
bool isGlobalFrozen (Account const& issuer);
STAmount rippleTransferFee (
Account const& uSenderID, Account const& uReceiverID,
Account const& issuer, const STAmount & saAmount);
TER rippleCredit (
Account const& uSenderID, Account const& uReceiverID,
const STAmount & saAmount, bool bCheckIssuer = true);
@@ -288,6 +284,14 @@ public:
mSet.setDeliveredAmount (amt);
}
TER issue_iou (Account const& account,
STAmount const& amount, Issue const& issue);
TER redeem_iou (Account const& account,
STAmount const& amount, Issue const& issue);
TER transfer_xrp (Account const& from, Account const& to, STAmount const& amount);
private:
Ledger::pointer mLedger;
std::map<uint256, LedgerEntrySetEntry> mEntries; // cannot be unordered!
@@ -327,6 +331,9 @@ private:
STAmount rippleHolds (
Account const& account, Currency const& currency,
Account const& issuer, FreezeHandling zeroIfFrozen);
bool checkState (SLE::pointer state, bool bIssuerHigh,
Account const& sender, STAmount const& before, STAmount const& after);
};
// NIKB FIXME: move these to the right place