mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-07 02:36:47 +00:00
Add support for XLS-85 Token Escrow (#5185)
- Specification: https://github.com/XRPLF/XRPL-Standards/pull/272 - Amendment: `TokenEscrow` - Enables escrowing of IOU and MPT tokens in addition to native XRP. - Allows accounts to lock issued tokens (IOU/MPT) in escrow objects, with support for freeze, authorization, and transfer rates. - Adds new ledger fields (`sfLockedAmount`, `sfIssuerNode`, etc.) to track locked balances for IOU and MPT escrows. - Updates EscrowCreate, EscrowFinish, and EscrowCancel transaction logic to support IOU and MPT assets, including proper handling of trustlines and MPT authorization, transfer rates, and locked balances. - Enforces invariant checks for escrowed IOU/MPT amounts. - Extends GatewayBalances RPC to report locked (escrowed) balances.
This commit is contained in:
@@ -3651,10 +3651,10 @@ private:
|
||||
// Can't pay into AMM with escrow.
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
env(escrow(carol, ammAlice.ammAccount(), XRP(1)),
|
||||
condition(cb1),
|
||||
finish_time(env.now() + 1s),
|
||||
cancel_time(env.now() + 2s),
|
||||
env(escrow::create(carol, ammAlice.ammAccount(), XRP(1)),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::finish_time(env.now() + 1s),
|
||||
escrow::cancel_time(env.now() + 2s),
|
||||
fee(baseFee * 150),
|
||||
ter(tecNO_PERMISSION));
|
||||
});
|
||||
|
||||
@@ -335,26 +335,11 @@ public:
|
||||
env(check::cancel(becky, checkId));
|
||||
env.close();
|
||||
|
||||
// Lambda to create an escrow.
|
||||
auto escrowCreate = [](jtx::Account const& account,
|
||||
jtx::Account const& to,
|
||||
STAmount const& amount,
|
||||
NetClock::time_point const& cancelAfter) {
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowCreate;
|
||||
jv[jss::Account] = account.human();
|
||||
jv[jss::Destination] = to.human();
|
||||
jv[jss::Amount] = amount.getJson(JsonOptions::none);
|
||||
jv[sfFinishAfter.jsonName] =
|
||||
cancelAfter.time_since_epoch().count() + 1;
|
||||
jv[sfCancelAfter.jsonName] =
|
||||
cancelAfter.time_since_epoch().count() + 2;
|
||||
return jv;
|
||||
};
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
std::uint32_t const escrowSeq{env.seq(alice)};
|
||||
env(escrowCreate(alice, becky, XRP(333), env.now() + 2s));
|
||||
env(escrow::create(alice, becky, XRP(333)),
|
||||
escrow::finish_time(env.now() + 3s),
|
||||
escrow::cancel_time(env.now() + 4s));
|
||||
env.close();
|
||||
|
||||
// alice and becky should be unable to delete their accounts because
|
||||
@@ -366,17 +351,39 @@ public:
|
||||
// Now cancel the escrow, but create a payment channel between
|
||||
// alice and becky.
|
||||
|
||||
// Lambda to cancel an escrow.
|
||||
auto escrowCancel =
|
||||
[](Account const& account, Account const& from, std::uint32_t seq) {
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowCancel;
|
||||
jv[jss::Account] = account.human();
|
||||
jv[sfOwner.jsonName] = from.human();
|
||||
jv[sfOfferSequence.jsonName] = seq;
|
||||
return jv;
|
||||
};
|
||||
env(escrowCancel(becky, alice, escrowSeq));
|
||||
bool const withTokenEscrow =
|
||||
env.current()->rules().enabled(featureTokenEscrow);
|
||||
if (withTokenEscrow)
|
||||
{
|
||||
Account const gw1("gw1");
|
||||
Account const carol("carol");
|
||||
auto const USD = gw1["USD"];
|
||||
env.fund(XRP(100000), carol, gw1);
|
||||
env(fset(gw1, asfAllowTrustLineLocking));
|
||||
env.close();
|
||||
env.trust(USD(10000), carol);
|
||||
env.close();
|
||||
env(pay(gw1, carol, USD(100)));
|
||||
env.close();
|
||||
|
||||
std::uint32_t const escrowSeq{env.seq(carol)};
|
||||
env(escrow::create(carol, becky, USD(1)),
|
||||
escrow::finish_time(env.now() + 3s),
|
||||
escrow::cancel_time(env.now() + 4s));
|
||||
env.close();
|
||||
|
||||
incLgrSeqForAccDel(env, gw1);
|
||||
|
||||
env(acctdelete(gw1, becky),
|
||||
fee(acctDelFee),
|
||||
ter(tecHAS_OBLIGATIONS));
|
||||
env.close();
|
||||
|
||||
env(escrow::cancel(becky, carol, escrowSeq));
|
||||
env.close();
|
||||
}
|
||||
|
||||
env(escrow::cancel(becky, alice, escrowSeq));
|
||||
env.close();
|
||||
|
||||
Keylet const alicePayChanKey{
|
||||
|
||||
@@ -714,12 +714,12 @@ struct DepositPreauth_test : public beast::unit_test::suite
|
||||
if (!supportsPreauth)
|
||||
{
|
||||
auto const seq1 = env.seq(alice);
|
||||
env(escrow(alice, becky, XRP(100)),
|
||||
finish_time(env.now() + 1s));
|
||||
env(escrow::create(alice, becky, XRP(100)),
|
||||
escrow::finish_time(env.now() + 1s));
|
||||
env.close();
|
||||
|
||||
// Failed as rule is disabled
|
||||
env(finish(gw, alice, seq1),
|
||||
env(escrow::finish(gw, alice, seq1),
|
||||
fee(1500),
|
||||
ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
@@ -1387,12 +1387,13 @@ struct DepositPreauth_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, bob, XRP(1000)), finish_time(env.now() + 1s));
|
||||
env(escrow::create(alice, bob, XRP(1000)),
|
||||
escrow::finish_time(env.now() + 1s));
|
||||
env.close();
|
||||
|
||||
// zelda can't finish escrow with invalid credentials
|
||||
{
|
||||
env(finish(zelda, alice, seq),
|
||||
env(escrow::finish(zelda, alice, seq),
|
||||
credentials::ids({}),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
@@ -1404,14 +1405,14 @@ struct DepositPreauth_test : public beast::unit_test::suite
|
||||
"0E0B04ED60588A758B67E21FBBE95AC5A63598BA951761DC0EC9C08D7E"
|
||||
"01E034";
|
||||
|
||||
env(finish(zelda, alice, seq),
|
||||
env(escrow::finish(zelda, alice, seq),
|
||||
credentials::ids({invalidIdx}),
|
||||
ter(tecBAD_CREDENTIALS));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{ // Ledger closed, time increased, zelda can't finish escrow
|
||||
env(finish(zelda, alice, seq),
|
||||
env(escrow::finish(zelda, alice, seq),
|
||||
credentials::ids({credIdx}),
|
||||
fee(1500),
|
||||
ter(tecEXPIRED));
|
||||
|
||||
3736
src/test/app/EscrowToken_test.cpp
Normal file
3736
src/test/app/EscrowToken_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1694,15 +1694,6 @@ class MPToken_test : public beast::unit_test::suite
|
||||
jv[jss::SendMax] = mpt.getJson(JsonOptions::none);
|
||||
test(jv, jss::SendMax.c_str());
|
||||
}
|
||||
// EscrowCreate
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowCreate;
|
||||
jv[jss::Account] = alice.human();
|
||||
jv[jss::Destination] = carol.human();
|
||||
jv[jss::Amount] = mpt.getJson(JsonOptions::none);
|
||||
test(jv, jss::Amount.c_str());
|
||||
}
|
||||
// OfferCreate
|
||||
{
|
||||
Json::Value jv = offer(alice, USD(100), mpt);
|
||||
|
||||
Reference in New Issue
Block a user