Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposed 0.50.0-b1 #1944

Merged
merged 18 commits into from Dec 25, 2016
Merged
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

Add support for tick sizes (RIPD-1363):

Add an amendment to allow gateways to set a "tick size"
for assets they issue. There are no changes unless the
amendment is enabled (since the tick size option cannot
be set).

With the amendment enabled:

AccountSet transactions may set a "TickSize" parameter.
Legal values are 0 and 3-15 inclusive. Zero removes the
setting. 3-15 allow that many decimal digits of precision
in the pricing of offers for assets issued by this account.

For asset pairs with XRP, the tick size imposed, if any,
is the tick size of the issuer of the non-XRP asset. For
asset pairs without XRP, the tick size imposed, if any,
is the smaller of the two issuer's configured tick sizes.

The tick size is imposed by rounding the offer quality
down to nearest tick and recomputing the non-critical
side of the offer. For a buy, the amount offered is
rounded down. For a sell, the amount charged is rounded up.

Gateways must enable a TickSize on their account for this
feature to benefit them.

The primary expected benefit is the elimination of bots
fighting over the tip of the order book. This means:

- Quicker price discovery as outpricing someone by a
  microscopic amount is made impossible. Currently
  bots can spend hours outbidding each other with no
  significant price movement.

- A reduction in offer creation and cancellation spam.

- More offers left on the books as priority means
  something when you can't outbid by a microscopic amount.
  • Loading branch information...
JoelKatz authored and nbougalis committed Dec 5, 2016
commit 22a375a5f460f3617f9c13a33613ab3978b899b6
@@ -48,7 +48,8 @@ supportedAmendments ()
{ "9178256A980A86CF3D70D0260A7DA6402AAFE43632FDBCB88037978404188871 OwnerPaysFee" },
{ "08DE7D96082187F6E6578530258C77FAABABE4C20474BDB82F04B021F1A68647 PayChan" },
{ "740352F2412A9909880C23A559FCECEDA3BE2126FED62FC7660D628A06927F11 Flow" },
{ "1562511F573A19AE9BD103B5D6B9E01B3B46805AEC5D3C4805C902B514399146 CryptoConditions" }
{ "1562511F573A19AE9BD103B5D6B9E01B3B46805AEC5D3C4805C902B514399146 CryptoConditions" },
{ "532651B4FD58DF8922A49BA101AB3E996E5BFBF95A913B3E392504863E63B164 TickSize" }
};
}

@@ -23,6 +23,7 @@
#include <ripple/app/ledger/OrderBookDB.h>
#include <ripple/basics/contract.h>
#include <ripple/protocol/st.h>
#include <ripple/protocol/Quality.h>
#include <ripple/basics/Log.h>
#include <ripple/json/to_string.h>
#include <ripple/ledger/Sandbox.h>
@@ -680,7 +681,7 @@ CreateOffer::applyGuts (ApplyView& view, ApplyView& view_cancel)
// This is the original rate of the offer, and is the rate at which
// it will be placed, even if crossing offers change the amounts that
// end up on the books.
auto const uRate = getRate (saTakerGets, saTakerPays);
auto uRate = getRate (saTakerGets, saTakerPays);

auto viewJ = ctx_.app.journal("View");

@@ -722,6 +723,58 @@ CreateOffer::applyGuts (ApplyView& view, ApplyView& view_cancel)

