Skip to content

Commit

Permalink
Clawback support
Browse files Browse the repository at this point in the history
  • Loading branch information
shawnxie999 committed May 31, 2023
1 parent 3620ac2 commit eeeb90c
Show file tree
Hide file tree
Showing 17 changed files with 1,294 additions and 1 deletion.
2 changes: 2 additions & 0 deletions Builds/CMake/RippledCore.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ target_sources (rippled PRIVATE
src/ripple/app/tx/impl/CancelOffer.cpp
src/ripple/app/tx/impl/CashCheck.cpp
src/ripple/app/tx/impl/Change.cpp
src/ripple/app/tx/impl/Clawback.cpp
src/ripple/app/tx/impl/CreateCheck.cpp
src/ripple/app/tx/impl/CreateOffer.cpp
src/ripple/app/tx/impl/CreateTicket.cpp
Expand Down Expand Up @@ -692,6 +693,7 @@ if (tests)
src/test/app/AccountTxPaging_test.cpp
src/test/app/AmendmentTable_test.cpp
src/test/app/Check_test.cpp
src/test/app/Clawback_test.cpp
src/test/app/CrossingLimits_test.cpp
src/test/app/DeliverMin_test.cpp
src/test/app/DepositAuth_test.cpp
Expand Down
173 changes: 173 additions & 0 deletions src/ripple/app/tx/impl/Clawback.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2023 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================

#include <ripple/app/tx/impl/Clawback.h>
#include <ripple/ledger/Directory.h>
#include <ripple/ledger/View.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Protocol.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/st.h>
#include <boost/endian/conversion.hpp>
#include <array>

namespace ripple {

NotTEC
Clawback::preflight(PreflightContext const& ctx)
{
if (!ctx.rules.enabled(featureClawback))
return temDISABLED;

if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;

if (ctx.tx.getFlags() & tfClawbackMask)
return temINVALID_FLAG;

AccountID const issuer = ctx.tx.getAccountID(sfAccount);
STAmount const clawAmount(ctx.tx.getFieldAmount(sfAmount));

// The issuer field is used for the token holder instead
AccountID const holder = clawAmount.getIssuer();

if (issuer == holder || isXRP(clawAmount) || clawAmount <= beast::zero)
return temBAD_AMOUNT;

return preflight2(ctx);
}

TER
Clawback::preclaim(PreclaimContext const& ctx)
{
AccountID const issuer = ctx.tx.getAccountID(sfAccount);
STAmount const clawAmount(ctx.tx.getFieldAmount(sfAmount));
AccountID const holder = clawAmount.getIssuer();

auto const sleIssuer = ctx.view.read(keylet::account(issuer));
auto const sleHolder = ctx.view.read(keylet::account(holder));
if (!sleIssuer || !sleHolder)
return terNO_ACCOUNT;

std::uint32_t const issuerFlagsIn = sleIssuer->getFieldU32(sfFlags);

// If AllowClawback is not set or NoFreeze is set, return no permission
if (!(issuerFlagsIn & lsfAllowClawback) || (issuerFlagsIn & lsfNoFreeze))
return tecNO_PERMISSION;

auto const sleRippleState =
ctx.view.read(keylet::line(holder, issuer, clawAmount.getCurrency()));
if (!sleRippleState)
return tecNO_LINE;

STAmount const balance = sleRippleState->getFieldAmount(sfBalance);

// If balance is positive, issuer must have higher address than holder
if (balance > beast::zero && issuer < holder)
return tecNO_PERMISSION;

// If balance is negative, issuer must have lower address than holder
if (balance < beast::zero && issuer > holder)
return tecNO_PERMISSION;

// At this point, we know that issuer and holder accounts
// are correct and a trustline exists between them.
//
// Must now explicitly check the balance to make sure
// available balance is non-zero.
//
// We can't directly check the balance of trustline because
// the available balance of a trustline is prone to new changes (eg.
// XLS-34). So we must use `accountHolds`.
if (accountHolds(
ctx.view,
holder,
clawAmount.getCurrency(),
issuer,
fhIGNORE_FREEZE,
ctx.j) <= beast::zero)
return tecNO_LINE;

return tesSUCCESS;
}

TER
Clawback::clawback(
AccountID const& issuer,
AccountID const& holder,
STAmount const& amount)
{
// This should never happen
if (amount <= beast::zero || issuer != amount.getIssuer())
return tecINTERNAL;

STAmount const holderBalance = accountHolds(
view(),
holder,
amount.getCurrency(),
amount.getIssuer(),
fhIGNORE_FREEZE,
j_);

// The amount to be clawed back can never exceed the amount that
// the token holder current owns
if (amount > holderBalance)
return tecINTERNAL;

auto const result = accountSend(view(), holder, issuer, amount, j_);

if (!isTesSuccess(result))
return result;

if (accountHolds(
view(),
holder,
amount.getCurrency(),
amount.getIssuer(),
fhIGNORE_FREEZE,
j_)
.signum() < 0)
return tecINTERNAL;

return tesSUCCESS;
}

TER
Clawback::doApply()
{
AccountID const issuer = account_;
STAmount clawAmount(ctx_.tx.getFieldAmount(sfAmount));
AccountID const holder = clawAmount.getIssuer();

// Replace the `issuer` field with holder's account
clawAmount.setIssuer(issuer);

// Get the spendable balance. Must use `accountHolds`.
STAmount const spendableAmount = accountHolds(
view(),
holder,
clawAmount.getCurrency(),
clawAmount.getIssuer(),
fhIGNORE_FREEZE,
j_);

return clawback(issuer, holder, std::min(spendableAmount, clawAmount));
}

} // namespace ripple
55 changes: 55 additions & 0 deletions src/ripple/app/tx/impl/Clawback.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2023 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================

#ifndef RIPPLE_TX_CLAWBACK_H_INCLUDED
#define RIPPLE_TX_CLAWBACK_H_INCLUDED

#include <ripple/app/tx/impl/Transactor.h>

namespace ripple {

class Clawback : public Transactor
{
private:
TER
clawback(
AccountID const& issuer,
AccountID const& holder,
STAmount const& amount);

public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};

explicit Clawback(ApplyContext& ctx) : Transactor(ctx)
{
}

static NotTEC
preflight(PreflightContext const& ctx);

static TER
preclaim(PreclaimContext const& ctx);

TER
doApply() override;
};

} // namespace ripple

