mirror of
https://github.com/XRPLF/rippled.git
synced 2026-02-10 17:02:29 +00:00
extend escrow to psuedo and blackholed
- allow LP tokens - allow blackholed accounts
This commit is contained in:
@@ -653,7 +653,8 @@ pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey);
|
||||
createPseudoAccount(
|
||||
ApplyView& view,
|
||||
uint256 const& pseudoOwnerKey,
|
||||
SField const& ownerField);
|
||||
SField const& ownerField,
|
||||
std::uint32_t additionalFlags = 0);
|
||||
|
||||
// Returns true iff sleAcct is a pseudo-account or specific
|
||||
// pseudo-accounts in pseudoFieldFilter.
|
||||
@@ -688,6 +689,13 @@ isPseudoAccount(
|
||||
view.read(keylet::account(accountId)), pseudoFieldFilter);
|
||||
}
|
||||
|
||||
// Returns true if the account is blackholed:
|
||||
// - lsfDisableMaster is set
|
||||
// - Regular key (if present) is AccountID(0), AccountID(1), or AccountID(2)
|
||||
// - No signer list exists
|
||||
[[nodiscard]] bool
|
||||
isBlackholed(ReadView const& view, std::shared_ptr<SLE const> const& sle);
|
||||
|
||||
[[nodiscard]] TER
|
||||
canAddHolding(ReadView const& view, Asset const& asset);
|
||||
|
||||
|
||||
@@ -1242,11 +1242,44 @@ isPseudoAccount(
|
||||
}) > 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isBlackholed(ReadView const& view, std::shared_ptr<SLE const> const& sle)
|
||||
{
|
||||
if (!sle || sle->getType() != ltACCOUNT_ROOT)
|
||||
return false;
|
||||
|
||||
// If master key is not disabled, not blackholed
|
||||
if (!sle->isFlag(lsfDisableMaster))
|
||||
return false;
|
||||
|
||||
// If a regular key is set, it must be AccountID(0), AccountID(1), or
|
||||
// AccountID(2) for the account to be blackholed
|
||||
if (sle->isFieldPresent(sfRegularKey))
|
||||
{
|
||||
AccountID const rk = sle->getAccountID(sfRegularKey);
|
||||
static AccountID const ACCOUNT_ZERO(0);
|
||||
static AccountID const ACCOUNT_ONE(1);
|
||||
static AccountID const ACCOUNT_TWO(2);
|
||||
|
||||
if (rk != ACCOUNT_ZERO && rk != ACCOUNT_ONE && rk != ACCOUNT_TWO)
|
||||
return false;
|
||||
}
|
||||
|
||||
// If a signer list is set, not blackholed
|
||||
AccountID const account = sle->getAccountID(sfAccount);
|
||||
if (view.exists(keylet::signers(account)))
|
||||
return false;
|
||||
|
||||
// All conditions met: account is blackholed
|
||||
return true;
|
||||
}
|
||||
|
||||
Expected<std::shared_ptr<SLE>, TER>
|
||||
createPseudoAccount(
|
||||
ApplyView& view,
|
||||
uint256 const& pseudoOwnerKey,
|
||||
SField const& ownerField)
|
||||
SField const& ownerField,
|
||||
std::uint32_t additionalFlags)
|
||||
{
|
||||
[[maybe_unused]]
|
||||
auto const& fields = getPseudoAccountFields();
|
||||
@@ -1281,7 +1314,8 @@ createPseudoAccount(
|
||||
// rippling, and enable deposit authorization to prevent payments into
|
||||
// pseudo-account.
|
||||
account->setFieldU32(
|
||||
sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
|
||||
sfFlags,
|
||||
lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth | additionalFlags);
|
||||
// Link the pseudo-account with its owner object.
|
||||
account->setFieldH256(ownerField, pseudoOwnerKey);
|
||||
|
||||
|
||||
@@ -130,6 +130,48 @@ private:
|
||||
BEAST_EXPECT(
|
||||
STIssue(sfAsset, STAmount(XRP(2'000)).issue()) !=
|
||||
STIssue(sfAsset, STAmount(USD(2'000)).issue()));
|
||||
|
||||
// AMM account flags with fixTokenEscrowV1_1
|
||||
// New AMM accounts should have lsfAllowTrustLineLocking set
|
||||
{
|
||||
Env env{*this, testable_amendments()};
|
||||
fund(env, gw, {alice}, {USD(20'000)}, Fund::All);
|
||||
AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
|
||||
auto const ammAccount = ammAlice.ammAccount();
|
||||
|
||||
// Check that AMM account has expected flags
|
||||
auto const sleAMM = env.le(keylet::account(ammAccount));
|
||||
BEAST_EXPECT(sleAMM);
|
||||
if (sleAMM)
|
||||
{
|
||||
// Base pseudo-account flags
|
||||
BEAST_EXPECT(sleAMM->isFlag(lsfDisableMaster));
|
||||
BEAST_EXPECT(sleAMM->isFlag(lsfDefaultRipple));
|
||||
BEAST_EXPECT(sleAMM->isFlag(lsfDepositAuth));
|
||||
BEAST_EXPECT(sleAMM->isFlag(lsfAllowTrustLineLocking));
|
||||
}
|
||||
}
|
||||
|
||||
// AMM account flags without fixTokenEscrowV1_1
|
||||
// New AMM accounts should NOT have lsfAllowTrustLineLocking
|
||||
{
|
||||
Env env{*this, testable_amendments() - fixTokenEscrowV1_1};
|
||||
fund(env, gw, {alice}, {USD(20'000)}, Fund::All);
|
||||
AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
|
||||
auto const ammAccount = ammAlice.ammAccount();
|
||||
|
||||
// Check that AMM account has expected flags
|
||||
auto const sleAMM = env.le(keylet::account(ammAccount));
|
||||
BEAST_EXPECT(sleAMM);
|
||||
if (sleAMM)
|
||||
{
|
||||
// Base pseudo-account flags
|
||||
BEAST_EXPECT(sleAMM->isFlag(lsfDisableMaster));
|
||||
BEAST_EXPECT(sleAMM->isFlag(lsfDefaultRipple));
|
||||
BEAST_EXPECT(sleAMM->isFlag(lsfDepositAuth));
|
||||
BEAST_EXPECT(!sleAMM->isFlag(lsfAllowTrustLineLocking));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/AMM.h>
|
||||
|
||||
#include <xrpld/app/tx/applySteps.h>
|
||||
|
||||
@@ -359,6 +360,97 @@ struct EscrowToken_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
}
|
||||
|
||||
// AMM issuer without asfAllowTrustLineLocking
|
||||
// (succeeds under fixTokenEscrowV1_1, fails otherwise)
|
||||
{
|
||||
bool const withFix = features[fixTokenEscrowV1_1];
|
||||
Env env{*this, features};
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const carol = Account("carol");
|
||||
auto const gw = Account{"gateway"};
|
||||
|
||||
env.fund(XRP(30'000), alice, bob, carol, gw);
|
||||
env.close();
|
||||
|
||||
// Create an AMM pool (XRP/USD)
|
||||
auto const USD = gw["USD"];
|
||||
env.trust(USD(30'000), alice);
|
||||
env.close();
|
||||
env(pay(gw, alice, USD(10'000)));
|
||||
env.close();
|
||||
|
||||
AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
|
||||
auto const ammAccount = ammAlice.ammAccount();
|
||||
auto const lpIssue = ammAlice.lptIssue();
|
||||
|
||||
// Carol gets LP tokens
|
||||
env.trust(STAmount{lpIssue, 10'000}, carol);
|
||||
env.close();
|
||||
env(pay(alice, carol, STAmount{lpIssue, 100}));
|
||||
env.close();
|
||||
|
||||
// Try to create escrow with AMM account as issuer (LP tokens)
|
||||
env(escrow::create(carol, bob, STAmount{lpIssue, 50}),
|
||||
escrow::finish_time(env.now() + 1s),
|
||||
fee(baseFee * 150),
|
||||
ter(withFix ? TER(tesSUCCESS) : TER(tecNO_PERMISSION)));
|
||||
env.close();
|
||||
|
||||
if (withFix)
|
||||
{
|
||||
// Verify the AMM account now has lsfAllowTrustLineLocking
|
||||
auto const sleAMM = env.le(keylet::account(ammAccount));
|
||||
BEAST_EXPECT(
|
||||
sleAMM && sleAMM->isFlag(lsfAllowTrustLineLocking));
|
||||
}
|
||||
}
|
||||
|
||||
// Blackholed issuer without asfAllowTrustLineLocking
|
||||
// (succeeds under fixTokenEscrowV1_1, fails otherwise)
|
||||
{
|
||||
bool const withFix = features[fixTokenEscrowV1_1];
|
||||
Env env{*this, features};
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const gw = Account{"gateway"};
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
env.fund(XRP(5000), alice, bob, gw);
|
||||
env.close();
|
||||
env.trust(USD(10'000), alice, bob);
|
||||
env.close();
|
||||
env(pay(gw, alice, USD(5000)));
|
||||
env(pay(gw, bob, USD(5000)));
|
||||
env.close();
|
||||
|
||||
// Blackhole the gateway: set regular key to AccountID(1)
|
||||
// (noAccount), then disable master key. AccountID(1) is a
|
||||
// non-functional account that nobody can sign with.
|
||||
Account const blackhole("blackhole", AccountID(1));
|
||||
env(regkey(gw, blackhole));
|
||||
env.close();
|
||||
// Disable master key (must use master key to disable it)
|
||||
env(fset(gw, asfDisableMaster), sig(gw));
|
||||
env.close();
|
||||
|
||||
// Try to create escrow with blackholed issuer
|
||||
env(escrow::create(alice, bob, USD(100)),
|
||||
escrow::finish_time(env.now() + 1s),
|
||||
fee(baseFee * 150),
|
||||
ter(withFix ? TER(tesSUCCESS) : TER(tecNO_PERMISSION)));
|
||||
env.close();
|
||||
|
||||
if (withFix)
|
||||
{
|
||||
// Verify the gateway now has lsfAllowTrustLineLocking
|
||||
auto const sleGW = env.le(keylet::account(gw));
|
||||
BEAST_EXPECT(sleGW && sleGW->isFlag(lsfAllowTrustLineLocking));
|
||||
}
|
||||
}
|
||||
|
||||
// tecNO_LINE: account does not have a trustline to the issuer
|
||||
{
|
||||
Env env{*this, features};
|
||||
@@ -4089,6 +4181,7 @@ public:
|
||||
testIOUWithFeats(all - fixTokenEscrowV1_1);
|
||||
testMPTWithFeats(all);
|
||||
testMPTWithFeats(all - fixTokenEscrowV1);
|
||||
testMPTWithFeats(all - fixTokenEscrowV1 - fixTokenEscrowV1_1);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -202,7 +202,10 @@ applyCreate(
|
||||
auto const ammKeylet = keylet::amm(amount.issue(), amount2.issue());
|
||||
|
||||
// Mitigate same account exists possibility
|
||||
auto const maybeAccount = createPseudoAccount(sb, ammKeylet.key, sfAMMID);
|
||||
std::uint32_t const additionalFlags =
|
||||
sb.rules().enabled(fixTokenEscrowV1_1) ? lsfAllowTrustLineLocking : 0;
|
||||
auto const maybeAccount =
|
||||
createPseudoAccount(sb, ammKeylet.key, sfAMMID, additionalFlags);
|
||||
// AMM account already exists (should not happen)
|
||||
if (!maybeAccount)
|
||||
{
|
||||
|
||||
@@ -184,7 +184,21 @@ escrowCreatePreclaimHelper<Issue>(
|
||||
if (!sleIssuer)
|
||||
return tecNO_ISSUER;
|
||||
if (!sleIssuer->isFlag(lsfAllowTrustLineLocking))
|
||||
return tecNO_PERMISSION;
|
||||
{
|
||||
// fixTokenEscrowV1_1: allows escrow for AMM pseudo accounts and
|
||||
// blackholed issuers
|
||||
if (ctx.view.rules().enabled(fixTokenEscrowV1_1))
|
||||
{
|
||||
bool const isAMM = isPseudoAccount(sleIssuer, {&sfAMMID});
|
||||
bool const isBlackholedIssuer = isBlackholed(ctx.view, sleIssuer);
|
||||
if (!isAMM && !isBlackholedIssuer)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
else
|
||||
{
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
// If the account does not have a trustline to the issuer, return tecNO_LINE
|
||||
auto const sleRippleState =
|
||||
@@ -478,6 +492,27 @@ EscrowCreate::doApply()
|
||||
auto const xferRate = transferRate(ctx_.view(), amount);
|
||||
if (xferRate != parityRate)
|
||||
(*slep)[sfTransferRate] = xferRate.value;
|
||||
|
||||
// Add lsfAllowTrustLineLocking to AMM pseudo accounts and
|
||||
// blackholed issuers under fixTokenEscrowV1_1
|
||||
if (ctx_.view().rules().enabled(fixTokenEscrowV1_1))
|
||||
{
|
||||
AccountID const issuer = amount.getIssuer();
|
||||
auto sleIssuer = ctx_.view().peek(keylet::account(issuer));
|
||||
if (sleIssuer && !sleIssuer->isFlag(lsfAllowTrustLineLocking))
|
||||
{
|
||||
bool const isAMM = isPseudoAccount(sleIssuer, {&sfAMMID});
|
||||
bool const isBlackholedIssuer =
|
||||
isBlackholed(ctx_.view(), sleIssuer);
|
||||
if (isAMM || isBlackholedIssuer)
|
||||
{
|
||||
sleIssuer->setFieldU32(
|
||||
sfFlags,
|
||||
sleIssuer->getFlags() | lsfAllowTrustLineLocking);
|
||||
ctx_.view().update(sleIssuer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx_.view().insert(slep);
|
||||
@@ -755,6 +790,13 @@ escrowUnlockApplyHelper<Issue>(
|
||||
bool createAsset,
|
||||
beast::Journal journal)
|
||||
{
|
||||
// fixTokenEscrowV1_1: sleDest must be an account root
|
||||
if (view.rules().enabled(fixTokenEscrowV1_1))
|
||||
{
|
||||
if (!sleDest || sleDest->getType() != ltACCOUNT_ROOT)
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
Keylet const trustLineKey = keylet::line(receiver, amount.issue());
|
||||
bool const recvLow = issuer > receiver;
|
||||
bool const senderIssuer = issuer == sender;
|
||||
@@ -888,6 +930,13 @@ escrowUnlockApplyHelper<MPTIssue>(
|
||||
bool createAsset,
|
||||
beast::Journal journal)
|
||||
{
|
||||
// fixTokenEscrowV1_1: sleDest must be an account root
|
||||
if (view.rules().enabled(fixTokenEscrowV1_1))
|
||||
{
|
||||
if (!sleDest || sleDest->getType() != ltACCOUNT_ROOT)
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
bool const senderIssuer = issuer == sender;
|
||||
bool const receiverIssuer = issuer == receiver;
|
||||
|
||||
|
||||
@@ -1067,9 +1067,14 @@ ValidNewAccountRoot::finalize(
|
||||
|
||||
if (pseudoAccount)
|
||||
{
|
||||
std::uint32_t const expected =
|
||||
std::uint32_t const base =
|
||||
(lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
|
||||
if (flags_ != expected)
|
||||
bool valid = (flags_ == base);
|
||||
// fixTokenEscrowV1_1: pseudo accounts will also have
|
||||
// lsfAllowTrustLineLocking set at creation time.
|
||||
if (!valid && view.rules().enabled(fixTokenEscrowV1_1))
|
||||
valid = (flags_ == (base | lsfAllowTrustLineLocking));
|
||||
if (!valid)
|
||||
{
|
||||
JLOG(j.fatal())
|
||||
<< "Invariant failed: pseudo-account created with "
|
||||
|
||||
Reference in New Issue
Block a user