if (result == tesSUCCESS)
{
// If a tick size applies, round the offer to the tick size
auto const& uPaysIssuerID = saTakerPays.getIssuer ();
auto const& uGetsIssuerID = saTakerGets.getIssuer ();

std::uint8_t uTickSize = Quality::maxTickSize;
if (!isXRP (uPaysIssuerID))
{
auto const sle =
view.read(keylet::account(uPaysIssuerID));
if (sle && sle->isFieldPresent (sfTickSize))
uTickSize = std::min (uTickSize,
(*sle)[sfTickSize]);
}
if (!isXRP (uGetsIssuerID))
{
auto const sle =
view.read(keylet::account(uGetsIssuerID));
if (sle && sle->isFieldPresent (sfTickSize))
uTickSize = std::min (uTickSize,
(*sle)[sfTickSize]);
}
if (uTickSize < Quality::maxTickSize)
{
auto const rate =
Quality{saTakerGets, saTakerPays}.round
(uTickSize).rate();

// We round the side that's not exact,
// just as if the offer happened to execute
// at a slightly better (for the placer) rate
if (bSell)
{
// this is a sell, round taker pays
saTakerPays = multiply (
saTakerGets, rate, saTakerPays.issue());
}
else
{
// this is a buy, round taker gets
saTakerGets = divide (
saTakerPays, rate, saTakerGets.issue());
}
if (! saTakerGets || ! saTakerPays)
{
JLOG (j_.debug()) <<
"Offer rounded to zero";
return { result, true };
}

uRate = getRate (saTakerGets, saTakerPays);
}

// We reverse pays and gets because during crossing we are taking.
Amounts const taker_amount (saTakerGets, saTakerPays);

@@ -124,6 +124,23 @@ SetAccount::preflight (PreflightContext const& ctx)
}
}

// TickSize
if (tx.isFieldPresent (sfTickSize))
{
if (!ctx.rules.enabled(featureTickSize,
ctx.app.config().features))
return temDISABLED;

auto uTickSize = tx[sfTickSize];
if (uTickSize &&
((uTickSize < Quality::minTickSize) ||
(uTickSize > Quality::maxTickSize)))
{
JLOG(j.trace()) << "Malformed transaction: Bad tick size.";
return temBAD_TICK_SIZE;
}
}

if (auto const mk = tx[~sfMessageKey])
{
if (mk->size() && ! publicKeyType ({mk->data(), mk->size()}))
@@ -445,6 +462,24 @@ SetAccount::doApply ()
}
}

//
// TickSize
//
if (ctx_.tx.isFieldPresent (sfTickSize))
{
auto uTickSize = ctx_.tx[sfTickSize];
if ((uTickSize == 0) || (uTickSize == Quality::maxTickSize))
{
JLOG(j_.trace()) << "unset tick size";
sle->makeFieldAbsent (sfTickSize);
}
else
{
JLOG(j_.trace()) << "set tick size";
sle->setFieldU8 (sfTickSize, uTickSize);
}
}

if (uFlagsIn != uFlagsOut)
sle->setFieldU32 (sfFlags, uFlagsOut);

@@ -45,6 +45,7 @@ extern uint256 const featureSHAMapV2;
extern uint256 const featurePayChan;
extern uint256 const featureFlow;
extern uint256 const featureCryptoConditions;
extern uint256 const featureTickSize;

} // ripple

@@ -124,6 +124,9 @@ class Quality
// have lower unsigned integer representations.
using value_type = std::uint64_t;

static const int minTickSize = 3;
static const int maxTickSize = 16;

private:
value_type m_value;

@@ -170,6 +173,12 @@ class Quality
return amountFromQuality (m_value);
}

/** Returns the quality rounded up to the specified number
of decimal digits.
*/
Quality
round (int tickSize) const;