#endif
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 @@ -218,6 +218,25 @@ SetAccount::preclaim(PreclaimContext const& ctx)
}
}

//
// Clawback
//
if (ctx.view.rules().enabled(featureClawback) &&
(uSetFlag == asfAllowClawback))
{
if (uFlagsIn & lsfNoFreeze)
{
JLOG(ctx.j.trace()) << "Can't set Clawback if NoFreeze is set";
return tecNO_PERMISSION;
}

if (!dirIsEmpty(ctx.view, keylet::ownerDir(id)))
{
JLOG(ctx.j.trace()) << "Owner directory not empty.";
return tecOWNERS;
}
}

return tesSUCCESS;
}

Expand Down Expand Up @@ -361,6 +380,14 @@ SetAccount::doApply()
return tecNEED_MASTER_KEY;
}

// Cannot set NoFreeze if clawback is enabled
if (ctx_.view().rules().enabled(featureClawback) &&
(uFlagsIn & lsfAllowClawback))
{
JLOG(j_.trace()) << "Can't set NoFreeze if clawback is enabled";
return tecNO_PERMISSION;
}

JLOG(j_.trace()) << "Set NoFreeze flag";
uFlagsOut |= lsfNoFreeze;
}
Expand Down Expand Up @@ -562,6 +589,14 @@ SetAccount::doApply()
uFlagsOut &= ~lsfDisallowIncomingTrustline;
}

// Set flag for clawback
if (ctx_.view().rules().enabled(featureClawback) &&
uSetFlag == asfAllowClawback)
{
JLOG(j_.trace()) << "set allow clawback";
uFlagsOut |= lsfAllowClawback;
}

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

