xrpld
Loading...
Searching...
No Matches
GatewayBalances_test.cpp
1#include <test/jtx/Account.h>
2#include <test/jtx/Env.h>
3#include <test/jtx/WSClient.h>
4#include <test/jtx/amount.h>
5#include <test/jtx/escrow.h>
6#include <test/jtx/mpt.h>
7#include <test/jtx/pay.h>
8#include <test/jtx/trust.h>
9
10#include <xrpl/beast/unit_test/suite.h>
11#include <xrpl/json/json_value.h>
12#include <xrpl/protocol/ApiVersion.h>
13#include <xrpl/protocol/Feature.h>
14#include <xrpl/protocol/STAmount.h>
15#include <xrpl/protocol/TxFlags.h>
16#include <xrpl/protocol/jss.h>
17
18namespace xrpl::test {
19
21{
22public:
23 void
25 {
26 using namespace std::chrono_literals;
27 using namespace jtx;
28 Env env(*this, features);
29
30 {
31 // Gateway account and assets
32 Account const alice{"alice"};
33 env.fund(XRP(10000), "alice");
34 auto usd = alice["USD"];
35 auto cny = alice["CNY"];
36 auto jpy = alice["JPY"];
37
38 // Create a hotwallet
39 Account const hw{"hw"};
40 env.fund(XRP(10000), "hw");
41 env.close();
42 env(trust(hw, usd(10000)));
43 env(trust(hw, jpy(10000)));
44 env(pay(alice, hw, usd(5000)));
45 env(pay(alice, hw, jpy(5000)));
46
47 // Create some clients
48 Account const bob{"bob"};
49 env.fund(XRP(10000), "bob");
50 env.close();
51 env(trust(bob, usd(100)));
52 env(trust(bob, cny(100)));
53 env(pay(alice, bob, usd(50)));
54
55 Account const charley{"charley"};
56 env.fund(XRP(10000), "charley");
57 env.close();
58 env(trust(charley, cny(500)));
59 env(trust(charley, jpy(500)));
60 env(pay(alice, charley, cny(250)));
61 env(pay(alice, charley, jpy(250)));
62
63 Account const dave{"dave"};
64 env.fund(XRP(10000), "dave");
65 env.close();
66 env(trust(dave, cny(100)));
67 env(pay(alice, dave, cny(30)));
68
69 // give the gateway an asset
70 env(trust(alice, charley["USD"](50)));
71 env(pay(charley, alice, usd(10)));
72
73 // freeze dave
74 env(trust(alice, dave["CNY"](0), dave, tfSetFreeze));
75
76 env.close();
77
78 auto wsc = makeWSClient(env.app().config());
79
80 json::Value qry;
81 qry[jss::account] = alice.human();
82 qry[jss::hotwallet] = hw.human();
83
84 auto jv = wsc->invoke("gateway_balances", qry);
85 expect(jv[jss::status] == "success");
86 if (wsc->version() == 2)
87 {
88 expect(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
89 expect(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
90 expect(jv.isMember(jss::id) && jv[jss::id] == 5);
91 }
92
93 auto const& result = jv[jss::result];
94 expect(result[jss::account] == alice.human());
95 expect(result[jss::status] == "success");
96
97 {
98 auto const& balances = result[jss::balances];
99 expect(balances.isObject(), "balances is not an object");
100 expect(balances.size() == 1, "balances size is not 1");
101
102 auto const& hwBalance = balances[hw.human()];
103 expect(hwBalance.isArray(), "hwBalance is not an array");
104 expect(hwBalance.size() == 2);
105 auto c1 = hwBalance[0u][jss::currency];
106 auto c2 = hwBalance[1u][jss::currency];
107 expect(c1 == "USD" || c2 == "USD");
108 expect(c1 == "JPY" || c2 == "JPY");
109 expect(hwBalance[0u][jss::value] == "5000" && hwBalance[1u][jss::value] == "5000");
110 }
111
112 {
113 auto const& fBalances = result[jss::frozen_balances];
114 expect(fBalances.isObject());
115 expect(fBalances.size() == 1);
116
117 auto const& fBal = fBalances[dave.human()];
118 expect(fBal.isArray());
119 expect(fBal.size() == 1);
120 expect(fBal[0u].isObject());
121 expect(fBal[0u][jss::currency] == "CNY");
122 expect(fBal[0u][jss::value] == "30");
123 }
124
125 {
126 auto const& assets = result[jss::assets];
127 expect(assets.isObject(), "assets it not an object");
128 expect(assets.size() == 1, "assets size is not 1");
129
130 auto const& cAssets = assets[charley.human()];
131 expect(cAssets.isArray());
132 expect(cAssets.size() == 1);
133 expect(cAssets[0u][jss::currency] == "USD");
134 expect(cAssets[0u][jss::value] == "10");
135 }
136
137 {
138 auto const& obligations = result[jss::obligations];
139 expect(obligations.isObject(), "obligations is not an object");
140 expect(obligations.size() == 3);
141 expect(obligations["CNY"] == "250");
142 expect(obligations["JPY"] == "250");
143 expect(obligations["USD"] == "50");
144 }
145 }
146 }
147
148 void
150 {
151 using namespace std::chrono_literals;
152 using namespace jtx;
153 Env env(*this, features);
154
155 // Gateway account and assets
156 Account const alice{"alice"};
157 env.fund(XRP(10000), alice);
158 Account const hw{"hw"};
159 env.fund(XRP(10000), hw);
160 env.close();
161
162 auto wsc = makeWSClient(env.app().config());
163
164 json::Value qry2;
165 qry2[jss::account] = alice.human();
166 qry2[jss::hotwallet] = "asdf";
167
168 forAllApiVersions([&, this](unsigned apiVersion) {
169 qry2[jss::api_version] = apiVersion;
170 auto jv = wsc->invoke("gateway_balances", qry2);
171 expect(jv[jss::status] == "error");
172
173 auto response = jv[jss::result];
174 auto const error = apiVersion < 2u ? "invalidHotWallet" : "invalidParams";
175 BEAST_EXPECT(response[jss::error] == error);
176 });
177 }
178
179 void
181 {
182 testcase("Gateway Balances with non-string account/ident");
183 using namespace std::chrono_literals;
184 using namespace jtx;
185 Env env(*this, features);
186
187 Account const alice{"alice"};
188 env.fund(XRP(10000), alice);
189 env.close();
190
191 auto wsc = makeWSClient(env.app().config());
192
193 // A non-string "account" must be rejected cleanly with invalidParams
194 // rather than throwing a Json::LogicError that surfaces as internal.
195 json::Value qry;
196 qry[jss::account] = 42;
197 qry[jss::hotwallet] = alice.human();
198
199 forAllApiVersions([&, this](unsigned apiVersion) {
200 qry[jss::api_version] = apiVersion;
201 auto jv = wsc->invoke("gateway_balances", qry);
202 expect(jv[jss::status] == "error");
203 BEAST_EXPECT(jv[jss::result][jss::error] == "invalidParams");
204 });
205
206 // The same applies to a non-string "ident".
207 json::Value qry2;
208 qry2[jss::ident] = 42;
209
210 forAllApiVersions([&, this](unsigned apiVersion) {
211 qry2[jss::api_version] = apiVersion;
212 auto jv = wsc->invoke("gateway_balances", qry2);
213 expect(jv[jss::status] == "error");
214 BEAST_EXPECT(jv[jss::result][jss::error] == "invalidParams");
215 });
216 }
217
218 void
220 {
221 using namespace std::chrono_literals;
222 using namespace jtx;
223 Env env(*this);
224
225 // Gateway account and assets
226 Account const alice{"alice"};
227 env.fund(XRP(10000), alice);
228 env.close();
229 auto usd = alice["USD"];
230
231 // The largest valid STAmount of USD:
233
234 // Create a hotwallet
235 Account const hw{"hw"};
236 env.fund(XRP(10000), hw);
237 env.close();
238 env(trust(hw, maxUSD));
239 env.close();
240 env(pay(alice, hw, maxUSD));
241
242 // Create some clients
243 Account const bob{"bob"};
244 env.fund(XRP(10000), bob);
245 env.close();
246 env(trust(bob, maxUSD));
247 env.close();
248 env(pay(alice, bob, maxUSD));
249
250 Account const charley{"charley"};
251 env.fund(XRP(10000), charley);
252 env.close();
253 env(trust(charley, maxUSD));
254 env.close();
255 env(pay(alice, charley, maxUSD));
256
257 env.close();
258
259 auto wsc = makeWSClient(env.app().config());
260
261 json::Value query;
262 query[jss::account] = alice.human();
263 query[jss::hotwallet] = hw.human();
264
265 // Note that the sum of bob's and charley's USD balances exceeds
266 // the amount that can be represented in an STAmount. Nevertheless
267 // we get a valid "obligations" that shows the maximum valid
268 // STAmount.
269 auto jv = wsc->invoke("gateway_balances", query);
270 expect(jv[jss::status] == "success");
271 expect(jv[jss::result][jss::obligations]["USD"] == maxUSD.getText());
272 }
273
274 void
276 {
277 testcase("Gateway Balances with MPT Escrow");
278 using namespace std::chrono_literals;
279 using namespace jtx;
280
281 // testableAmendments() includes MPT
282 FeatureBitset const features = testableAmendments();
283 Env env(*this, features);
284
285 Account const alice{"alice"};
286 Account const bob{"bob"};
287
288 env.fund(XRP(10000), alice, bob);
289 env.close();
290
291 // Create MPT issuance (Alice) with Escrow capability
292 MPTTester mpt(env, alice, {.holders = {bob}, .fund = false});
293 mpt.create({.flags = tfMPTCanEscrow});
294
295 // Authorize Bob and fund him
296 mpt.authorize({.account = bob, .holderCount = 1});
297 mpt.pay(alice, bob, 1000);
298
299 // Bob creates an escrow of MPT to Alice.
300 auto const MPT = mpt["MPT"]; // NOLINT(readability-identifier-naming)
301 env(escrow::create(bob, alice, MPT(100)), escrow::kFinishTime(env.now() + 10s));
302 env.close();
303
304 // Query gateway_balances for Bob.
305 auto wsc = makeWSClient(env.app().config());
306 json::Value qry;
307 qry[jss::account] = bob.human();
308
309 auto jv = wsc->invoke("gateway_balances", qry);
310 expect(jv[jss::status] == "success");
311 }
312
313 void
314 run() override
315 {
316 using namespace jtx;
317 auto const sa = testableAmendments();
318 for (auto feature : {sa - featurePermissionedDEX, sa})
319 {
320 testGWB(feature);
321 testGWBApiVersions(feature);
322 testGWBInvalidAccount(feature);
323 }
326 }
327};
328
329BEAST_DEFINE_TESTSUITE(GatewayBalances, rpc, xrpl);
330
331} // namespace xrpl::test
A testsuite class.
Definition suite.h:52
bool expect(Condition const &shouldBeTrue)
Evaluate a test condition.
Definition suite.h:235
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:155
Represents a JSON value.
Definition json_value.h:117
virtual Config & config()=0
std::string getText() const override
Definition STAmount.cpp:646
static constexpr std::uint64_t kMaxValue
Definition STAmount.h:67
static constexpr int kMaxOffset
Definition STAmount.h:62
void testGWB(FeatureBitset features)
void run() override
Runs the suite.
void testGWBApiVersions(FeatureBitset features)
void testGWBInvalidAccount(FeatureBitset features)
Immutable cryptographic account descriptor.
Definition jtx/Account.h:21
std::string const & human() const
Returns the human readable public key.
A transaction testing environment.
Definition Env.h:161
Application & app()
Definition Env.h:300
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:133
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:323
NetClock::time_point now()
Returns the current network time.
Definition Env.h:326
Test helper for creating, mutating, and asserting MPT and confidential MPT ledger state.
Definition mpt.h:447
void create(MPTCreate const &arg=MPTCreate{})
Definition mpt.cpp:241
Converts to MPT Issue or STAmount.
API version numbers used in later API versions.
Definition ApiVersion.h:36
json::Value create(AccountID const &account, AccountID const &to, STAmount const &amount)
Definition escrow.cpp:24
auto const kFinishTime
Set the "FinishAfter" time tag on a JTx.
Definition escrow.h:78
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:14
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
FeatureBitset testableAmendments()
Definition Env.h:92
json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:18
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
std::unique_ptr< WSClient > makeWSClient(Config const &cfg, bool v2, unsigned rpcVersion, std::unordered_map< std::string, std::string > const &headers)
Returns a client operating through WebSockets/S.
Definition WSClient.cpp:371
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
void forAllApiVersions(Fn const &fn, Args &&... args)
Definition ApiVersion.h:159