/** Returns the scaled amount with in capped.
Math is avoided if the result is exact. The output is clamped
to prevent money creation.
@@ -330,6 +330,7 @@ extern SField const sfMetadata;
extern SF_U8 const sfCloseResolution;
extern SF_U8 const sfMethod;
extern SF_U8 const sfTransactionResult;
extern SF_U8 const sfTickSize;

// 16-bit integers
extern SF_U16 const sfLedgerEntryType;
@@ -85,6 +85,7 @@ enum TER
temBAD_SIGNER,
temBAD_QUORUM,
temBAD_WEIGHT,
temBAD_TICK_SIZE,

// An intermediate result used internally, should never be returned.
temUNCERTAIN,
@@ -56,5 +56,6 @@ uint256 const featureSHAMapV2 = feature("SHAMapV2");
uint256 const featurePayChan = feature("PayChan");
uint256 const featureFlow = feature("Flow");
uint256 const featureCryptoConditions = feature("CryptoConditions");
uint256 const featureTickSize = feature("TickSize");

} // ripple
@@ -39,6 +39,7 @@ LedgerFormats::LedgerFormats ()
<< SOElement (sfMessageKey, SOE_OPTIONAL)
<< SOElement (sfTransferRate, SOE_OPTIONAL)
<< SOElement (sfDomain, SOE_OPTIONAL)
<< SOElement (sfTickSize, SOE_OPTIONAL)
;

add ("DirectoryNode", ltDIR_NODE)
@@ -120,4 +120,36 @@ composed_quality (Quality const& lhs, Quality const& rhs)
return Quality ((stored_exponent << (64 - 8)) | stored_mantissa);
}

Quality
Quality::round (int digits) const
{
// Modulus for mantissa
static const std::uint64_t mod[17] = {
/* 0 */ 10000000000000000,
/* 1 */ 1000000000000000,
/* 2 */ 100000000000000,
/* 3 */ 10000000000000,
/* 4 */ 1000000000000,
/* 5 */ 100000000000,
/* 6 */ 10000000000,
/* 7 */ 1000000000,
/* 8 */ 100000000,
/* 9 */ 10000000,
/* 10 */ 1000000,
/* 11 */ 100000,
/* 12 */ 10000,
/* 13 */ 1000,
/* 14 */ 100,
/* 15 */ 10,
/* 16 */ 1,
};

auto exponent = m_value >> (64 - 8);
auto mantissa = m_value & 0x00ffffffffffffffULL;
mantissa += mod[digits] - 1;
mantissa -= (mantissa % mod[digits]);

return Quality{(exponent << (64 - 8)) | mantissa};
}

}
@@ -82,6 +82,9 @@ SF_U8 const sfCloseResolution = make::one<SF_U8::type>(&sfCloseResolution, S
SF_U8 const sfMethod = make::one<SF_U8::type>(&sfMethod, STI_UINT8, 2, "Method");
SF_U8 const sfTransactionResult = make::one<SF_U8::type>(&sfTransactionResult, STI_UINT8, 3, "TransactionResult");

// 8-bit integers (uncommon)
SF_U8 const sfTickSize = make::one<SF_U8::type>(&sfTickSize, STI_UINT8, 16, "TickSize");

// 16-bit integers
SF_U16 const sfLedgerEntryType = make::one<SF_U16::type>(&sfLedgerEntryType, STI_UINT16, 1, "LedgerEntryType", SField::sMD_Never);
SF_U16 const sfTransactionType = make::one<SF_U16::type>(&sfTransactionType, STI_UINT16, 2, "TransactionType");
@@ -123,6 +123,7 @@ bool transResultInfo (TER code, std::string& token, std::string& text)
{ temUNCERTAIN, { "temUNCERTAIN", "In process of determining result. Never returned." } },
{ temUNKNOWN, { "temUNKNOWN", "The transaction requires logic that is not implemented yet." } },
{ temDISABLED, { "temDISABLED", "The transaction requires logic that is currently disabled." } },
{ temBAD_TICK_SIZE, { "temBAD_TICK_SIZE", "Malformed: Tick size out of range." } },

{ terRETRY, { "terRETRY", "Retry transaction." } },
{ terFUNDS_SPENT, { "terFUNDS_SPENT", "Can't set password, password set funds already spent." } },
@@ -33,6 +33,7 @@ TxFormats::TxFormats ()
<< SOElement (sfTransferRate, SOE_OPTIONAL)
<< SOElement (sfSetFlag, SOE_OPTIONAL)
<< SOElement (sfClearFlag, SOE_OPTIONAL)
<< SOElement (sfTickSize, SOE_OPTIONAL)
;

add ("TrustSet", ttTRUST_SET)
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.