Expand Down
11 changes: 11 additions & 0 deletions src/ripple/app/tx/impl/applySteps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <ripple/app/tx/impl/CancelOffer.h>
#include <ripple/app/tx/impl/CashCheck.h>
#include <ripple/app/tx/impl/Change.h>
#include <ripple/app/tx/impl/Clawback.h>
#include <ripple/app/tx/impl/CreateCheck.h>
#include <ripple/app/tx/impl/CreateOffer.h>
#include <ripple/app/tx/impl/CreateTicket.h>
Expand Down Expand Up @@ -147,6 +148,8 @@ invoke_preflight(PreflightContext const& ctx)
return invoke_preflight_helper<NFTokenCancelOffer>(ctx);
case ttNFTOKEN_ACCEPT_OFFER:
return invoke_preflight_helper<NFTokenAcceptOffer>(ctx);
case ttCLAWBACK:
return invoke_preflight_helper<Clawback>(ctx);
default:
assert(false);
return {temUNKNOWN, TxConsequences{temUNKNOWN}};
Expand Down Expand Up @@ -248,6 +251,8 @@ invoke_preclaim(PreclaimContext const& ctx)
return invoke_preclaim<NFTokenCancelOffer>(ctx);
case ttNFTOKEN_ACCEPT_OFFER:
return invoke_preclaim<NFTokenAcceptOffer>(ctx);
case ttCLAWBACK:
return invoke_preclaim<Clawback>(ctx);
default:
assert(false);
return temUNKNOWN;
Expand Down Expand Up @@ -311,6 +316,8 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
return NFTokenCancelOffer::calculateBaseFee(view, tx);
case ttNFTOKEN_ACCEPT_OFFER:
return NFTokenAcceptOffer::calculateBaseFee(view, tx);
case ttCLAWBACK:
return Clawback::calculateBaseFee(view, tx);
default:
assert(false);
return XRPAmount{0};
Expand Down Expand Up @@ -463,6 +470,10 @@ invoke_apply(ApplyContext& ctx)
NFTokenAcceptOffer p(ctx);
return p();
}
case ttCLAWBACK: {
Clawback p(ctx);
return p();
}
default:
assert(false);
return {temUNKNOWN, false};
Expand Down
3 changes: 2 additions & 1 deletion src/ripple/protocol/Feature.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ namespace detail {
// Feature.cpp. Because it's only used to reserve storage, and determine how
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
// the actual number of amendments. A LogicError on startup will verify this.
static constexpr std::size_t numFeatures = 58;
static constexpr std::size_t numFeatures = 59;

/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated
Expand Down Expand Up @@ -345,6 +345,7 @@ extern uint256 const featureXRPFees;
extern uint256 const fixUniversalNumber;
extern uint256 const fixNonFungibleTokensV1_2;
extern uint256 const fixNFTokenRemint;
extern uint256 const featureClawback;

} // namespace ripple

Expand Down
5 changes: 5 additions & 0 deletions src/ripple/protocol/LedgerFormats.h
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,11 @@ enum LedgerSpecificFlags {
0x10000000, // True, reject new paychans
lsfDisallowIncomingTrustline =
0x20000000, // True, reject new trustlines (only if no issued assets)
/* // reserved for AMM amendment
lsfAMM = 0x40000000, // True, AMM account
*/
lsfAllowClawback =
0x80000000, // True, enable clawback

// ltOFFER
lsfPassive = 0x00010000,
Expand Down
4 changes: 4 additions & 0 deletions src/ripple/protocol/TxFlags.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ constexpr std::uint32_t asfDisallowIncomingNFTokenOffer = 12;
constexpr std::uint32_t asfDisallowIncomingCheck = 13;
constexpr std::uint32_t asfDisallowIncomingPayChan = 14;
constexpr std::uint32_t asfDisallowIncomingTrustline = 15;
constexpr std::uint32_t asfAllowClawback = 16;

// OfferCreate flags:
constexpr std::uint32_t tfPassive = 0x00010000;
Expand Down Expand Up @@ -159,6 +160,9 @@ constexpr std::uint32_t const tfNFTokenCancelOfferMask = ~(tfUniversal);
// NFTokenAcceptOffer flags:
constexpr std::uint32_t const tfNFTokenAcceptOfferMask = ~tfUniversal;

// Clawback flags:
constexpr std::uint32_t const tfClawbackMask = ~tfUniversal;

// clang-format on

} // namespace ripple
Expand Down
Loading

0 comments on commit eeeb90c

Please sign in to comment.