Skip to content

Commit fe2cf3a

Browse files
ckeshavacoderabbitai[bot]Patel-Raj11
authored
fix: rectify STIssue codec (MPTCurrency case only) (#870)
* fix the errors in STIssue codec * feature: allow xrpl-py integ tests to run on XRPL Devnet; This commit adds utility methods that aid in this effort * add SAV integ test with MPToken as Vault asset * fix: big-endian format to interpret the sequence number in MPTID --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Raj Patel <rajp@ripple.com>
1 parent 58afc45 commit fe2cf3a

File tree

6 files changed

+369
-44
lines changed

6 files changed

+369
-44
lines changed

tests/integration/it_utils.py

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
JSON_TESTNET_URL = "https://s.altnet.rippletest.net:51234"
4040
WEBSOCKET_TESTNET_URL = "wss://s.altnet.rippletest.net:51233"
4141

42+
JSON_DEVNET_URL = "https://s.devnet.rippletest.net:51234/"
43+
WEBSOCKET_DEVNET_URL = "wss://s.devnet.rippletest.net:51233/"
44+
4245
JSON_RPC_CLIENT = JsonRpcClient(JSON_RPC_URL)
4346
ASYNC_JSON_RPC_CLIENT = AsyncJsonRpcClient(JSON_RPC_URL)
4447

@@ -51,16 +54,27 @@
5154
WEBSOCKET_TESTNET_CLIENT = WebsocketClient(WEBSOCKET_TESTNET_URL)
5255
ASYNC_WEBSOCKET_TESTNET_CLIENT = AsyncWebsocketClient(WEBSOCKET_TESTNET_URL)
5356

54-
# (is_async, is_json, is_testnet) -> client
57+
JSON_RPC_DEVNET_CLIENT = JsonRpcClient(JSON_DEVNET_URL)
58+
ASYNC_JSON_RPC_DEVNET_CLIENT = AsyncJsonRpcClient(JSON_DEVNET_URL)
59+
60+
WEBSOCKET_DEVNET_CLIENT = WebsocketClient(WEBSOCKET_DEVNET_URL)
61+
ASYNC_WEBSOCKET_DEVNET_CLIENT = AsyncWebsocketClient(WEBSOCKET_DEVNET_URL)
62+
63+
# (is_async, is_json, is_testnet, use_devnet) -> client
5564
_CLIENTS = {
56-
(True, True, True): ASYNC_JSON_RPC_TESTNET_CLIENT,
57-
(True, True, False): ASYNC_JSON_RPC_CLIENT,
58-
(True, False, True): ASYNC_WEBSOCKET_TESTNET_CLIENT,
59-
(True, False, False): ASYNC_WEBSOCKET_CLIENT,
60-
(False, True, True): JSON_RPC_TESTNET_CLIENT,
61-
(False, True, False): JSON_RPC_CLIENT,
62-
(False, False, True): WEBSOCKET_TESTNET_CLIENT,
63-
(False, False, False): WEBSOCKET_CLIENT,
65+
(True, True, True, False): ASYNC_JSON_RPC_TESTNET_CLIENT,
66+
(True, True, False, False): ASYNC_JSON_RPC_CLIENT,
67+
(True, False, True, False): ASYNC_WEBSOCKET_TESTNET_CLIENT,
68+
(True, False, False, False): ASYNC_WEBSOCKET_CLIENT,
69+
(False, True, True, False): JSON_RPC_TESTNET_CLIENT,
70+
(False, True, False, False): JSON_RPC_CLIENT,
71+
(False, False, True, False): WEBSOCKET_TESTNET_CLIENT,
72+
(False, False, False, False): WEBSOCKET_CLIENT,
73+
# Both use_testnet and use_devnet cannot be specified at the same time
74+
(True, True, False, True): ASYNC_JSON_RPC_DEVNET_CLIENT,
75+
(True, False, False, True): ASYNC_WEBSOCKET_DEVNET_CLIENT,
76+
(False, True, False, True): JSON_RPC_DEVNET_CLIENT,
77+
(False, False, False, True): WEBSOCKET_DEVNET_CLIENT,
6478
}
6579

6680
MASTER_ACCOUNT = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"
@@ -156,6 +170,7 @@ def sign_and_reliable_submission(
156170
wallet: Wallet,
157171
client: SyncClient = JSON_RPC_CLIENT,
158172
check_fee: bool = True,
173+
is_devnet_or_testnet=False,
159174
) -> Response:
160175
modified_transaction = transaction
161176

@@ -183,7 +198,10 @@ def sign_and_reliable_submission(
183198
response = submit_transaction(
184199
modified_transaction, wallet, client, check_fee=check_fee
185200
)
186-
client.request(LEDGER_ACCEPT_REQUEST)
201+
202+
# On devnet or testnet, wait for the transaction to be validated
203+
if not is_devnet_or_testnet:
204+
client.request(LEDGER_ACCEPT_REQUEST)
187205
return response
188206

189207

@@ -193,6 +211,7 @@ async def sign_and_reliable_submission_async(
193211
wallet: Wallet,
194212
client: AsyncClient = ASYNC_JSON_RPC_CLIENT,
195213
check_fee: bool = True,
214+
is_devnet_or_testnet=False,
196215
) -> Response:
197216
modified_transaction = transaction
198217

@@ -219,7 +238,10 @@ async def sign_and_reliable_submission_async(
219238
response = await submit_transaction_async(
220239
modified_transaction, wallet, client, check_fee=check_fee
221240
)
222-
await client.request(LEDGER_ACCEPT_REQUEST)
241+
242+
# On devnet or testnet, wait for the transaction to be validated
243+
if not is_devnet_or_testnet:
244+
await client.request(LEDGER_ACCEPT_REQUEST)
223245
return response
224246

225247

@@ -253,16 +275,22 @@ async def accept_ledger_async(
253275
AsyncTestTimer(client, delay)
254276

255277

278+
# The _choose_client(_async)? methods are only used to send LEDGER_ACCEPT_REQUEST.
279+
# Hence, they are not applicable for devnet/testnet clients.
256280
def _choose_client(use_json_client: bool) -> SyncClient:
257-
return cast(SyncClient, _CLIENTS[(False, use_json_client, False)])
281+
return cast(SyncClient, _CLIENTS[(False, use_json_client, False, False)])
258282

259283

260284
def _choose_client_async(use_json_client: bool) -> AsyncClient:
261-
return cast(AsyncClient, _CLIENTS[(True, use_json_client, False)])
285+
return cast(AsyncClient, _CLIENTS[(True, use_json_client, False, False)])
262286

263287

264-
def _get_client(is_async: bool, is_json: bool, is_testnet: bool) -> Client:
265-
return _CLIENTS[(is_async, is_json, is_testnet)]
288+
def _get_client(
289+
is_async: bool, is_json: bool, is_testnet: bool, is_devnet: bool
290+
) -> Client:
291+
if is_testnet and is_devnet:
292+
raise ValueError("use_testnet and use_devnet are mutually exclusive")
293+
return _CLIENTS[(is_async, is_json, is_testnet, is_devnet)]
266294

267295

268296
def test_async_and_sync(
@@ -271,6 +299,7 @@ def test_async_and_sync(
271299
websockets_only=False,
272300
num_retries=1,
273301
use_testnet=False,
302+
use_devnet=False,
274303
async_only=False,
275304
):
276305
def decorator(test_function):
@@ -345,18 +374,26 @@ def modified_test(self):
345374
if not websockets_only:
346375
with self.subTest(version="async", client="json"):
347376
asyncio.run(
348-
_run_async_test(self, _get_client(True, True, use_testnet), 1)
377+
_run_async_test(
378+
self, _get_client(True, True, use_testnet, use_devnet), 1
379+
)
349380
)
350381
if not async_only:
351382
with self.subTest(version="sync", client="json"):
352-
_run_sync_test(self, _get_client(False, True, use_testnet), 2)
383+
_run_sync_test(
384+
self, _get_client(False, True, use_testnet, use_devnet), 2
385+
)
353386
with self.subTest(version="async", client="websocket"):
354387
asyncio.run(
355-
_run_async_test(self, _get_client(True, False, use_testnet), 3)
388+
_run_async_test(
389+
self, _get_client(True, False, use_testnet, use_devnet), 3
390+
)
356391
)
357392
if not async_only:
358393
with self.subTest(version="sync", client="websocket"):
359-
_run_sync_test(self, _get_client(False, False, use_testnet), 4)
394+
_run_sync_test(
395+
self, _get_client(False, False, use_testnet, use_devnet), 4
396+
)
360397

361398
return modified_test
362399

tests/integration/transactions/test_single_asset_vault.py

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,137 @@
1818
VaultWithdraw,
1919
)
2020
from xrpl.models.amounts.issued_currency_amount import IssuedCurrencyAmount
21+
from xrpl.models.amounts.mpt_amount import MPTAmount
2122
from xrpl.models.currencies import IssuedCurrency
22-
from xrpl.models.requests import AccountObjects, LedgerEntry
23+
from xrpl.models.currencies.mpt_currency import MPTCurrency
24+
from xrpl.models.requests import AccountObjects, LedgerEntry, Tx
2325
from xrpl.models.requests.account_objects import AccountObjectType
2426
from xrpl.models.response import ResponseStatus
27+
from xrpl.models.transactions.mptoken_authorize import MPTokenAuthorize
28+
from xrpl.models.transactions.mptoken_issuance_create import (
29+
MPTokenIssuanceCreate,
30+
MPTokenIssuanceCreateFlag,
31+
)
2532
from xrpl.models.transactions.vault_create import WithdrawalPolicy
2633
from xrpl.utils import str_to_hex
2734
from xrpl.wallet import Wallet
2835

2936

3037
class TestSingleAssetVault(IntegrationTestCase):
38+
@test_async_and_sync(globals())
39+
async def test_vault_with_mptoken(self, client):
40+
vault_owner = Wallet.create()
41+
await fund_wallet_async(vault_owner)
42+
43+
# Create a MPToken
44+
tx = MPTokenIssuanceCreate(
45+
account=vault_owner.address,
46+
flags=MPTokenIssuanceCreateFlag.TF_MPT_CAN_TRANSFER
47+
+ MPTokenIssuanceCreateFlag.TF_MPT_CAN_CLAWBACK,
48+
)
49+
response = await sign_and_reliable_submission_async(tx, vault_owner, client)
50+
self.assertTrue(response.is_successful())
51+
self.assertEqual(response.result["engine_result"], "tesSUCCESS")
52+
53+
# fetch the mpt_issuance_id
54+
response = await client.request(
55+
Tx(transaction=response.result["tx_json"]["hash"])
56+
)
57+
MPT_ISSUANCE_ID = response.result["meta"]["mpt_issuance_id"]
58+
59+
# Create a holder wallet to validate VaultDeposit+VaultWithdraw+VaultClawback
60+
# transactions
61+
holder_wallet = Wallet.create()
62+
await fund_wallet_async(holder_wallet)
63+
64+
# holder provides authorization to hold the MPToken
65+
tx = MPTokenAuthorize(
66+
account=holder_wallet.address,
67+
mptoken_issuance_id=MPT_ISSUANCE_ID,
68+
)
69+
response = await sign_and_reliable_submission_async(tx, holder_wallet, client)
70+
self.assertEqual(response.status, ResponseStatus.SUCCESS)
71+
self.assertEqual(response.result["engine_result"], "tesSUCCESS")
72+
73+
# transfer some MPToken to the holder wallet
74+
tx = Payment(
75+
account=vault_owner.address,
76+
amount=MPTAmount(mpt_issuance_id=MPT_ISSUANCE_ID, value="100"),
77+
destination=holder_wallet.address,
78+
)
79+
response = await sign_and_reliable_submission_async(tx, vault_owner, client)
80+
self.assertEqual(response.status, ResponseStatus.SUCCESS)
81+
self.assertEqual(response.result["engine_result"], "tesSUCCESS")
82+
83+
# Step-1: Create a vault
84+
tx = VaultCreate(
85+
account=vault_owner.address,
86+
asset=MPTCurrency(mpt_issuance_id=MPT_ISSUANCE_ID),
87+
)
88+
response = await sign_and_reliable_submission_async(tx, vault_owner, client)
89+
self.assertTrue(response.is_successful())
90+
self.assertEqual(response.result["engine_result"], "tesSUCCESS")
91+
92+
print(response.result)
93+
94+
# Step-1.b: Verify the existence of the vault with account_objects RPC call
95+
account_objects_response = await client.request(
96+
AccountObjects(account=vault_owner.address, type=AccountObjectType.VAULT)
97+
)
98+
self.assertEqual(len(account_objects_response.result["account_objects"]), 1)
99+
100+
VAULT_ID = account_objects_response.result["account_objects"][0]["index"]
101+
102+
# Step-2: Update the characteristics of the vault with VaultSet transaction
103+
tx = VaultSet(
104+
account=vault_owner.address,
105+
vault_id=VAULT_ID,
106+
data=str_to_hex("auxilliary data pertaining to the vault"),
107+
)
108+
response = await sign_and_reliable_submission_async(tx, vault_owner, client)
109+
self.assertEqual(response.status, ResponseStatus.SUCCESS)
110+
self.assertEqual(response.result["engine_result"], "tesSUCCESS")
111+
112+
# Step-3: Execute a VaultDeposit transaction
113+
tx = VaultDeposit(
114+
account=holder_wallet.address,
115+
vault_id=VAULT_ID,
116+
amount=MPTAmount(mpt_issuance_id=MPT_ISSUANCE_ID, value="10"),
117+
)
118+
response = await sign_and_reliable_submission_async(tx, holder_wallet, client)
119+
self.assertEqual(response.status, ResponseStatus.SUCCESS)
120+
self.assertEqual(response.result["engine_result"], "tesSUCCESS")
121+
122+
# Step-4: Execute a VaultWithdraw transaction
123+
tx = VaultWithdraw(
124+
account=holder_wallet.address,
125+
vault_id=VAULT_ID,
126+
amount=MPTAmount(mpt_issuance_id=MPT_ISSUANCE_ID, value="5"),
127+
)
128+
response = await sign_and_reliable_submission_async(tx, holder_wallet, client)
129+
self.assertEqual(response.status, ResponseStatus.SUCCESS)
130+
self.assertEqual(response.result["engine_result"], "tesSUCCESS")
131+
132+
# Step-5: Execute a VaultClawback transaction
133+
tx = VaultClawback(
134+
account=vault_owner.address,
135+
holder=holder_wallet.address,
136+
vault_id=VAULT_ID,
137+
amount=MPTAmount(mpt_issuance_id=MPT_ISSUANCE_ID, value="100"),
138+
)
139+
response = await sign_and_reliable_submission_async(tx, vault_owner, client)
140+
self.assertEqual(response.status, ResponseStatus.SUCCESS)
141+
self.assertEqual(response.result["engine_result"], "tesSUCCESS")
142+
143+
# Step-6: Delete the Vault with VaultDelete transaction
144+
tx = VaultDelete(
145+
account=vault_owner.address,
146+
vault_id=VAULT_ID,
147+
)
148+
response = await sign_and_reliable_submission_async(tx, vault_owner, client)
149+
self.assertEqual(response.status, ResponseStatus.SUCCESS)
150+
self.assertEqual(response.result["engine_result"], "tesSUCCESS")
151+
31152
@test_async_and_sync(globals())
32153
async def test_sav_lifecycle(self, client):
33154

tests/unit/core/binarycodec/fixtures/data/codec-fixtures.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5023,6 +5023,22 @@
50235023
"AssetsMaximum": "1000",
50245024
"WithdrawalPolicy": 1
50255025
}
5026+
},
5027+
{
5028+
"binary": "120041210000F7E02400000557201B0000056D6840000000004C4B407321ED7FEAF5E19A09D903C13983D30BB7A67CBDA64AAB38A4C0DA6046644FFC0AF487744014B5CAC538F7094C2CCF1FB8A1E05068727F7A10441AA68C23CC50843EB558DA61E191430925890420EAE0F97E345F37F10D6FB89AA1E173EBF3C50B1095570D811443ABF2D7B13CE9EA5F6D2D87DCBFE078E2454C24031843ABF2D7B13CE9EA5F6D2D87DCBFE078E2454C24000000000000000000000000000000000000000155050000",
5029+
"json": {
5030+
"Account":"rfwFGsxok92FwnSTY4VcEpWXhARpNVrao4",
5031+
"Asset":{
5032+
"mpt_issuance_id":"0000055543ABF2D7B13CE9EA5F6D2D87DCBFE078E2454C24"
5033+
},
5034+
"Fee":"5000000",
5035+
"LastLedgerSequence":1389,
5036+
"NetworkID":63456,
5037+
"Sequence":1367,
5038+
"SigningPubKey":"ED7FEAF5E19A09D903C13983D30BB7A67CBDA64AAB38A4C0DA6046644FFC0AF487",
5039+
"TransactionType":"VaultCreate",
5040+
"TxnSignature":"14B5CAC538F7094C2CCF1FB8A1E05068727F7A10441AA68C23CC50843EB558DA61E191430925890420EAE0F97E345F37F10D6FB89AA1E173EBF3C50B1095570D"
5041+
}
50265042
}
50275043
],
50285044
"ledgerData": [

tests/unit/core/binarycodec/types/test_account_id.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,25 @@ def test_from_value_base58(self):
2222
def test_raises_invalid_value_type(self):
2323
invalid_value = 30
2424
self.assertRaises(XRPLBinaryCodecException, AccountID.from_value, invalid_value)
25+
26+
def test_special_account_ACCOUNT_ONE(self):
27+
self.assertEqual(
28+
AccountID.from_value("0000000000000000000000000000000000000001").to_json(),
29+
"rrrrrrrrrrrrrrrrrrrrBZbvji",
30+
)
31+
32+
self.assertEqual(
33+
AccountID.from_value("rrrrrrrrrrrrrrrrrrrrBZbvji").to_hex(),
34+
AccountID.from_value("0000000000000000000000000000000000000001").to_hex(),
35+
)
36+
37+
def test_special_account_ACCOUNT_ZERO(self):
38+
self.assertEqual(
39+
AccountID.from_value("0000000000000000000000000000000000000000").to_json(),
40+
"rrrrrrrrrrrrrrrrrrrrrhoLvTp",
41+
)
42+
43+
self.assertEqual(
44+
AccountID.from_value("rrrrrrrrrrrrrrrrrrrrrhoLvTp").to_hex(),
45+
AccountID.from_value("0000000000000000000000000000000000000000").to_hex(),
46+
)

0 commit comments

Comments
 (0)