mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-18 17:45:48 +00:00
merged IOUEscrow amendment
This commit is contained in:
@@ -33,6 +33,8 @@
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <ripple/protocol/digest.h>
|
||||
#include <ripple/protocol/st.h>
|
||||
#include <ripple/protocol/Rate.h>
|
||||
|
||||
|
||||
// During an EscrowFinish, the transaction must specify both
|
||||
// a condition and a fulfillment. We track whether that
|
||||
@@ -93,7 +95,8 @@ after(NetClock::time_point now, std::uint32_t mark)
|
||||
TxConsequences
|
||||
EscrowCreate::makeTxConsequences(PreflightContext const& ctx)
|
||||
{
|
||||
return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
|
||||
return TxConsequences{ctx.tx,
|
||||
isXRP(ctx.tx[sfAmount]) ? ctx.tx[sfAmount].xrp() : beast::zero};
|
||||
}
|
||||
|
||||
NotTEC
|
||||
@@ -105,8 +108,25 @@ EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
if (!isXRP(ctx.tx[sfAmount]))
|
||||
return temBAD_AMOUNT;
|
||||
STAmount const amount {ctx.tx[sfAmount]};
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (!ctx.rules.enabled(featurePaychanAndEscrowForTokens))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (!isLegalNet(amount))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (isFakeXRP(amount))
|
||||
return temBAD_CURRENCY;
|
||||
|
||||
if (ctx.tx[sfAccount] == amount.getIssuer())
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "Malformed transaction: Cannot escrow own tokens to self.";
|
||||
return temDST_IS_SRC;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.tx[sfAmount] <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
@@ -199,17 +219,66 @@ EscrowCreate::doApply()
|
||||
if (!sle)
|
||||
return tefINTERNAL;
|
||||
|
||||
STAmount const amount {ctx_.tx[sfAmount]};
|
||||
|
||||
std::shared_ptr<SLE> sleLine;
|
||||
|
||||
auto const balance = STAmount((*sle)[sfBalance]).xrp();
|
||||
auto const reserve =
|
||||
ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
// Check reserve and funds availability
|
||||
if (isXRP(amount) && balance < reserve + STAmount(ctx_.tx[sfAmount]).xrp())
|
||||
return tecUNFUNDED;
|
||||
else
|
||||
{
|
||||
auto const balance = STAmount((*sle)[sfBalance]).xrp();
|
||||
auto const reserve =
|
||||
ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1);
|
||||
// preflight will prevent this ever firing, included
|
||||
// defensively for completeness
|
||||
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tefINTERNAL;
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
// check if the escrow is capable of being
|
||||
// finished before we allow it to be created
|
||||
{
|
||||
TER result =
|
||||
trustTransferAllowed(
|
||||
ctx_.view(),
|
||||
{account, ctx_.tx[sfDestination]},
|
||||
amount.issue(),
|
||||
ctx_.journal);
|
||||
|
||||
if (balance < reserve + STAmount(ctx_.tx[sfAmount]).xrp())
|
||||
return tecUNFUNDED;
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "EscrowCreate::doApply trustTransferAllowed result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
// perform the lock as a dry run before
|
||||
// we modify anything on-ledger
|
||||
sleLine = ctx_.view().peek(keylet::line(account, amount.getIssuer(), amount.getCurrency()));
|
||||
|
||||
{
|
||||
TER result =
|
||||
trustAdjustLockedBalance(
|
||||
ctx_.view(),
|
||||
sleLine,
|
||||
amount,
|
||||
1,
|
||||
ctx_.journal,
|
||||
DryRun);
|
||||
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "EscrowCreate::doApply trustAdjustLockedBalance (dry) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Check destination account
|
||||
@@ -274,7 +343,31 @@ EscrowCreate::doApply()
|
||||
}
|
||||
|
||||
// Deduct owner's balance, increment owner count
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
||||
if (isXRP(amount))
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
||||
else
|
||||
{
|
||||
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens) || !sleLine)
|
||||
return tefINTERNAL;
|
||||
|
||||
// do the lock-up for real now
|
||||
TER result =
|
||||
trustAdjustLockedBalance(
|
||||
ctx_.view(),
|
||||
sleLine,
|
||||
amount,
|
||||
1,
|
||||
ctx_.journal,
|
||||
WetRun);
|
||||
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "EscrowCreate::doApply trustAdjustLockedBalance (wet) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
@@ -384,6 +477,10 @@ EscrowFinish::doApply()
|
||||
if (!slep)
|
||||
return tecNO_TARGET;
|
||||
|
||||
AccountID const account = (*slep)[sfAccount];
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
auto amount = slep->getFieldAmount(sfAmount);
|
||||
|
||||
// If a cancel time is present, a finish operation should only succeed prior
|
||||
// to that time. fix1571 corrects a logic error in the check that would make
|
||||
// a finish only succeed strictly after the cancel time.
|
||||
@@ -484,7 +581,33 @@ EscrowFinish::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
AccountID const account = (*slep)[sfAccount];
|
||||
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tefINTERNAL;
|
||||
|
||||
// perform a dry run of the transfer before we
|
||||
// change anything on-ledger
|
||||
TER result =
|
||||
trustTransferLockedBalance(
|
||||
ctx_.view(),
|
||||
account_, // txn signing account
|
||||
sle, // src account
|
||||
sled, // dst account
|
||||
amount, // xfer amount
|
||||
-1,
|
||||
j_,
|
||||
DryRun // dry run
|
||||
);
|
||||
|
||||
JLOG(j_.trace())
|
||||
<< "EscrowFinish::doApply trustTransferLockedBalance (dry) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
// Remove escrow from owner directory
|
||||
{
|
||||
@@ -508,12 +631,38 @@ EscrowFinish::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
// Transfer amount to destination
|
||||
(*sled)[sfBalance] = (*sled)[sfBalance] + (*slep)[sfAmount];
|
||||
|
||||
|
||||
if (isXRP(amount))
|
||||
(*sled)[sfBalance] = (*sled)[sfBalance] + (*slep)[sfAmount];
|
||||
else
|
||||
{
|
||||
// all the significant complexity of checking the validity of this
|
||||
// transfer and ensuring the lines exist etc is hidden away in this
|
||||
// function, all we need to do is call it and return if unsuccessful.
|
||||
TER result =
|
||||
trustTransferLockedBalance(
|
||||
ctx_.view(),
|
||||
account_, // txn signing account
|
||||
sle, // src account
|
||||
sled, // dst account
|
||||
amount, // xfer amount
|
||||
-1,
|
||||
j_,
|
||||
WetRun // wet run;
|
||||
);
|
||||
|
||||
JLOG(j_.trace())
|
||||
<< "EscrowFinish::doApply trustTransferLockedBalance (wet) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
ctx_.view().update(sled);
|
||||
|
||||
// Adjust source owner count
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
@@ -581,6 +730,32 @@ EscrowCancel::doApply()
|
||||
}
|
||||
|
||||
AccountID const account = (*slep)[sfAccount];
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
auto amount = slep->getFieldAmount(sfAmount);
|
||||
|
||||
std::shared_ptr<SLE> sleLine;
|
||||
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tefINTERNAL;
|
||||
|
||||
sleLine =
|
||||
ctx_.view().peek(
|
||||
keylet::line(account, amount.getIssuer(), amount.getCurrency()));
|
||||
|
||||
// dry run before we make any changes to ledger
|
||||
if (TER result =
|
||||
trustAdjustLockedBalance(
|
||||
ctx_.view(),
|
||||
sleLine,
|
||||
-amount,
|
||||
-1,
|
||||
ctx_.journal,
|
||||
DryRun);
|
||||
result != tesSUCCESS)
|
||||
return result;
|
||||
}
|
||||
|
||||
// Remove escrow from owner directory
|
||||
{
|
||||
@@ -607,9 +782,33 @@ EscrowCancel::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
// Transfer amount back to owner, decrement owner count
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount];
|
||||
// Transfer amount back to the owner (or unlock it in TL case)
|
||||
if (isXRP(amount))
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount];
|
||||
else
|
||||
{
|
||||
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tefINTERNAL;
|
||||
|
||||
// unlock previously locked tokens from source line
|
||||
TER result =
|
||||
trustAdjustLockedBalance(
|
||||
ctx_.view(),
|
||||
sleLine,
|
||||
-amount,
|
||||
-1,
|
||||
ctx_.journal,
|
||||
WetRun);
|
||||
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "EscrowCancel::doApply trustAdjustLockedBalance (wet) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
// Decrement owner count
|
||||
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
|
||||
@@ -99,11 +99,14 @@ XRPNotCreated::visitEntry(
|
||||
drops_ -= (*before)[sfBalance].xrp().drops();
|
||||
break;
|
||||
case ltPAYCHAN:
|
||||
drops_ -=
|
||||
((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
|
||||
if (isXRP((*before)[sfAmount]))
|
||||
drops_ -=
|
||||
((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
|
||||
break;
|
||||
case ltESCROW:
|
||||
drops_ -= (*before)[sfAmount].xrp().drops();
|
||||
if (isXRP((*before)[sfAmount]))
|
||||
drops_ -=
|
||||
(*before)[sfAmount].xrp().drops();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -118,14 +121,14 @@ XRPNotCreated::visitEntry(
|
||||
drops_ += (*after)[sfBalance].xrp().drops();
|
||||
break;
|
||||
case ltPAYCHAN:
|
||||
if (!isDelete)
|
||||
drops_ += ((*after)[sfAmount] - (*after)[sfBalance])
|
||||
.xrp()
|
||||
.drops();
|
||||
if (!isDelete && isXRP((*after)[sfAmount]))
|
||||
drops_ +=
|
||||
((*after)[sfAmount] - (*after)[sfBalance]).xrp().drops();
|
||||
break;
|
||||
case ltESCROW:
|
||||
if (!isDelete)
|
||||
drops_ += (*after)[sfAmount].xrp().drops();
|
||||
if (!isDelete && isXRP((*after)[sfAmount]))
|
||||
drops_ +=
|
||||
(*after)[sfAmount].xrp().drops();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -285,12 +288,25 @@ NoZeroEscrow::visitEntry(
|
||||
|
||||
bool
|
||||
NoZeroEscrow::finalize(
|
||||
STTx const&,
|
||||
STTx const& txn,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const&,
|
||||
ReadView const& rv,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
// bypass this invariant check for IOU escrows
|
||||
if (bad_ &&
|
||||
rv.rules().enabled(featurePaychanAndEscrowForTokens) &&
|
||||
txn.isFieldPresent(sfTransactionType))
|
||||
{
|
||||
uint16_t tt = txn.getFieldU16(sfTransactionType);
|
||||
if (tt == ttESCROW_CANCEL || tt == ttESCROW_FINISH)
|
||||
return true;
|
||||
|
||||
if (txn.isFieldPresent(sfAmount) && !isXRP(txn.getFieldAmount(sfAmount)))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bad_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: escrow specifies invalid amount";
|
||||
|
||||
@@ -120,6 +120,36 @@ closeChannel(
|
||||
beast::Journal j)
|
||||
{
|
||||
AccountID const src = (*slep)[sfAccount];
|
||||
auto const amount = (*slep)[sfAmount] - (*slep)[sfBalance];
|
||||
|
||||
std::shared_ptr<SLE> sleLine;
|
||||
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (!view.rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tefINTERNAL;
|
||||
|
||||
sleLine =
|
||||
view.peek(keylet::line(src, amount.getIssuer(), amount.getCurrency()));
|
||||
|
||||
// dry run
|
||||
TER result =
|
||||
trustAdjustLockedBalance(
|
||||
view,
|
||||
sleLine,
|
||||
-amount,
|
||||
-1,
|
||||
j,
|
||||
DryRun);
|
||||
|
||||
JLOG(j.trace())
|
||||
<< "closeChannel: trustAdjustLockedBalance(dry) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
// Remove PayChan from owner directory
|
||||
{
|
||||
auto const page = (*slep)[sfOwnerNode];
|
||||
@@ -150,8 +180,28 @@ closeChannel(
|
||||
return tefINTERNAL;
|
||||
|
||||
assert((*slep)[sfAmount] >= (*slep)[sfBalance]);
|
||||
(*sle)[sfBalance] =
|
||||
(*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
|
||||
|
||||
if (isXRP(amount))
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] + amount;
|
||||
else
|
||||
{
|
||||
TER result =
|
||||
trustAdjustLockedBalance(
|
||||
view,
|
||||
sleLine,
|
||||
-amount,
|
||||
-1,
|
||||
j,
|
||||
WetRun);
|
||||
|
||||
JLOG(j.trace())
|
||||
<< "closeChannel: trustAdjustLockedBalance(wet) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
adjustOwnerCount(view, sle, -1, j);
|
||||
view.update(sle);
|
||||
|
||||
@@ -165,7 +215,8 @@ closeChannel(
|
||||
TxConsequences
|
||||
PayChanCreate::makeTxConsequences(PreflightContext const& ctx)
|
||||
{
|
||||
return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
|
||||
return TxConsequences{ctx.tx,
|
||||
isXRP(ctx.tx[sfAmount]) ? ctx.tx[sfAmount].xrp() : beast::zero};
|
||||
}
|
||||
|
||||
NotTEC
|
||||
@@ -177,7 +228,27 @@ PayChanCreate::preflight(PreflightContext const& ctx)
|
||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
if (!isXRP(ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::zero))
|
||||
STAmount const amount {ctx.tx[sfAmount]};
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (!ctx.rules.enabled(featurePaychanAndEscrowForTokens))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (!isLegalNet(amount))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (isFakeXRP(amount))
|
||||
return temBAD_CURRENCY;
|
||||
|
||||
if (ctx.tx[sfAccount] == amount.getIssuer())
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "Malformed transaction: Cannot paychan own tokens to self.";
|
||||
return temDST_IS_SRC;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.tx[sfAmount] <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
|
||||
@@ -197,21 +268,65 @@ PayChanCreate::preclaim(PreclaimContext const& ctx)
|
||||
if (!sle)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
// Check reserve and funds availability
|
||||
{
|
||||
auto const balance = (*sle)[sfBalance];
|
||||
auto const reserve =
|
||||
ctx.view.fees().accountReserve((*sle)[sfOwnerCount] + 1);
|
||||
STAmount const amount {ctx.tx[sfAmount]};
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
auto const balance = (*sle)[sfBalance];
|
||||
auto const reserve =
|
||||
ctx.view.fees().accountReserve((*sle)[sfOwnerCount] + 1);
|
||||
|
||||
if (balance < reserve + ctx.tx[sfAmount])
|
||||
return tecUNFUNDED;
|
||||
}
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
auto const dst = ctx.tx[sfDestination];
|
||||
|
||||
// Check reserve and funds availability
|
||||
if (isXRP(amount) && balance < reserve + ctx.tx[sfAmount])
|
||||
return tecUNFUNDED;
|
||||
else
|
||||
{
|
||||
if (!ctx.view.rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tecINTERNAL;
|
||||
|
||||
// check for any possible bars to a channel existing
|
||||
// between these accounts for this asset
|
||||
{
|
||||
TER result =
|
||||
trustTransferAllowed(
|
||||
ctx.view,
|
||||
{account, dst},
|
||||
amount.issue(),
|
||||
ctx.j);
|
||||
JLOG(ctx.j.trace())
|
||||
<< "PayChanCreate::preclaim trustTransferAllowed result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
// check if the amount can be locked
|
||||
{
|
||||
auto sleLine =
|
||||
ctx.view.read(
|
||||
keylet::line(account, amount.getIssuer(), amount.getCurrency()));
|
||||
TER result =
|
||||
trustAdjustLockedBalance(
|
||||
ctx.view,
|
||||
sleLine,
|
||||
amount,
|
||||
1,
|
||||
ctx.j,
|
||||
DryRun);
|
||||
|
||||
JLOG(ctx.j.trace())
|
||||
<< "PayChanCreate::preclaim trustAdjustLockedBalance(dry) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Check destination account
|
||||
auto const sled = ctx.view.read(keylet::account(dst));
|
||||
@@ -241,6 +356,8 @@ PayChanCreate::doApply()
|
||||
|
||||
auto const dst = ctx_.tx[sfDestination];
|
||||
|
||||
STAmount const amount {ctx_.tx[sfAmount]};
|
||||
|
||||
// Create PayChan in ledger.
|
||||
//
|
||||
// Note that we we use the value from the sequence or ticket as the
|
||||
@@ -293,7 +410,36 @@ PayChanCreate::doApply()
|
||||
}
|
||||
|
||||
// Deduct owner's balance, increment owner count
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
||||
if (isXRP(amount))
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - amount;
|
||||
else
|
||||
{
|
||||
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tefINTERNAL;
|
||||
|
||||
auto sleLine =
|
||||
ctx_.view().peek(keylet::line(account, amount.getIssuer(), amount.getCurrency()));
|
||||
|
||||
if (!sleLine)
|
||||
return tecUNFUNDED_PAYMENT;
|
||||
|
||||
TER result =
|
||||
trustAdjustLockedBalance(
|
||||
ctx_.view(),
|
||||
sleLine,
|
||||
amount,
|
||||
1,
|
||||
ctx_.journal,
|
||||
WetRun);
|
||||
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "PayChanCreate::doApply trustAdjustLockedBalance(wet) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return tefINTERNAL;
|
||||
}
|
||||
|
||||
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
@@ -305,7 +451,8 @@ PayChanCreate::doApply()
|
||||
TxConsequences
|
||||
PayChanFund::makeTxConsequences(PreflightContext const& ctx)
|
||||
{
|
||||
return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
|
||||
return TxConsequences{ctx.tx,
|
||||
isXRP(ctx.tx[sfAmount]) ? ctx.tx[sfAmount].xrp() : beast::zero};
|
||||
}
|
||||
|
||||
NotTEC
|
||||
@@ -317,7 +464,27 @@ PayChanFund::preflight(PreflightContext const& ctx)
|
||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
if (!isXRP(ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::zero))
|
||||
STAmount const amount {ctx.tx[sfAmount]};
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (!ctx.rules.enabled(featurePaychanAndEscrowForTokens))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (!isLegalNet(amount))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (isFakeXRP(amount))
|
||||
return temBAD_CURRENCY;
|
||||
|
||||
if (ctx.tx[sfAccount] == amount.getIssuer())
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "Malformed transaction: Cannot escrow own tokens to self.";
|
||||
return temDST_IS_SRC;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.tx[sfAmount] <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
return preflight2(ctx);
|
||||
@@ -330,11 +497,42 @@ PayChanFund::doApply()
|
||||
auto const slep = ctx_.view().peek(k);
|
||||
if (!slep)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
STAmount const amount {ctx_.tx[sfAmount]};
|
||||
|
||||
std::shared_ptr<SLE> sleLine; // if XRP or featurePaychanAndEscrowForTokens
|
||||
// not enabled this remains null
|
||||
|
||||
// if this is a Fund operation on an IOU then perform a dry run here
|
||||
if (!isXRP(amount) &&
|
||||
ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
{
|
||||
sleLine = ctx_.view().peek(
|
||||
keylet::line(
|
||||
(*slep)[sfAccount],
|
||||
amount.getIssuer(),
|
||||
amount.getCurrency()));
|
||||
|
||||
TER result =
|
||||
trustAdjustLockedBalance(
|
||||
ctx_.view(),
|
||||
sleLine,
|
||||
amount,
|
||||
1,
|
||||
ctx_.journal,
|
||||
DryRun);
|
||||
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "PayChanFund::doApply trustAdjustLockedBalance(dry) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
AccountID const src = (*slep)[sfAccount];
|
||||
auto const txAccount = ctx_.tx[sfAccount];
|
||||
auto const expiration = (*slep)[~sfExpiration];
|
||||
|
||||
{
|
||||
auto const cancelAfter = (*slep)[~sfCancelAfter];
|
||||
auto const closeTime =
|
||||
@@ -367,19 +565,6 @@ PayChanFund::doApply()
|
||||
if (!sle)
|
||||
return tefINTERNAL;
|
||||
|
||||
{
|
||||
// Check reserve and funds availability
|
||||
auto const balance = (*sle)[sfBalance];
|
||||
auto const reserve =
|
||||
ctx_.view().fees().accountReserve((*sle)[sfOwnerCount]);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
if (balance < reserve + ctx_.tx[sfAmount])
|
||||
return tecUNFUNDED;
|
||||
}
|
||||
|
||||
// do not allow adding funds if dst does not exist
|
||||
if (AccountID const dst = (*slep)[sfDestination];
|
||||
!ctx_.view().read(keylet::account(dst)))
|
||||
@@ -387,12 +572,49 @@ PayChanFund::doApply()
|
||||
return tecNO_DST;
|
||||
}
|
||||
|
||||
// Check reserve and funds availability
|
||||
auto const balance = (*sle)[sfBalance];
|
||||
auto const reserve =
|
||||
ctx_.view().fees().accountReserve((*sle)[sfOwnerCount]);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
|
||||
if (isXRP(amount))
|
||||
{
|
||||
if (balance < reserve + amount)
|
||||
return tecUNFUNDED;
|
||||
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - amount;
|
||||
ctx_.view().update(sle);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tefINTERNAL;
|
||||
|
||||
|
||||
TER result =
|
||||
trustAdjustLockedBalance(
|
||||
ctx_.view(),
|
||||
sleLine,
|
||||
amount,
|
||||
1,
|
||||
ctx_.journal,
|
||||
WetRun);
|
||||
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "PayChanFund::doApply trustAdjustLockedBalance(wet) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return tefINTERNAL;
|
||||
}
|
||||
|
||||
(*slep)[sfAmount] = (*slep)[sfAmount] + ctx_.tx[sfAmount];
|
||||
ctx_.view().update(slep);
|
||||
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
||||
ctx_.view().update(sle);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -405,12 +627,25 @@ PayChanClaim::preflight(PreflightContext const& ctx)
|
||||
return ret;
|
||||
|
||||
auto const bal = ctx.tx[~sfBalance];
|
||||
if (bal && (!isXRP(*bal) || *bal <= beast::zero))
|
||||
return temBAD_AMOUNT;
|
||||
if (bal)
|
||||
{
|
||||
if (!isXRP(*bal) && !ctx.rules.enabled(featurePaychanAndEscrowForTokens))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (*bal <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
|
||||
auto const amt = ctx.tx[~sfAmount];
|
||||
if (amt && (!isXRP(*amt) || *amt <= beast::zero))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (amt)
|
||||
{
|
||||
if (!isXRP(*amt) && !ctx.rules.enabled(featurePaychanAndEscrowForTokens))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (*amt <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
|
||||
if (bal && amt && *bal > *amt)
|
||||
return temBAD_AMOUNT;
|
||||
@@ -434,8 +669,8 @@ PayChanClaim::preflight(PreflightContext const& ctx)
|
||||
// The signature isn't needed if txAccount == src, but if it's
|
||||
// present, check it
|
||||
|
||||
auto const reqBalance = bal->xrp();
|
||||
auto const authAmt = amt ? amt->xrp() : reqBalance;
|
||||
auto const reqBalance = *bal;
|
||||
auto const authAmt = *amt ? *amt : reqBalance;
|
||||
|
||||
if (reqBalance > authAmt)
|
||||
return temBAD_AMOUNT;
|
||||
@@ -446,7 +681,12 @@ PayChanClaim::preflight(PreflightContext const& ctx)
|
||||
|
||||
PublicKey const pk(ctx.tx[sfPublicKey]);
|
||||
Serializer msg;
|
||||
serializePayChanAuthorization(msg, k.key, authAmt);
|
||||
|
||||
if (isXRP(authAmt))
|
||||
serializePayChanAuthorization(msg, k.key, authAmt.xrp());
|
||||
else
|
||||
serializePayChanAuthorization(msg, k.key, authAmt.iou(), authAmt.getCurrency(), authAmt.getIssuer());
|
||||
|
||||
if (!verify(pk, msg.slice(), *sig, /*canonical*/ true))
|
||||
return temBAD_SIGNATURE;
|
||||
}
|
||||
@@ -482,9 +722,9 @@ PayChanClaim::doApply()
|
||||
|
||||
if (ctx_.tx[~sfBalance])
|
||||
{
|
||||
auto const chanBalance = slep->getFieldAmount(sfBalance).xrp();
|
||||
auto const chanFunds = slep->getFieldAmount(sfAmount).xrp();
|
||||
auto const reqBalance = ctx_.tx[sfBalance].xrp();
|
||||
auto const chanBalance = slep->getFieldAmount(sfBalance);
|
||||
auto const chanFunds = slep->getFieldAmount(sfAmount);
|
||||
auto const reqBalance = ctx_.tx[sfBalance];
|
||||
|
||||
if (txAccount == dst && !ctx_.tx[~sfSignature])
|
||||
return temBAD_SIGNATURE;
|
||||
@@ -503,7 +743,7 @@ PayChanClaim::doApply()
|
||||
// nothing requested
|
||||
return tecUNFUNDED_PAYMENT;
|
||||
|
||||
auto const sled = ctx_.view().peek(keylet::account(dst));
|
||||
auto sled = ctx_.view().peek(keylet::account(dst));
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
|
||||
@@ -511,6 +751,7 @@ PayChanClaim::doApply()
|
||||
// featureDepositAuth to remove the bug.
|
||||
bool const depositAuth{ctx_.view().rules().enabled(featureDepositAuth)};
|
||||
if (!depositAuth &&
|
||||
// RH TODO: does this condition need to be changed for IOU paychans?
|
||||
(txAccount == src && (sled->getFlags() & lsfDisallowXRP)))
|
||||
return tecNO_TARGET;
|
||||
|
||||
@@ -529,9 +770,38 @@ PayChanClaim::doApply()
|
||||
}
|
||||
|
||||
(*slep)[sfBalance] = ctx_.tx[sfBalance];
|
||||
XRPAmount const reqDelta = reqBalance - chanBalance;
|
||||
STAmount const reqDelta = reqBalance - chanBalance;
|
||||
assert(reqDelta >= beast::zero);
|
||||
(*sled)[sfBalance] = (*sled)[sfBalance] + reqDelta;
|
||||
if (isXRP(reqDelta))
|
||||
(*sled)[sfBalance] = (*sled)[sfBalance] + reqDelta;
|
||||
else
|
||||
{
|
||||
// xfer locked tokens to satisfy claim
|
||||
// RH NOTE: there's no ledger modification before this point so
|
||||
// no reason to do a dry run first
|
||||
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tefINTERNAL;
|
||||
|
||||
auto sleSrcAcc = ctx_.view().peek(keylet::account(src));
|
||||
TER result =
|
||||
trustTransferLockedBalance(
|
||||
ctx_.view(),
|
||||
txAccount,
|
||||
sleSrcAcc,
|
||||
sled,
|
||||
reqDelta,
|
||||
0,
|
||||
ctx_.journal,
|
||||
WetRun);
|
||||
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "PayChanClaim::doApply trustTransferLockedBalance(wet) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
ctx_.view().update(sled);
|
||||
ctx_.view().update(slep);
|
||||
}
|
||||
|
||||
@@ -33,11 +33,13 @@
|
||||
#include <ripple/protocol/STTx.h>
|
||||
#include <ripple/protocol/Serializer.h>
|
||||
#include <ripple/protocol/TER.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
@@ -351,6 +353,7 @@ trustDelete(
|
||||
AccountID const& uHighAccountID,
|
||||
beast::Journal j);
|
||||
|
||||
|
||||
/** Delete an offer.
|
||||
|
||||
Requirements:
|
||||
@@ -413,6 +416,628 @@ transferXRP(
|
||||
STAmount const& amount,
|
||||
beast::Journal j);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//
|
||||
// Trustline Locking and Transfer (PaychanAndEscrowForTokens)
|
||||
//
|
||||
|
||||
// In functions white require a `RunType`
|
||||
// pass DryRun (don't apply changes) or WetRun (do apply changes)
|
||||
// to allow compile time evaluation of which types and calls to use
|
||||
|
||||
// For all functions below that take a Dry/Wet run parameter
|
||||
// View may be ReadView const or ApplyView for DryRuns.
|
||||
// View *must* be ApplyView for a WetRun.
|
||||
// Passed SLEs must be non-const for WetRun.
|
||||
#define DryRun RunType<bool, true>()
|
||||
#define WetRun RunType<bool, false>()
|
||||
template <class T, T V>
|
||||
struct RunType
|
||||
{
|
||||
//see:
|
||||
//http://alumni.media.mit.edu/~rahimi/compile-time-flags/
|
||||
constexpr operator T() const
|
||||
{
|
||||
static_assert(std::is_same<bool, T>::value);
|
||||
return V;
|
||||
}
|
||||
|
||||
constexpr T operator!() const
|
||||
{
|
||||
static_assert(std::is_same<bool, T>::value);
|
||||
return !(V);
|
||||
}
|
||||
};
|
||||
|
||||
// allow party lists to be logged easily
|
||||
template <class T>
|
||||
std::ostream& operator<< (std::ostream& lhs, std::vector<T> const& rhs)
|
||||
{
|
||||
lhs << "{";
|
||||
for (int i = 0; i < rhs.size(); ++i)
|
||||
lhs << rhs[i] << (i < rhs.size() - 1 ? ", " : "");
|
||||
lhs << "}";
|
||||
return lhs;
|
||||
}
|
||||
// Return true iff the acc side of line is in default state
|
||||
bool isTrustDefault(
|
||||
std::shared_ptr<SLE> const& acc, // side to check
|
||||
std::shared_ptr<SLE> const& line); // line to check
|
||||
|
||||
/** Lock or unlock a TrustLine balance.
|
||||
If positive deltaAmt lock the amount.
|
||||
If negative deltaAmt unlock the amount.
|
||||
*/
|
||||
template<class V, class S, class R>
|
||||
[[nodiscard]] TER
|
||||
trustAdjustLockedBalance(
|
||||
V& view,
|
||||
S& sleLine,
|
||||
STAmount const& deltaAmt,
|
||||
int deltaLockCount, // if +1 lockCount is increased, -1 is decreased, 0 unchanged
|
||||
beast::Journal const& j,
|
||||
R dryRun)
|
||||
{
|
||||
|
||||
static_assert(
|
||||
(std::is_same<V, ReadView const>::value &&
|
||||
std::is_same<S, std::shared_ptr<SLE const>>::value) ||
|
||||
(std::is_same<V, ApplyView>::value &&
|
||||
std::is_same<S, std::shared_ptr<SLE>>::value));
|
||||
|
||||
// dry runs are explicit in code, but really the view type determines
|
||||
// what occurs here, so this combination is invalid.
|
||||
|
||||
static_assert(!(std::is_same<V, ReadView const>::value && !dryRun));
|
||||
|
||||
if (!view.rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tefINTERNAL;
|
||||
|
||||
auto const currency = deltaAmt.getCurrency();
|
||||
auto const issuer = deltaAmt.getIssuer();
|
||||
|
||||
STAmount lowLimit = sleLine->getFieldAmount(sfLowLimit);
|
||||
|
||||
// the account which is modifying the LockedBalance is always
|
||||
// the side that isn't the issuer, so if the low side is the
|
||||
// issuer then the high side is the account.
|
||||
bool high = lowLimit.getIssuer() == issuer;
|
||||
|
||||
std::vector<AccountID> parties
|
||||
{high ? sleLine->getFieldAmount(sfHighLimit).getIssuer(): lowLimit.getIssuer()};
|
||||
|
||||
// check for freezes & auth
|
||||
{
|
||||
TER result =
|
||||
trustTransferAllowed(
|
||||
view,
|
||||
parties,
|
||||
deltaAmt.issue(),
|
||||
j);
|
||||
|
||||
JLOG(j.trace())
|
||||
<< "trustAdjustLockedBalance: trustTransferAllowed result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
// pull the TL balance from the account's perspective
|
||||
STAmount balance =
|
||||
high ? -(*sleLine)[sfBalance] : (*sleLine)[sfBalance];
|
||||
|
||||
// this would mean somehow the issuer is trying to lock balance
|
||||
if (balance < beast::zero)
|
||||
return tecINTERNAL;
|
||||
|
||||
if (deltaAmt == beast::zero)
|
||||
return tesSUCCESS;
|
||||
|
||||
// can't lock or unlock a zero balance
|
||||
if (balance == beast::zero)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "trustAdjustLockedBalance failed, zero balance";
|
||||
return tecUNFUNDED_PAYMENT;
|
||||
}
|
||||
|
||||
STAmount priorLockedBalance {sfLockedBalance, deltaAmt.issue()};
|
||||
if (sleLine->isFieldPresent(sfLockedBalance))
|
||||
priorLockedBalance =
|
||||
high ? -(*sleLine)[sfLockedBalance] : (*sleLine)[sfLockedBalance];
|
||||
|
||||
uint32_t priorLockCount = 0;
|
||||
if (sleLine->isFieldPresent(sfLockCount))
|
||||
priorLockCount = sleLine->getFieldU32(sfLockCount);
|
||||
|
||||
uint32_t finalLockCount = priorLockCount + deltaLockCount;
|
||||
STAmount finalLockedBalance = priorLockedBalance + deltaAmt;
|
||||
|
||||
if (finalLockedBalance > balance)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "trustAdjustLockedBalance: "
|
||||
<< "lockedBalance("
|
||||
<< finalLockedBalance
|
||||
<< ") > balance("
|
||||
<< balance
|
||||
<< ") = true\n";
|
||||
return tecUNFUNDED_PAYMENT;
|
||||
}
|
||||
|
||||
if (finalLockedBalance < beast::zero)
|
||||
return tecINTERNAL;
|
||||
|
||||
// check if there is significant precision loss
|
||||
if (!isAddable(balance, deltaAmt) ||
|
||||
!isAddable(priorLockedBalance, deltaAmt) ||
|
||||
!isAddable(finalLockedBalance, balance))
|
||||
return tecPRECISION_LOSS;
|
||||
|
||||
// sanity check possible overflows on the lock counter
|
||||
if ((deltaLockCount > 0 && priorLockCount > finalLockCount) ||
|
||||
(deltaLockCount < 0 && priorLockCount < finalLockCount) ||
|
||||
(deltaLockCount == 0 && priorLockCount != finalLockCount))
|
||||
return tecINTERNAL;
|
||||
|
||||
// we won't update any SLEs if it is a dry run
|
||||
if (dryRun)
|
||||
return tesSUCCESS;
|
||||
|
||||
if constexpr(std::is_same<V, ApplyView>::value && std::is_same<S, std::shared_ptr<SLE>>::value)
|
||||
{
|
||||
if (finalLockedBalance == beast::zero || finalLockCount == 0)
|
||||
{
|
||||
sleLine->makeFieldAbsent(sfLockedBalance);
|
||||
sleLine->makeFieldAbsent(sfLockCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
sleLine->
|
||||
setFieldAmount(sfLockedBalance, high ? -finalLockedBalance : finalLockedBalance);
|
||||
sleLine->setFieldU32(sfLockCount, finalLockCount);
|
||||
}
|
||||
|
||||
view.update(sleLine);
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/** Check if a set of accounts can freely exchange the specified token.
|
||||
Read only, does not change any ledger object.
|
||||
May be called with ApplyView or ReadView.
|
||||
(including unlocking) is forbidden by any flag or condition.
|
||||
If parties contains 1 entry then noRipple is not a bar to xfer.
|
||||
If parties contains more than 1 entry then any party with noRipple
|
||||
on issuer side is a bar to xfer.
|
||||
*/
|
||||
template<class V>
|
||||
[[nodiscard]]TER
|
||||
trustTransferAllowed(
|
||||
V& view,
|
||||
std::vector<AccountID> const& parties,
|
||||
Issue const& issue,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
static_assert(
|
||||
std::is_same<V, ReadView const>::value ||
|
||||
std::is_same<V, ApplyView>::value);
|
||||
|
||||
typedef typename std::conditional<
|
||||
std::is_same<V, ApplyView>::value,
|
||||
std::shared_ptr<SLE>,
|
||||
std::shared_ptr<SLE const>>::type SLEPtr;
|
||||
|
||||
|
||||
if (isFakeXRP(issue.currency))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const sleIssuerAcc = view.read(keylet::account(issue.account));
|
||||
|
||||
bool lockedBalanceAllowed =
|
||||
view.rules().enabled(featurePaychanAndEscrowForTokens);
|
||||
|
||||
// missing issuer is always a bar to xfer
|
||||
if (!sleIssuerAcc)
|
||||
return tecNO_ISSUER;
|
||||
|
||||
// issuer global freeze is always a bar to xfer
|
||||
if (isGlobalFrozen(view, issue.account))
|
||||
return tecFROZEN;
|
||||
|
||||
uint32_t issuerFlags = sleIssuerAcc->getFieldU32(sfFlags);
|
||||
|
||||
bool requireAuth = issuerFlags & lsfRequireAuth;
|
||||
|
||||
for (AccountID const& p: parties)
|
||||
{
|
||||
if (p == issue.account)
|
||||
continue;
|
||||
|
||||
auto const line = view.read(keylet::line(p, issue.account, issue.currency));
|
||||
if (!line)
|
||||
{
|
||||
if (requireAuth)
|
||||
{
|
||||
// the line doesn't exist, i.e. it is in default state
|
||||
// default state means the line has not been authed
|
||||
// therefore if auth is required by issuer then
|
||||
// this is now a bar to xfer
|
||||
return tecNO_AUTH;
|
||||
}
|
||||
|
||||
// missing line is a line in default state, this is not
|
||||
// a general bar to xfer, however additional conditions
|
||||
// do attach to completing an xfer into a default line
|
||||
// but these are checked in trustTransferLockedBalance at
|
||||
// the point of transfer.
|
||||
continue;
|
||||
}
|
||||
|
||||
// sanity check the line, insane lines are a bar to xfer
|
||||
{
|
||||
// these "strange" old lines, if they even exist anymore are
|
||||
// always a bar to xfer
|
||||
if (line->getFieldAmount(sfLowLimit).getIssuer() ==
|
||||
line->getFieldAmount(sfHighLimit).getIssuer())
|
||||
return tecINTERNAL;
|
||||
|
||||
if (line->isFieldPresent(sfLockedBalance))
|
||||
{
|
||||
if (!lockedBalanceAllowed)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "trustTransferAllowed: "
|
||||
<< "sfLockedBalance found on line when amendment not enabled";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
STAmount lockedBalance = line->getFieldAmount(sfLockedBalance);
|
||||
STAmount balance = line->getFieldAmount(sfBalance);
|
||||
|
||||
if (lockedBalance.getCurrency() != balance.getCurrency())
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "trustTansferAllowed: "
|
||||
<< "lockedBalance currency did not match balance currency";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check the bars to xfer ... these are:
|
||||
// any TL in the set has noRipple on the issuer's side
|
||||
// any TL in the set has a freeze on the issuer's side
|
||||
// any TL in the set has RequireAuth and the TL lacks lsf*Auth
|
||||
{
|
||||
bool pHigh = p > issue.account;
|
||||
|
||||
auto const flagIssuerNoRipple { pHigh ? lsfLowNoRipple : lsfHighNoRipple };
|
||||
auto const flagIssuerFreeze { pHigh ? lsfLowFreeze : lsfHighFreeze };
|
||||
auto const flagIssuerAuth { pHigh ? lsfLowAuth : lsfHighAuth };
|
||||
|
||||
uint32_t flags = line->getFieldU32(sfFlags);
|
||||
|
||||
if (flags & flagIssuerFreeze)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "trustTransferAllowed: "
|
||||
<< "parties=[" << parties << "], "
|
||||
<< "issuer: " << issue.account << " "
|
||||
<< "has freeze on party: " << p;
|
||||
return tecFROZEN;
|
||||
}
|
||||
|
||||
// if called with more than one party then any party
|
||||
// that has a noripple on the issuer side of their tl
|
||||
// blocks any possible xfer
|
||||
if (parties.size() > 1 && (flags & flagIssuerNoRipple))
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "trustTransferAllowed: "
|
||||
<< "parties=[" << parties << "], "
|
||||
<< "issuer: " << issue.account << " "
|
||||
<< "has noRipple on party: " << p;
|
||||
return tecPATH_DRY;
|
||||
}
|
||||
|
||||
// every party involved must be on an authed trustline if
|
||||
// the issuer has specified lsfRequireAuth
|
||||
if (requireAuth && !(flags & flagIssuerAuth))
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "trustTransferAllowed: "
|
||||
<< "parties=[" << parties << "], "
|
||||
<< "issuer: " << issue.account << " "
|
||||
<< "requires TL auth which "
|
||||
<< "party: " << p << " "
|
||||
<< "does not possess.";
|
||||
return tecNO_AUTH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
/** Transfer a locked balance from one TL to an unlocked balance on another
|
||||
or create a line at the destination if the actingAcc has permission to.
|
||||
Used for resolving payment instruments that use locked TL balances.
|
||||
*/
|
||||
template <class V, class S, class R>
|
||||
[[nodiscard]] TER
|
||||
trustTransferLockedBalance(
|
||||
V& view,
|
||||
AccountID const& actingAccID, // the account whose tx is actioning xfer
|
||||
S& sleSrcAcc,
|
||||
S& sleDstAcc,
|
||||
STAmount const& amount, // issuer, currency are in this field
|
||||
int deltaLockCount, // -1 decrement, +1 increment, 0 unchanged
|
||||
beast::Journal const& j,
|
||||
R dryRun)
|
||||
{
|
||||
|
||||
typedef typename std::conditional<
|
||||
std::is_same<V, ApplyView>::value && !dryRun,
|
||||
std::shared_ptr<SLE>,
|
||||
std::shared_ptr<SLE const>>::type SLEPtr;
|
||||
|
||||
auto peek = [&](Keylet& k)
|
||||
{
|
||||
if constexpr (std::is_same<V, ApplyView>::value && !dryRun)
|
||||
return const_cast<ApplyView&>(view).peek(k);
|
||||
else
|
||||
return view.read(k);
|
||||
};
|
||||
|
||||
static_assert(std::is_same<V, ApplyView>::value || dryRun);
|
||||
|
||||
if (!view.rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tefINTERNAL;
|
||||
|
||||
if (!sleSrcAcc || !sleDstAcc)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "trustTransferLockedBalance without sleSrc/sleDst";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
if (amount <= beast::zero)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "trustTransferLockedBalance with non-positive amount";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
auto issuerAccID = amount.getIssuer();
|
||||
auto currency = amount.getCurrency();
|
||||
auto srcAccID = sleSrcAcc->getAccountID(sfAccount);
|
||||
auto dstAccID = sleDstAcc->getAccountID(sfAccount);
|
||||
|
||||
bool srcHigh = srcAccID > issuerAccID;
|
||||
bool dstHigh = dstAccID > issuerAccID;
|
||||
|
||||
// check for freezing, auth, no ripple and TL sanity
|
||||
{
|
||||
TER result =
|
||||
trustTransferAllowed(
|
||||
view,
|
||||
{srcAccID, dstAccID},
|
||||
{currency, issuerAccID},
|
||||
j);
|
||||
|
||||
JLOG(j.trace())
|
||||
<< "trustTransferLockedBalance: trustTransferAlowed result="
|
||||
<< result;
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
// ensure source line exists
|
||||
Keylet klSrcLine { keylet::line(srcAccID, issuerAccID, currency)};
|
||||
SLEPtr sleSrcLine = peek(klSrcLine);
|
||||
|
||||
if (!sleSrcLine)
|
||||
return tecNO_LINE;
|
||||
|
||||
// can't transfer a locked balance that does not exist
|
||||
if (!sleSrcLine->isFieldPresent(sfLockedBalance) || !sleSrcLine->isFieldPresent(sfLockCount))
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "trustTransferLockedBalance could not find sfLockedBalance/sfLockCount on source line";
|
||||
return tecUNFUNDED_PAYMENT;
|
||||
}
|
||||
|
||||
|
||||
// decrement source balance
|
||||
{
|
||||
STAmount priorBalance =
|
||||
srcHigh ? -((*sleSrcLine)[sfBalance]) : (*sleSrcLine)[sfBalance];
|
||||
|
||||
STAmount priorLockedBalance =
|
||||
srcHigh ? -((*sleSrcLine)[sfLockedBalance]) : (*sleSrcLine)[sfLockedBalance];
|
||||
|
||||
uint32_t priorLockCount = (*sleSrcLine)[sfLockCount];
|
||||
|
||||
AccountID srcIssuerAccID =
|
||||
sleSrcLine->getFieldAmount(srcHigh ? sfLowLimit : sfHighLimit).getIssuer();
|
||||
|
||||
// check they have sufficient funds
|
||||
if (amount > priorLockedBalance)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "trustTransferLockedBalance amount > lockedBalance: "
|
||||
<< "amount=" << amount << " lockedBalance="
|
||||
<< priorLockedBalance;
|
||||
return tecUNFUNDED_PAYMENT;
|
||||
}
|
||||
|
||||
STAmount finalBalance = priorBalance - amount;
|
||||
|
||||
STAmount finalLockedBalance = priorLockedBalance - amount;
|
||||
|
||||
uint32_t finalLockCount = priorLockCount + deltaLockCount;
|
||||
|
||||
// check if there is significant precision loss
|
||||
if (!isAddable(priorBalance, amount) ||
|
||||
!isAddable(priorLockedBalance, amount))
|
||||
return tecPRECISION_LOSS;
|
||||
|
||||
// sanity check possible overflows on the lock counter
|
||||
if ((deltaLockCount > 0 && priorLockCount > finalLockCount) ||
|
||||
(deltaLockCount < 0 && priorLockCount < finalLockCount) ||
|
||||
(deltaLockCount == 0 && priorLockCount != finalLockCount))
|
||||
return tecINTERNAL;
|
||||
|
||||
// this should never happen but defensively check it here before updating sle
|
||||
if (finalBalance < beast::zero || finalLockedBalance < beast::zero)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "trustTransferLockedBalance results in a negative balance on source line";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
if constexpr(!dryRun)
|
||||
{
|
||||
sleSrcLine->setFieldAmount(sfBalance, srcHigh ? -finalBalance : finalBalance);
|
||||
|
||||
if (finalLockedBalance == beast::zero || finalLockCount == 0)
|
||||
{
|
||||
sleSrcLine->makeFieldAbsent(sfLockedBalance);
|
||||
sleSrcLine->makeFieldAbsent(sfLockCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
sleSrcLine->setFieldAmount(sfLockedBalance, srcHigh ? -finalLockedBalance : finalLockedBalance);
|
||||
sleSrcLine->setFieldU32(sfLockCount, finalLockCount);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// dstLow XNOR srcLow tells us if we need to flip the balance amount
|
||||
// on the destination line
|
||||
bool flipDstAmt = !((dstHigh && srcHigh) || (!dstHigh && !srcHigh));
|
||||
|
||||
// compute transfer fee, if any
|
||||
auto xferRate = transferRate(view, issuerAccID);
|
||||
|
||||
// the destination will sometimes get less depending on xfer rate
|
||||
// with any difference in tokens burned
|
||||
auto dstAmt =
|
||||
xferRate == parityRate
|
||||
? amount
|
||||
: multiplyRound(amount, xferRate, amount.issue(), true);
|
||||
|
||||
// check for a destination line
|
||||
Keylet klDstLine = keylet::line(dstAccID, issuerAccID, currency);
|
||||
SLEPtr sleDstLine = peek(klDstLine);
|
||||
|
||||
if (!sleDstLine)
|
||||
{
|
||||
// in most circumstances a missing destination line is a deal breaker
|
||||
if (actingAccID != dstAccID && srcAccID != dstAccID)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
STAmount dstBalanceDrops = sleDstAcc->getFieldAmount(sfBalance);
|
||||
|
||||
// no dst line exists, we might be able to create one...
|
||||
if (std::uint32_t const ownerCount = {sleDstAcc->at(sfOwnerCount)};
|
||||
dstBalanceDrops < view.fees().accountReserve(ownerCount + 1))
|
||||
return tecNO_LINE_INSUF_RESERVE;
|
||||
|
||||
// yes we can... we will
|
||||
|
||||
if constexpr(!dryRun)
|
||||
{
|
||||
// clang-format off
|
||||
if (TER const ter = trustCreate(
|
||||
view,
|
||||
!dstHigh, // is dest low?
|
||||
issuerAccID, // source
|
||||
dstAccID, // destination
|
||||
klDstLine.key, // ledger index
|
||||
sleDstAcc, // Account to add to
|
||||
false, // authorize account
|
||||
(sleDstAcc->getFlags() & lsfDefaultRipple) == 0,
|
||||
false, // freeze trust line
|
||||
flipDstAmt ? -dstAmt : dstAmt, // initial balance
|
||||
Issue(currency, dstAccID), // limit of zero
|
||||
0, // quality in
|
||||
0, // quality out
|
||||
j); // journal
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
return ter;
|
||||
}
|
||||
}
|
||||
// clang-format on
|
||||
}
|
||||
else
|
||||
{
|
||||
// the dst line does exist, and it would have been checked above
|
||||
// in trustTransferAllowed for NoRipple and Freeze flags
|
||||
|
||||
// check the limit
|
||||
STAmount dstLimit =
|
||||
dstHigh ? (*sleDstLine)[sfHighLimit] : (*sleDstLine)[sfLowLimit];
|
||||
|
||||
STAmount priorBalance =
|
||||
dstHigh ? -((*sleDstLine)[sfBalance]) : (*sleDstLine)[sfBalance];
|
||||
|
||||
STAmount finalBalance = priorBalance + (flipDstAmt ? -dstAmt : dstAmt);
|
||||
|
||||
if (finalBalance < priorBalance)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "trustTransferLockedBalance resulted in a lower final balance on dest line";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
if (finalBalance > dstLimit && actingAccID != dstAccID)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "trustTransferLockedBalance would increase dest line above limit without permission";
|
||||
return tecPATH_DRY;
|
||||
}
|
||||
|
||||
// check if there is significant precision loss
|
||||
if (!isAddable(priorBalance, dstAmt))
|
||||
return tecPRECISION_LOSS;
|
||||
|
||||
if constexpr(!dryRun)
|
||||
sleDstLine->setFieldAmount(sfBalance, dstHigh ? -finalBalance : finalBalance);
|
||||
}
|
||||
|
||||
if constexpr (!dryRun)
|
||||
{
|
||||
static_assert(std::is_same<V, ApplyView>::value);
|
||||
|
||||
// check if source line ended up in default state and adjust owner count if it did
|
||||
if (isTrustDefault(sleSrcAcc, sleSrcLine))
|
||||
{
|
||||
uint32_t flags = sleSrcLine->getFieldU32(sfFlags);
|
||||
uint32_t fReserve { srcHigh ? lsfHighReserve : lsfLowReserve };
|
||||
if (flags & fReserve)
|
||||
{
|
||||
sleSrcLine->setFieldU32(sfFlags, flags & ~fReserve);
|
||||
adjustOwnerCount(view, sleSrcAcc, -1, j);
|
||||
view.update(sleSrcAcc);
|
||||
}
|
||||
}
|
||||
|
||||
view.update(sleSrcLine);
|
||||
|
||||
// a destination line already existed and was updated
|
||||
if (sleDstLine)
|
||||
view.update(sleDstLine);
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -254,6 +254,29 @@ accountHolds(
|
||||
// Put balance in account terms.
|
||||
amount.negate();
|
||||
}
|
||||
|
||||
// If tokens can be escrowed then they can be locked in the trustline
|
||||
// which means we must never spend them until the escrow is released.
|
||||
if (view.rules().enabled(featurePaychanAndEscrowForTokens)
|
||||
&& sle->isFieldPresent(sfLockedBalance))
|
||||
{
|
||||
STAmount lockedBalance = sle->getFieldAmount(sfLockedBalance);
|
||||
STAmount spendableBalance = amount -
|
||||
(account > issuer ? -lockedBalance : lockedBalance);
|
||||
|
||||
// RH NOTE: this is defensively programmed, it should never fire
|
||||
// if something bad does happen the trustline acts as a frozen line.
|
||||
if (spendableBalance < beast::zero || spendableBalance > amount)
|
||||
{
|
||||
JLOG(j.error())
|
||||
<< "SpendableBalance has illegal value in accountHolds "
|
||||
<< spendableBalance;
|
||||
amount.clear(Issue{currency, issuer});
|
||||
}
|
||||
else
|
||||
amount = spendableBalance;
|
||||
}
|
||||
|
||||
amount.setIssuer(issuer);
|
||||
}
|
||||
JLOG(j.trace()) << "accountHolds:"
|
||||
@@ -889,6 +912,55 @@ trustDelete(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
bool isTrustDefault(
|
||||
std::shared_ptr<SLE> const& acc,
|
||||
std::shared_ptr<SLE> const& line)
|
||||
{
|
||||
assert(acc && line);
|
||||
|
||||
uint32_t tlFlags = line->getFieldU32(sfFlags);
|
||||
|
||||
AccountID highAccID = line->getFieldAmount(sfHighLimit).issue().account;
|
||||
AccountID lowAccID = line->getFieldAmount(sfLowLimit ).issue().account;
|
||||
|
||||
AccountID accID = acc->getAccountID(sfAccount);
|
||||
|
||||
assert(accID == highAccID || accID == lowAccID);
|
||||
|
||||
bool high = accID == highAccID;
|
||||
|
||||
uint32_t acFlags = line->getFieldU32(sfFlags);
|
||||
|
||||
const auto fNoRipple {high ? lsfHighNoRipple : lsfLowNoRipple};
|
||||
const auto fFreeze {high ? lsfHighFreeze : lsfLowFreeze};
|
||||
|
||||
if (tlFlags & fFreeze)
|
||||
return false;
|
||||
|
||||
if ((acFlags & lsfDefaultRipple) && (tlFlags & fNoRipple))
|
||||
return false;
|
||||
|
||||
if (line->getFieldAmount(sfBalance) != beast::zero)
|
||||
return false;
|
||||
|
||||
if (line->isFieldPresent(sfLockedBalance))
|
||||
return false;
|
||||
|
||||
if (line->getFieldAmount(high ? sfHighLimit : sfLowLimit) != beast::zero)
|
||||
return false;
|
||||
|
||||
uint32_t qualityIn = line->getFieldU32(high ? sfHighQualityIn : sfLowQualityIn);
|
||||
uint32_t qualityOut = line->getFieldU32(high ? sfHighQualityOut : sfLowQualityOut);
|
||||
|
||||
if (qualityIn && qualityIn != QUALITY_ONE)
|
||||
return false;
|
||||
|
||||
if (qualityOut && qualityOut != QUALITY_ONE)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TER
|
||||
offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j)
|
||||
{
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace detail {
|
||||
// Feature.cpp. Because it's only used to reserve storage, and determine how
|
||||
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
|
||||
// the actual number of amendments. A LogicError on startup will verify this.
|
||||
static constexpr std::size_t numFeatures = 49;
|
||||
static constexpr std::size_t numFeatures = 50;
|
||||
|
||||
/** Amendments that this server supports and the default voting behavior.
|
||||
Whether they are enabled depends on the Rules defined in the validated
|
||||
@@ -337,6 +337,7 @@ extern uint256 const featureHooks;
|
||||
extern uint256 const featureExpandedSignerList;
|
||||
extern uint256 const featureNonFungibleTokensV1;
|
||||
extern uint256 const fixNFTokenDirV1;
|
||||
extern uint256 const featurePaychanAndEscrowForTokens;
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
|
||||
@@ -38,6 +38,31 @@ serializePayChanAuthorization(
|
||||
msg.add64(amt.drops());
|
||||
}
|
||||
|
||||
inline void
|
||||
serializePayChanAuthorization(
|
||||
Serializer& msg,
|
||||
uint256 const& key,
|
||||
IOUAmount const& amt,
|
||||
Currency const& cur,
|
||||
AccountID const& iss)
|
||||
{
|
||||
msg.add32(HashPrefix::paymentChannelClaim);
|
||||
msg.addBitString(key);
|
||||
if (amt == beast::zero)
|
||||
msg.add64(STAmount::cNotNative);
|
||||
else if (amt.signum() == -1) // 512 = not native
|
||||
msg.add64(
|
||||
amt.mantissa() |
|
||||
(static_cast<std::uint64_t>(amt.exponent() + 512 + 97) << (64 - 10)));
|
||||
else // 256 = positive
|
||||
msg.add64(
|
||||
amt.mantissa() |
|
||||
(static_cast<std::uint64_t>(amt.exponent() + 512 + 256 + 97)
|
||||
<< (64 - 10)));
|
||||
msg.addBitString(cur);
|
||||
msg.addBitString(iss);
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -400,6 +400,7 @@ extern SF_UINT32 const sfMintedNFTokens;
|
||||
extern SF_UINT32 const sfBurnedNFTokens;
|
||||
extern SF_UINT32 const sfHookStateCount;
|
||||
extern SF_UINT32 const sfEmitGeneration;
|
||||
extern SF_UINT32 const sfLockCount;
|
||||
|
||||
// 64-bit integers (common)
|
||||
extern SF_UINT64 const sfIndexNext;
|
||||
@@ -479,6 +480,7 @@ extern SF_AMOUNT const sfHighLimit;
|
||||
extern SF_AMOUNT const sfFee;
|
||||
extern SF_AMOUNT const sfSendMax;
|
||||
extern SF_AMOUNT const sfDeliverMin;
|
||||
extern SF_AMOUNT const sfLockedBalance;
|
||||
|
||||
// currency amount (uncommon)
|
||||
extern SF_AMOUNT const sfMinimumOffer;
|
||||
|
||||
@@ -517,6 +517,56 @@ isXRP(STAmount const& amount)
|
||||
return isXRP(amount.issue().currency);
|
||||
}
|
||||
|
||||
inline bool
|
||||
isFakeXRP(STAmount const& amount)
|
||||
{
|
||||
if (amount.native())
|
||||
return false;
|
||||
|
||||
return isFakeXRP(amount.issue().currency);
|
||||
}
|
||||
|
||||
/** returns true iff adding or subtracting results in less than or equal to 0.01% precision loss **/
|
||||
inline bool
|
||||
isAddable(STAmount const& amt1, STAmount const& amt2)
|
||||
{
|
||||
// special case: adding anything to zero is always fine
|
||||
if (amt1 == beast::zero || amt2 == beast::zero)
|
||||
return true;
|
||||
|
||||
// special case: adding two xrp amounts together.
|
||||
// this is just an overflow check
|
||||
if (isXRP(amt1) && isXRP(amt2))
|
||||
{
|
||||
XRPAmount A = (amt1.signum() == -1 ? -(amt1.xrp()) : amt1.xrp());
|
||||
XRPAmount B = (amt2.signum() == -1 ? -(amt2.xrp()) : amt2.xrp());
|
||||
|
||||
XRPAmount finalAmt = A + B;
|
||||
return (finalAmt >= A && finalAmt >= B);
|
||||
}
|
||||
|
||||
static const STAmount one {IOUAmount{1, 0}, noIssue()};
|
||||
static const STAmount maxLoss {IOUAmount{1, -4}, noIssue()};
|
||||
|
||||
STAmount A = amt1;
|
||||
STAmount B = amt2;
|
||||
|
||||
if (isXRP(A))
|
||||
A = STAmount{IOUAmount{A.xrp().drops(), -6}, noIssue()};
|
||||
|
||||
if (isXRP(B))
|
||||
B = STAmount{IOUAmount{B.xrp().drops(), -6}, noIssue()};
|
||||
|
||||
A.setIssue(noIssue());
|
||||
B.setIssue(noIssue());
|
||||
|
||||
STAmount lhs = divide((A - B) + B, A, noIssue()) - one;
|
||||
STAmount rhs = divide((B - A) + A, B, noIssue()) - one;
|
||||
|
||||
return ((rhs.negative() ? -rhs : rhs) + (lhs.negative() ? -lhs : lhs)) <= maxLoss;
|
||||
}
|
||||
|
||||
|
||||
// Since `canonicalize` does not have access to a ledger, this is needed to put
|
||||
// the low-level routine stAmountCanonicalize on an amendment switch. Only
|
||||
// transactions need to use this switchover. Outside of a transaction it's safe
|
||||
|
||||
@@ -293,6 +293,7 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecOBJECT_NOT_FOUND = 160,
|
||||
tecINSUFFICIENT_PAYMENT = 161,
|
||||
tecREQUIRES_FLAG = 162,
|
||||
tecPRECISION_LOSS = 163,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -77,6 +77,12 @@ isXRP(Currency const& c)
|
||||
return c == beast::zero;
|
||||
}
|
||||
|
||||
inline bool
|
||||
isFakeXRP(Currency const& c)
|
||||
{
|
||||
return c == badCurrency();
|
||||
}
|
||||
|
||||
/** Returns "", "XRP", or three letter ISO code. */
|
||||
std::string
|
||||
to_string(Currency const& c);
|
||||
|
||||
@@ -441,6 +441,7 @@ REGISTER_FEATURE(Hooks, Supported::yes, DefaultVote::no)
|
||||
REGISTER_FEATURE(NonFungibleTokensV1, Supported::yes, DefaultVote::no);
|
||||
REGISTER_FEATURE(ExpandedSignerList, Supported::yes, DefaultVote::no);
|
||||
REGISTER_FIX (fixNFTokenDirV1, Supported::yes, DefaultVote::no);
|
||||
REGISTER_FEATURE(PaychanAndEscrowForTokens, Supported::yes, DefaultVote::no);
|
||||
|
||||
// The following amendments have been active for at least two years. Their
|
||||
// pre-amendment code has been removed and the identifiers are deprecated.
|
||||
|
||||
@@ -56,7 +56,7 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
FEE_SETTINGS = 'e',
|
||||
TICKET = 'T',
|
||||
SIGNER_LIST = 'S',
|
||||
XRP_PAYMENT_CHANNEL = 'x',
|
||||
PAYMENT_CHANNEL = 'x',
|
||||
CHECK = 'C',
|
||||
DEPOSIT_PREAUTH = 'p',
|
||||
NEGATIVE_UNL = 'N',
|
||||
@@ -369,7 +369,7 @@ payChan(AccountID const& src, AccountID const& dst, UInt32or256 const& seq) noex
|
||||
{
|
||||
return {
|
||||
ltPAYCHAN,
|
||||
indexHash(LedgerNameSpace::XRP_PAYMENT_CHANNEL, src, dst, seq)};
|
||||
indexHash(LedgerNameSpace::PAYMENT_CHANNEL, src, dst, seq)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
|
||||
@@ -108,6 +108,8 @@ LedgerFormats::LedgerFormats()
|
||||
{sfHighNode, soeOPTIONAL},
|
||||
{sfHighQualityIn, soeOPTIONAL},
|
||||
{sfHighQualityOut, soeOPTIONAL},
|
||||
{sfLockedBalance, soeOPTIONAL},
|
||||
{sfLockCount, soeOPTIONAL},
|
||||
},
|
||||
commonFields);
|
||||
|
||||
|
||||
@@ -150,6 +150,7 @@ CONSTRUCT_TYPED_SFIELD(sfMintedNFTokens, "MintedNFTokens", UINT32,
|
||||
CONSTRUCT_TYPED_SFIELD(sfBurnedNFTokens, "BurnedNFTokens", UINT32, 44);
|
||||
CONSTRUCT_TYPED_SFIELD(sfHookStateCount, "HookStateCount", UINT32, 45);
|
||||
CONSTRUCT_TYPED_SFIELD(sfEmitGeneration, "EmitGeneration", UINT32, 46);
|
||||
CONSTRUCT_TYPED_SFIELD(sfLockCount, "LockCount", UINT32, 47);
|
||||
|
||||
// 64-bit integers (common)
|
||||
CONSTRUCT_TYPED_SFIELD(sfIndexNext, "IndexNext", UINT64, 1);
|
||||
@@ -236,6 +237,7 @@ CONSTRUCT_TYPED_SFIELD(sfRippleEscrow, "RippleEscrow", AMOUNT,
|
||||
CONSTRUCT_TYPED_SFIELD(sfDeliveredAmount, "DeliveredAmount", AMOUNT, 18);
|
||||
CONSTRUCT_TYPED_SFIELD(sfNFTokenBrokerFee, "NFTokenBrokerFee", AMOUNT, 19);
|
||||
CONSTRUCT_TYPED_SFIELD(sfHookCallbackFee, "HookCallbackFee", AMOUNT, 20);
|
||||
CONSTRUCT_TYPED_SFIELD(sfLockedBalance, "LockedBalance", AMOUNT, 21);
|
||||
|
||||
// variable length (common)
|
||||
CONSTRUCT_TYPED_SFIELD(sfPublicKey, "PublicKey", VL, 1);
|
||||
|
||||
@@ -89,7 +89,7 @@ transResults()
|
||||
MAKE_ERROR(tecOBJECT_NOT_FOUND, "A requested object could not be located."),
|
||||
MAKE_ERROR(tecINSUFFICIENT_PAYMENT, "The payment is not sufficient."),
|
||||
MAKE_ERROR(tecHOOK_REJECTED, "Rejected by hook on sending or receiving account."),
|
||||
|
||||
MAKE_ERROR(tecPRECISION_LOSS, "The IOU amounts used by the transaction cannot interact."),
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
MAKE_ERROR(tefBAD_AUTH, "Transaction's public key is not authorized."),
|
||||
|
||||
Reference in New Issue
Block a user