mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-20 10:35:50 +00:00
merged IOUEscrow amendment
This commit is contained in:
@@ -33,6 +33,8 @@
|
|||||||
#include <ripple/protocol/TxFlags.h>
|
#include <ripple/protocol/TxFlags.h>
|
||||||
#include <ripple/protocol/digest.h>
|
#include <ripple/protocol/digest.h>
|
||||||
#include <ripple/protocol/st.h>
|
#include <ripple/protocol/st.h>
|
||||||
|
#include <ripple/protocol/Rate.h>
|
||||||
|
|
||||||
|
|
||||||
// During an EscrowFinish, the transaction must specify both
|
// During an EscrowFinish, the transaction must specify both
|
||||||
// a condition and a fulfillment. We track whether that
|
// a condition and a fulfillment. We track whether that
|
||||||
@@ -93,7 +95,8 @@ after(NetClock::time_point now, std::uint32_t mark)
|
|||||||
TxConsequences
|
TxConsequences
|
||||||
EscrowCreate::makeTxConsequences(PreflightContext const& ctx)
|
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
|
NotTEC
|
||||||
@@ -105,9 +108,26 @@ EscrowCreate::preflight(PreflightContext const& ctx)
|
|||||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
if (!isXRP(ctx.tx[sfAmount]))
|
STAmount const amount {ctx.tx[sfAmount]};
|
||||||
|
if (!isXRP(amount))
|
||||||
|
{
|
||||||
|
if (!ctx.rules.enabled(featurePaychanAndEscrowForTokens))
|
||||||
return temBAD_AMOUNT;
|
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)
|
if (ctx.tx[sfAmount] <= beast::zero)
|
||||||
return temBAD_AMOUNT;
|
return temBAD_AMOUNT;
|
||||||
|
|
||||||
@@ -199,8 +219,10 @@ EscrowCreate::doApply()
|
|||||||
if (!sle)
|
if (!sle)
|
||||||
return tefINTERNAL;
|
return tefINTERNAL;
|
||||||
|
|
||||||
// Check reserve and funds availability
|
STAmount const amount {ctx_.tx[sfAmount]};
|
||||||
{
|
|
||||||
|
std::shared_ptr<SLE> sleLine;
|
||||||
|
|
||||||
auto const balance = STAmount((*sle)[sfBalance]).xrp();
|
auto const balance = STAmount((*sle)[sfBalance]).xrp();
|
||||||
auto const reserve =
|
auto const reserve =
|
||||||
ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1);
|
ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1);
|
||||||
@@ -208,8 +230,55 @@ EscrowCreate::doApply()
|
|||||||
if (balance < reserve)
|
if (balance < reserve)
|
||||||
return tecINSUFFICIENT_RESERVE;
|
return tecINSUFFICIENT_RESERVE;
|
||||||
|
|
||||||
if (balance < reserve + STAmount(ctx_.tx[sfAmount]).xrp())
|
// Check reserve and funds availability
|
||||||
|
if (isXRP(amount) && balance < reserve + STAmount(ctx_.tx[sfAmount]).xrp())
|
||||||
return tecUNFUNDED;
|
return tecUNFUNDED;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// preflight will prevent this ever firing, included
|
||||||
|
// defensively for completeness
|
||||||
|
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
|
||||||
|
return tefINTERNAL;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
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
|
// Check destination account
|
||||||
@@ -274,7 +343,31 @@ EscrowCreate::doApply()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Deduct owner's balance, increment owner count
|
// Deduct owner's balance, increment owner count
|
||||||
|
if (isXRP(amount))
|
||||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
(*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);
|
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
|
||||||
ctx_.view().update(sle);
|
ctx_.view().update(sle);
|
||||||
|
|
||||||
@@ -384,6 +477,10 @@ EscrowFinish::doApply()
|
|||||||
if (!slep)
|
if (!slep)
|
||||||
return tecNO_TARGET;
|
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
|
// 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
|
// to that time. fix1571 corrects a logic error in the check that would make
|
||||||
// a finish only succeed strictly after the cancel time.
|
// 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
|
// Remove escrow from owner directory
|
||||||
{
|
{
|
||||||
@@ -508,12 +631,38 @@ EscrowFinish::doApply()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transfer amount to destination
|
|
||||||
|
|
||||||
|
if (isXRP(amount))
|
||||||
(*sled)[sfBalance] = (*sled)[sfBalance] + (*slep)[sfAmount];
|
(*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);
|
ctx_.view().update(sled);
|
||||||
|
|
||||||
// Adjust source owner count
|
// Adjust source owner count
|
||||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
|
||||||
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
|
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
|
||||||
ctx_.view().update(sle);
|
ctx_.view().update(sle);
|
||||||
|
|
||||||
@@ -581,6 +730,32 @@ EscrowCancel::doApply()
|
|||||||
}
|
}
|
||||||
|
|
||||||
AccountID const account = (*slep)[sfAccount];
|
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
|
// Remove escrow from owner directory
|
||||||
{
|
{
|
||||||
@@ -607,9 +782,33 @@ EscrowCancel::doApply()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transfer amount back to owner, decrement owner count
|
// Transfer amount back to the owner (or unlock it in TL case)
|
||||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
if (isXRP(amount))
|
||||||
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount];
|
(*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);
|
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
|
||||||
ctx_.view().update(sle);
|
ctx_.view().update(sle);
|
||||||
|
|
||||||
|
|||||||
@@ -99,11 +99,14 @@ XRPNotCreated::visitEntry(
|
|||||||
drops_ -= (*before)[sfBalance].xrp().drops();
|
drops_ -= (*before)[sfBalance].xrp().drops();
|
||||||
break;
|
break;
|
||||||
case ltPAYCHAN:
|
case ltPAYCHAN:
|
||||||
|
if (isXRP((*before)[sfAmount]))
|
||||||
drops_ -=
|
drops_ -=
|
||||||
((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
|
((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
|
||||||
break;
|
break;
|
||||||
case ltESCROW:
|
case ltESCROW:
|
||||||
drops_ -= (*before)[sfAmount].xrp().drops();
|
if (isXRP((*before)[sfAmount]))
|
||||||
|
drops_ -=
|
||||||
|
(*before)[sfAmount].xrp().drops();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -118,14 +121,14 @@ XRPNotCreated::visitEntry(
|
|||||||
drops_ += (*after)[sfBalance].xrp().drops();
|
drops_ += (*after)[sfBalance].xrp().drops();
|
||||||
break;
|
break;
|
||||||
case ltPAYCHAN:
|
case ltPAYCHAN:
|
||||||
if (!isDelete)
|
if (!isDelete && isXRP((*after)[sfAmount]))
|
||||||
drops_ += ((*after)[sfAmount] - (*after)[sfBalance])
|
drops_ +=
|
||||||
.xrp()
|
((*after)[sfAmount] - (*after)[sfBalance]).xrp().drops();
|
||||||
.drops();
|
|
||||||
break;
|
break;
|
||||||
case ltESCROW:
|
case ltESCROW:
|
||||||
if (!isDelete)
|
if (!isDelete && isXRP((*after)[sfAmount]))
|
||||||
drops_ += (*after)[sfAmount].xrp().drops();
|
drops_ +=
|
||||||
|
(*after)[sfAmount].xrp().drops();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -285,12 +288,25 @@ NoZeroEscrow::visitEntry(
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
NoZeroEscrow::finalize(
|
NoZeroEscrow::finalize(
|
||||||
STTx const&,
|
STTx const& txn,
|
||||||
TER const,
|
TER const,
|
||||||
XRPAmount const,
|
XRPAmount const,
|
||||||
ReadView const&,
|
ReadView const& rv,
|
||||||
beast::Journal const& j)
|
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_)
|
if (bad_)
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << "Invariant failed: escrow specifies invalid amount";
|
JLOG(j.fatal()) << "Invariant failed: escrow specifies invalid amount";
|
||||||
|
|||||||
@@ -120,6 +120,36 @@ closeChannel(
|
|||||||
beast::Journal j)
|
beast::Journal j)
|
||||||
{
|
{
|
||||||
AccountID const src = (*slep)[sfAccount];
|
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
|
// Remove PayChan from owner directory
|
||||||
{
|
{
|
||||||
auto const page = (*slep)[sfOwnerNode];
|
auto const page = (*slep)[sfOwnerNode];
|
||||||
@@ -150,8 +180,28 @@ closeChannel(
|
|||||||
return tefINTERNAL;
|
return tefINTERNAL;
|
||||||
|
|
||||||
assert((*slep)[sfAmount] >= (*slep)[sfBalance]);
|
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);
|
adjustOwnerCount(view, sle, -1, j);
|
||||||
view.update(sle);
|
view.update(sle);
|
||||||
|
|
||||||
@@ -165,7 +215,8 @@ closeChannel(
|
|||||||
TxConsequences
|
TxConsequences
|
||||||
PayChanCreate::makeTxConsequences(PreflightContext const& ctx)
|
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
|
NotTEC
|
||||||
@@ -177,7 +228,27 @@ PayChanCreate::preflight(PreflightContext const& ctx)
|
|||||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||||
return 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;
|
return temBAD_AMOUNT;
|
||||||
|
|
||||||
if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
|
if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
|
||||||
@@ -197,8 +268,8 @@ PayChanCreate::preclaim(PreclaimContext const& ctx)
|
|||||||
if (!sle)
|
if (!sle)
|
||||||
return terNO_ACCOUNT;
|
return terNO_ACCOUNT;
|
||||||
|
|
||||||
// Check reserve and funds availability
|
STAmount const amount {ctx.tx[sfAmount]};
|
||||||
{
|
|
||||||
auto const balance = (*sle)[sfBalance];
|
auto const balance = (*sle)[sfBalance];
|
||||||
auto const reserve =
|
auto const reserve =
|
||||||
ctx.view.fees().accountReserve((*sle)[sfOwnerCount] + 1);
|
ctx.view.fees().accountReserve((*sle)[sfOwnerCount] + 1);
|
||||||
@@ -206,11 +277,55 @@ PayChanCreate::preclaim(PreclaimContext const& ctx)
|
|||||||
if (balance < reserve)
|
if (balance < reserve)
|
||||||
return tecINSUFFICIENT_RESERVE;
|
return tecINSUFFICIENT_RESERVE;
|
||||||
|
|
||||||
if (balance < reserve + ctx.tx[sfAmount])
|
auto const dst = ctx.tx[sfDestination];
|
||||||
|
|
||||||
|
// Check reserve and funds availability
|
||||||
|
if (isXRP(amount) && balance < reserve + ctx.tx[sfAmount])
|
||||||
return tecUNFUNDED;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const dst = ctx.tx[sfDestination];
|
// 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
|
// Check destination account
|
||||||
@@ -241,6 +356,8 @@ PayChanCreate::doApply()
|
|||||||
|
|
||||||
auto const dst = ctx_.tx[sfDestination];
|
auto const dst = ctx_.tx[sfDestination];
|
||||||
|
|
||||||
|
STAmount const amount {ctx_.tx[sfAmount]};
|
||||||
|
|
||||||
// Create PayChan in ledger.
|
// Create PayChan in ledger.
|
||||||
//
|
//
|
||||||
// Note that we we use the value from the sequence or ticket as the
|
// 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
|
// 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);
|
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
|
||||||
ctx_.view().update(sle);
|
ctx_.view().update(sle);
|
||||||
|
|
||||||
@@ -305,7 +451,8 @@ PayChanCreate::doApply()
|
|||||||
TxConsequences
|
TxConsequences
|
||||||
PayChanFund::makeTxConsequences(PreflightContext const& ctx)
|
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
|
NotTEC
|
||||||
@@ -317,7 +464,27 @@ PayChanFund::preflight(PreflightContext const& ctx)
|
|||||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||||
return 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 temBAD_AMOUNT;
|
||||||
|
|
||||||
return preflight2(ctx);
|
return preflight2(ctx);
|
||||||
@@ -331,10 +498,41 @@ PayChanFund::doApply()
|
|||||||
if (!slep)
|
if (!slep)
|
||||||
return tecNO_ENTRY;
|
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];
|
AccountID const src = (*slep)[sfAccount];
|
||||||
auto const txAccount = ctx_.tx[sfAccount];
|
auto const txAccount = ctx_.tx[sfAccount];
|
||||||
auto const expiration = (*slep)[~sfExpiration];
|
auto const expiration = (*slep)[~sfExpiration];
|
||||||
|
|
||||||
{
|
{
|
||||||
auto const cancelAfter = (*slep)[~sfCancelAfter];
|
auto const cancelAfter = (*slep)[~sfCancelAfter];
|
||||||
auto const closeTime =
|
auto const closeTime =
|
||||||
@@ -367,7 +565,13 @@ PayChanFund::doApply()
|
|||||||
if (!sle)
|
if (!sle)
|
||||||
return tefINTERNAL;
|
return tefINTERNAL;
|
||||||
|
|
||||||
|
// do not allow adding funds if dst does not exist
|
||||||
|
if (AccountID const dst = (*slep)[sfDestination];
|
||||||
|
!ctx_.view().read(keylet::account(dst)))
|
||||||
{
|
{
|
||||||
|
return tecNO_DST;
|
||||||
|
}
|
||||||
|
|
||||||
// Check reserve and funds availability
|
// Check reserve and funds availability
|
||||||
auto const balance = (*sle)[sfBalance];
|
auto const balance = (*sle)[sfBalance];
|
||||||
auto const reserve =
|
auto const reserve =
|
||||||
@@ -376,23 +580,41 @@ PayChanFund::doApply()
|
|||||||
if (balance < reserve)
|
if (balance < reserve)
|
||||||
return tecINSUFFICIENT_RESERVE;
|
return tecINSUFFICIENT_RESERVE;
|
||||||
|
|
||||||
if (balance < reserve + ctx_.tx[sfAmount])
|
|
||||||
return tecUNFUNDED;
|
|
||||||
}
|
|
||||||
|
|
||||||
// do not allow adding funds if dst does not exist
|
if (isXRP(amount))
|
||||||
if (AccountID const dst = (*slep)[sfDestination];
|
|
||||||
!ctx_.view().read(keylet::account(dst)))
|
|
||||||
{
|
{
|
||||||
return tecNO_DST;
|
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];
|
(*slep)[sfAmount] = (*slep)[sfAmount] + ctx_.tx[sfAmount];
|
||||||
ctx_.view().update(slep);
|
ctx_.view().update(slep);
|
||||||
|
|
||||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
|
||||||
ctx_.view().update(sle);
|
|
||||||
|
|
||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,12 +627,25 @@ PayChanClaim::preflight(PreflightContext const& ctx)
|
|||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
auto const bal = ctx.tx[~sfBalance];
|
auto const bal = ctx.tx[~sfBalance];
|
||||||
if (bal && (!isXRP(*bal) || *bal <= beast::zero))
|
if (bal)
|
||||||
|
{
|
||||||
|
if (!isXRP(*bal) && !ctx.rules.enabled(featurePaychanAndEscrowForTokens))
|
||||||
return temBAD_AMOUNT;
|
return temBAD_AMOUNT;
|
||||||
|
|
||||||
auto const amt = ctx.tx[~sfAmount];
|
if (*bal <= beast::zero)
|
||||||
if (amt && (!isXRP(*amt) || *amt <= beast::zero))
|
|
||||||
return temBAD_AMOUNT;
|
return temBAD_AMOUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const amt = ctx.tx[~sfAmount];
|
||||||
|
|
||||||
|
if (amt)
|
||||||
|
{
|
||||||
|
if (!isXRP(*amt) && !ctx.rules.enabled(featurePaychanAndEscrowForTokens))
|
||||||
|
return temBAD_AMOUNT;
|
||||||
|
|
||||||
|
if (*amt <= beast::zero)
|
||||||
|
return temBAD_AMOUNT;
|
||||||
|
}
|
||||||
|
|
||||||
if (bal && amt && *bal > *amt)
|
if (bal && amt && *bal > *amt)
|
||||||
return temBAD_AMOUNT;
|
return temBAD_AMOUNT;
|
||||||
@@ -434,8 +669,8 @@ PayChanClaim::preflight(PreflightContext const& ctx)
|
|||||||
// The signature isn't needed if txAccount == src, but if it's
|
// The signature isn't needed if txAccount == src, but if it's
|
||||||
// present, check it
|
// present, check it
|
||||||
|
|
||||||
auto const reqBalance = bal->xrp();
|
auto const reqBalance = *bal;
|
||||||
auto const authAmt = amt ? amt->xrp() : reqBalance;
|
auto const authAmt = *amt ? *amt : reqBalance;
|
||||||
|
|
||||||
if (reqBalance > authAmt)
|
if (reqBalance > authAmt)
|
||||||
return temBAD_AMOUNT;
|
return temBAD_AMOUNT;
|
||||||
@@ -446,7 +681,12 @@ PayChanClaim::preflight(PreflightContext const& ctx)
|
|||||||
|
|
||||||
PublicKey const pk(ctx.tx[sfPublicKey]);
|
PublicKey const pk(ctx.tx[sfPublicKey]);
|
||||||
Serializer msg;
|
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))
|
if (!verify(pk, msg.slice(), *sig, /*canonical*/ true))
|
||||||
return temBAD_SIGNATURE;
|
return temBAD_SIGNATURE;
|
||||||
}
|
}
|
||||||
@@ -482,9 +722,9 @@ PayChanClaim::doApply()
|
|||||||
|
|
||||||
if (ctx_.tx[~sfBalance])
|
if (ctx_.tx[~sfBalance])
|
||||||
{
|
{
|
||||||
auto const chanBalance = slep->getFieldAmount(sfBalance).xrp();
|
auto const chanBalance = slep->getFieldAmount(sfBalance);
|
||||||
auto const chanFunds = slep->getFieldAmount(sfAmount).xrp();
|
auto const chanFunds = slep->getFieldAmount(sfAmount);
|
||||||
auto const reqBalance = ctx_.tx[sfBalance].xrp();
|
auto const reqBalance = ctx_.tx[sfBalance];
|
||||||
|
|
||||||
if (txAccount == dst && !ctx_.tx[~sfSignature])
|
if (txAccount == dst && !ctx_.tx[~sfSignature])
|
||||||
return temBAD_SIGNATURE;
|
return temBAD_SIGNATURE;
|
||||||
@@ -503,7 +743,7 @@ PayChanClaim::doApply()
|
|||||||
// nothing requested
|
// nothing requested
|
||||||
return tecUNFUNDED_PAYMENT;
|
return tecUNFUNDED_PAYMENT;
|
||||||
|
|
||||||
auto const sled = ctx_.view().peek(keylet::account(dst));
|
auto sled = ctx_.view().peek(keylet::account(dst));
|
||||||
if (!sled)
|
if (!sled)
|
||||||
return tecNO_DST;
|
return tecNO_DST;
|
||||||
|
|
||||||
@@ -511,6 +751,7 @@ PayChanClaim::doApply()
|
|||||||
// featureDepositAuth to remove the bug.
|
// featureDepositAuth to remove the bug.
|
||||||
bool const depositAuth{ctx_.view().rules().enabled(featureDepositAuth)};
|
bool const depositAuth{ctx_.view().rules().enabled(featureDepositAuth)};
|
||||||
if (!depositAuth &&
|
if (!depositAuth &&
|
||||||
|
// RH TODO: does this condition need to be changed for IOU paychans?
|
||||||
(txAccount == src && (sled->getFlags() & lsfDisallowXRP)))
|
(txAccount == src && (sled->getFlags() & lsfDisallowXRP)))
|
||||||
return tecNO_TARGET;
|
return tecNO_TARGET;
|
||||||
|
|
||||||
@@ -529,9 +770,38 @@ PayChanClaim::doApply()
|
|||||||
}
|
}
|
||||||
|
|
||||||
(*slep)[sfBalance] = ctx_.tx[sfBalance];
|
(*slep)[sfBalance] = ctx_.tx[sfBalance];
|
||||||
XRPAmount const reqDelta = reqBalance - chanBalance;
|
STAmount const reqDelta = reqBalance - chanBalance;
|
||||||
assert(reqDelta >= beast::zero);
|
assert(reqDelta >= beast::zero);
|
||||||
|
if (isXRP(reqDelta))
|
||||||
(*sled)[sfBalance] = (*sled)[sfBalance] + 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(sled);
|
||||||
ctx_.view().update(slep);
|
ctx_.view().update(slep);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,11 +33,13 @@
|
|||||||
#include <ripple/protocol/STTx.h>
|
#include <ripple/protocol/STTx.h>
|
||||||
#include <ripple/protocol/Serializer.h>
|
#include <ripple/protocol/Serializer.h>
|
||||||
#include <ripple/protocol/TER.h>
|
#include <ripple/protocol/TER.h>
|
||||||
|
#include <ripple/protocol/Feature.h>
|
||||||
|
#include <ripple/basics/Log.h>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <type_traits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
@@ -351,6 +353,7 @@ trustDelete(
|
|||||||
AccountID const& uHighAccountID,
|
AccountID const& uHighAccountID,
|
||||||
beast::Journal j);
|
beast::Journal j);
|
||||||
|
|
||||||
|
|
||||||
/** Delete an offer.
|
/** Delete an offer.
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
@@ -413,6 +416,628 @@ transferXRP(
|
|||||||
STAmount const& amount,
|
STAmount const& amount,
|
||||||
beast::Journal j);
|
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
|
} // namespace ripple
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -254,6 +254,29 @@ accountHolds(
|
|||||||
// Put balance in account terms.
|
// Put balance in account terms.
|
||||||
amount.negate();
|
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);
|
amount.setIssuer(issuer);
|
||||||
}
|
}
|
||||||
JLOG(j.trace()) << "accountHolds:"
|
JLOG(j.trace()) << "accountHolds:"
|
||||||
@@ -889,6 +912,55 @@ trustDelete(
|
|||||||
return tesSUCCESS;
|
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
|
TER
|
||||||
offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j)
|
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
|
// 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
|
// 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.
|
// 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.
|
/** Amendments that this server supports and the default voting behavior.
|
||||||
Whether they are enabled depends on the Rules defined in the validated
|
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 featureExpandedSignerList;
|
||||||
extern uint256 const featureNonFungibleTokensV1;
|
extern uint256 const featureNonFungibleTokensV1;
|
||||||
extern uint256 const fixNFTokenDirV1;
|
extern uint256 const fixNFTokenDirV1;
|
||||||
|
extern uint256 const featurePaychanAndEscrowForTokens;
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,31 @@ serializePayChanAuthorization(
|
|||||||
msg.add64(amt.drops());
|
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
|
} // namespace ripple
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -400,6 +400,7 @@ extern SF_UINT32 const sfMintedNFTokens;
|
|||||||
extern SF_UINT32 const sfBurnedNFTokens;
|
extern SF_UINT32 const sfBurnedNFTokens;
|
||||||
extern SF_UINT32 const sfHookStateCount;
|
extern SF_UINT32 const sfHookStateCount;
|
||||||
extern SF_UINT32 const sfEmitGeneration;
|
extern SF_UINT32 const sfEmitGeneration;
|
||||||
|
extern SF_UINT32 const sfLockCount;
|
||||||
|
|
||||||
// 64-bit integers (common)
|
// 64-bit integers (common)
|
||||||
extern SF_UINT64 const sfIndexNext;
|
extern SF_UINT64 const sfIndexNext;
|
||||||
@@ -479,6 +480,7 @@ extern SF_AMOUNT const sfHighLimit;
|
|||||||
extern SF_AMOUNT const sfFee;
|
extern SF_AMOUNT const sfFee;
|
||||||
extern SF_AMOUNT const sfSendMax;
|
extern SF_AMOUNT const sfSendMax;
|
||||||
extern SF_AMOUNT const sfDeliverMin;
|
extern SF_AMOUNT const sfDeliverMin;
|
||||||
|
extern SF_AMOUNT const sfLockedBalance;
|
||||||
|
|
||||||
// currency amount (uncommon)
|
// currency amount (uncommon)
|
||||||
extern SF_AMOUNT const sfMinimumOffer;
|
extern SF_AMOUNT const sfMinimumOffer;
|
||||||
|
|||||||
@@ -517,6 +517,56 @@ isXRP(STAmount const& amount)
|
|||||||
return isXRP(amount.issue().currency);
|
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
|
// Since `canonicalize` does not have access to a ledger, this is needed to put
|
||||||
// the low-level routine stAmountCanonicalize on an amendment switch. Only
|
// the low-level routine stAmountCanonicalize on an amendment switch. Only
|
||||||
// transactions need to use this switchover. Outside of a transaction it's safe
|
// transactions need to use this switchover. Outside of a transaction it's safe
|
||||||
|
|||||||
@@ -293,6 +293,7 @@ enum TECcodes : TERUnderlyingType {
|
|||||||
tecOBJECT_NOT_FOUND = 160,
|
tecOBJECT_NOT_FOUND = 160,
|
||||||
tecINSUFFICIENT_PAYMENT = 161,
|
tecINSUFFICIENT_PAYMENT = 161,
|
||||||
tecREQUIRES_FLAG = 162,
|
tecREQUIRES_FLAG = 162,
|
||||||
|
tecPRECISION_LOSS = 163,
|
||||||
};
|
};
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -77,6 +77,12 @@ isXRP(Currency const& c)
|
|||||||
return c == beast::zero;
|
return c == beast::zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
isFakeXRP(Currency const& c)
|
||||||
|
{
|
||||||
|
return c == badCurrency();
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns "", "XRP", or three letter ISO code. */
|
/** Returns "", "XRP", or three letter ISO code. */
|
||||||
std::string
|
std::string
|
||||||
to_string(Currency const& c);
|
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(NonFungibleTokensV1, Supported::yes, DefaultVote::no);
|
||||||
REGISTER_FEATURE(ExpandedSignerList, Supported::yes, DefaultVote::no);
|
REGISTER_FEATURE(ExpandedSignerList, Supported::yes, DefaultVote::no);
|
||||||
REGISTER_FIX (fixNFTokenDirV1, 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
|
// The following amendments have been active for at least two years. Their
|
||||||
// pre-amendment code has been removed and the identifiers are deprecated.
|
// pre-amendment code has been removed and the identifiers are deprecated.
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ enum class LedgerNameSpace : std::uint16_t {
|
|||||||
FEE_SETTINGS = 'e',
|
FEE_SETTINGS = 'e',
|
||||||
TICKET = 'T',
|
TICKET = 'T',
|
||||||
SIGNER_LIST = 'S',
|
SIGNER_LIST = 'S',
|
||||||
XRP_PAYMENT_CHANNEL = 'x',
|
PAYMENT_CHANNEL = 'x',
|
||||||
CHECK = 'C',
|
CHECK = 'C',
|
||||||
DEPOSIT_PREAUTH = 'p',
|
DEPOSIT_PREAUTH = 'p',
|
||||||
NEGATIVE_UNL = 'N',
|
NEGATIVE_UNL = 'N',
|
||||||
@@ -369,7 +369,7 @@ payChan(AccountID const& src, AccountID const& dst, UInt32or256 const& seq) noex
|
|||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
ltPAYCHAN,
|
ltPAYCHAN,
|
||||||
indexHash(LedgerNameSpace::XRP_PAYMENT_CHANNEL, src, dst, seq)};
|
indexHash(LedgerNameSpace::PAYMENT_CHANNEL, src, dst, seq)};
|
||||||
}
|
}
|
||||||
|
|
||||||
Keylet
|
Keylet
|
||||||
|
|||||||
@@ -108,6 +108,8 @@ LedgerFormats::LedgerFormats()
|
|||||||
{sfHighNode, soeOPTIONAL},
|
{sfHighNode, soeOPTIONAL},
|
||||||
{sfHighQualityIn, soeOPTIONAL},
|
{sfHighQualityIn, soeOPTIONAL},
|
||||||
{sfHighQualityOut, soeOPTIONAL},
|
{sfHighQualityOut, soeOPTIONAL},
|
||||||
|
{sfLockedBalance, soeOPTIONAL},
|
||||||
|
{sfLockCount, soeOPTIONAL},
|
||||||
},
|
},
|
||||||
commonFields);
|
commonFields);
|
||||||
|
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ CONSTRUCT_TYPED_SFIELD(sfMintedNFTokens, "MintedNFTokens", UINT32,
|
|||||||
CONSTRUCT_TYPED_SFIELD(sfBurnedNFTokens, "BurnedNFTokens", UINT32, 44);
|
CONSTRUCT_TYPED_SFIELD(sfBurnedNFTokens, "BurnedNFTokens", UINT32, 44);
|
||||||
CONSTRUCT_TYPED_SFIELD(sfHookStateCount, "HookStateCount", UINT32, 45);
|
CONSTRUCT_TYPED_SFIELD(sfHookStateCount, "HookStateCount", UINT32, 45);
|
||||||
CONSTRUCT_TYPED_SFIELD(sfEmitGeneration, "EmitGeneration", UINT32, 46);
|
CONSTRUCT_TYPED_SFIELD(sfEmitGeneration, "EmitGeneration", UINT32, 46);
|
||||||
|
CONSTRUCT_TYPED_SFIELD(sfLockCount, "LockCount", UINT32, 47);
|
||||||
|
|
||||||
// 64-bit integers (common)
|
// 64-bit integers (common)
|
||||||
CONSTRUCT_TYPED_SFIELD(sfIndexNext, "IndexNext", UINT64, 1);
|
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(sfDeliveredAmount, "DeliveredAmount", AMOUNT, 18);
|
||||||
CONSTRUCT_TYPED_SFIELD(sfNFTokenBrokerFee, "NFTokenBrokerFee", AMOUNT, 19);
|
CONSTRUCT_TYPED_SFIELD(sfNFTokenBrokerFee, "NFTokenBrokerFee", AMOUNT, 19);
|
||||||
CONSTRUCT_TYPED_SFIELD(sfHookCallbackFee, "HookCallbackFee", AMOUNT, 20);
|
CONSTRUCT_TYPED_SFIELD(sfHookCallbackFee, "HookCallbackFee", AMOUNT, 20);
|
||||||
|
CONSTRUCT_TYPED_SFIELD(sfLockedBalance, "LockedBalance", AMOUNT, 21);
|
||||||
|
|
||||||
// variable length (common)
|
// variable length (common)
|
||||||
CONSTRUCT_TYPED_SFIELD(sfPublicKey, "PublicKey", VL, 1);
|
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(tecOBJECT_NOT_FOUND, "A requested object could not be located."),
|
||||||
MAKE_ERROR(tecINSUFFICIENT_PAYMENT, "The payment is not sufficient."),
|
MAKE_ERROR(tecINSUFFICIENT_PAYMENT, "The payment is not sufficient."),
|
||||||
MAKE_ERROR(tecHOOK_REJECTED, "Rejected by hook on sending or receiving account."),
|
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(tefALREADY, "The exact transaction was already in this ledger."),
|
||||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||||
MAKE_ERROR(tefBAD_AUTH, "Transaction's public key is not authorized."),
|
MAKE_ERROR(tefBAD_AUTH, "Transaction's public key is not authorized."),
|
||||||
|
|||||||
Reference in New Issue
Block a user