Skip to content

Commit

Permalink
Add support for tick sizes (RIPD-1363):
Browse files Browse the repository at this point in the history
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 23, 2016
1 parent 3337d17 commit 22a375a
Show file tree
Hide file tree
Showing 15 changed files with 288 additions and 2 deletions.
3 changes: 2 additions & 1 deletion src/ripple/app/main/Amendments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ supportedAmendments ()
{ "9178256A980A86CF3D70D0260A7DA6402AAFE43632FDBCB88037978404188871 OwnerPaysFee" },
{ "08DE7D96082187F6E6578530258C77FAABABE4C20474BDB82F04B021F1A68647 PayChan" },
{ "740352F2412A9909880C23A559FCECEDA3BE2126FED62FC7660D628A06927F11 Flow" },
{ "1562511F573A19AE9BD103B5D6B9E01B3B46805AEC5D3C4805C902B514399146 CryptoConditions" }
{ "1562511F573A19AE9BD103B5D6B9E01B3B46805AEC5D3C4805C902B514399146 CryptoConditions" },
{ "532651B4FD58DF8922A49BA101AB3E996E5BFBF95A913B3E392504863E63B164 TickSize" }
};
}

Expand Down
55 changes: 54 additions & 1 deletion src/ripple/app/tx/impl/CreateOffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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");

Expand Down Expand Up @@ -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);

Expand Down
35 changes: 35 additions & 0 deletions src/ripple/app/tx/impl/SetAccount.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()}))
Expand Down Expand Up @@ -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);

Expand Down
1 change: 1 addition & 0 deletions src/ripple/protocol/Feature.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 9 additions & 0 deletions src/ripple/protocol/Quality.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/ripple/protocol/SField.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/ripple/protocol/TER.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/ripple/protocol/impl/Feature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions src/ripple/protocol/impl/LedgerFormats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
32 changes: 32 additions & 0 deletions src/ripple/protocol/impl/Quality.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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};
}

}
3 changes: 3 additions & 0 deletions src/ripple/protocol/impl/SField.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
1 change: 1 addition & 0 deletions src/ripple/protocol/impl/TER.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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." } },
Expand Down
1 change: 1 addition & 0 deletions src/ripple/protocol/impl/TxFormats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 22a375a

Please sign in to comment.