Skip to content

Commit 2b65eb2

Browse files
khancodemvadari
authored andcommitted
feat: add AMM support (XRPLF#422)
* add AMMInstanceCreate * update definitions to get AMMInstanceCreate working * fix lint errors * add unit test for Instance Create * fix lint errors * fix definitions and change AmmCreate to AMMInstanceCreate * fix lint error * add AMMInfo * update AMMInfo docstring * add docstring to AMMInfo params * update definitions.json * remove AMMAccount from AMMInstanceCreate model * add AMMDeposit * fix lint errors * add AMMWithdraw * add AMMVote * add AMMBid * fix typo * update AMMBid test * add MaxSlotPrice param to AMMBid * refactor test * update definitions and replace AMMHash with AMMID * update lptokens type to IssuedCurrencyAmount * assert with error message * assert with error messages for AMMInstanceCreate and amm_info * move to_xrpl tests to test_base_model * rename lptokens to lp_tokens * update amm_info request params to be in snake_case * update docstrings * reorder SPECIAL_CAMELCASE_STRINGS to alphabetical order * refactor ABBREVIATIONS to be set in one place * rename LPTokens to LPToken * update CHANGELOG.md * fix typo * fix lint error * refactor max trading fee to be in one place * update amm_bid error message * add AuthAccount base model for AMMBid * update definitions to fix AMM in LEDGER_ENTRY_TYPES * update CHANGELOG.md to specify XLS-30 * update wording on AMMDeposit & AMMWithdraw validation errors * add negative FeeVal check * add negative TradingFee check * fix lint error * export AuthAccount model * add AuthAccount and refactor special case models check * revert Path and _value_to_tx_json() changes * fix AuthAccount capitalization issues (XRPLF#432) Co-authored-by: Omar Khan <[email protected]> * add AMMBid codec-fixture * add AMMInstanceCreate codec-fixture * update definitions.json with different AuthAccounts number * remove AMM codec-fixtures * Change amm_info asset parameters to Currency type * API name changes with updated definitions.json * rename amm_info param asset1 -> asset * change AMM_MAX_TRADING_FEE to 1% and rename fee_val to trading_fee * rename MinBidPrice -> BidMin and MaxBidPrice -> BidMax * update definitions to change Asset & Asset2 nth values to 3 & 4 * Use asset/asset2 instead of amm_id for Deposit/Withdraw/Bid/Vote * update definitions * add Issue type * add flags to AMM deposit & withdraw * add Issue model * add Issue type to models with asset & asset2; remove amm_id * resolve lint errors * rename LPToken in amm deposit & withdraw * update docstrings * add AMM codec-fixtures * add one asset withdraw & withdraw all tests * update definitions.json with refactored error codes * add Owner Reserve Fee for AMMCreate transaction * refactor asset pair to be Currency type * update amm_info asset pair to be Currency type and remove Issue model * update definitions and codec-fixtures * update DiscountedFee definition * update definitions and codec-fixtures * update definitions * remove sidechain method * small refactor * update docstrings * refactor _value_to_tx_json to remove special case for auth_account * refactor AuthAccount to be a NestedModel * remove test_base_model tests for amm * add test_to_xrpl_auth_accounts * fix indentation with tests * update definitions * add AMMDelete * add AMMDelete docstring --------- Co-authored-by: Mayukha Vadari <[email protected]>
1 parent f109d96 commit 2b65eb2

30 files changed

+1708
-36
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- Function to parse the final account balances from a transaction's metadata
1414
- Function to parse order book changes from a transaction's metadata
1515
- Support for Ed25519 seeds that don't use the `sEd` prefix
16+
- Support for Automated Market Maker (AMM) transactions and requests as defined in XLS-30.
1617
- Add docs to`get_account_transactions` explaining how to allow pagination through all transaction history [#462]
1718
- Common field `ticket_sequence` to Transaction class
1819

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

Lines changed: 222 additions & 13 deletions
Large diffs are not rendered by default.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from unittest import TestCase
2+
3+
from xrpl.models.currencies import XRP, IssuedCurrency
4+
from xrpl.models.requests import AMMInfo
5+
6+
_ASSET = XRP()
7+
_ASSET_2 = IssuedCurrency(currency="USD", issuer="rN6zcSynkRnf8zcgTVrRL8K7r4ovE7J4Zj")
8+
9+
10+
class TestAMMInfo(TestCase):
11+
def test_asset_asset2(self):
12+
request = AMMInfo(
13+
asset=_ASSET,
14+
asset2=_ASSET_2,
15+
)
16+
self.assertTrue(request.is_valid())

tests/unit/models/test_base_model.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from xrpl.models import XRPLModelException
66
from xrpl.models.amounts import IssuedCurrencyAmount
7+
from xrpl.models.currencies import XRP, IssuedCurrency
78
from xrpl.models.requests import (
89
AccountChannels,
910
BookOffers,
@@ -16,6 +17,8 @@
1617
SubmitOnly,
1718
)
1819
from xrpl.models.transactions import (
20+
AMMBid,
21+
AuthAccount,
1922
CheckCreate,
2023
Memo,
2124
Payment,
@@ -597,3 +600,72 @@ def test_to_xrpl_signer(self):
597600
],
598601
}
599602
self.assertEqual(tx.to_xrpl(), expected)
603+
604+
def test_to_xrpl_auth_accounts(self):
605+
tx = AMMBid(
606+
account="r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ",
607+
asset=XRP(),
608+
asset2=IssuedCurrency(
609+
currency="ETH", issuer="rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW"
610+
),
611+
bid_min=IssuedCurrencyAmount(
612+
currency="5475B6C930B7BDD81CDA8FBA5CED962B11218E5A",
613+
issuer="r3628pXjRqfw5zfwGfhSusjZTvE3BoxEBw",
614+
value="25",
615+
),
616+
bid_max=IssuedCurrencyAmount(
617+
currency="5475B6C930B7BDD81CDA8FBA5CED962B11218E5A",
618+
issuer="r3628pXjRqfw5zfwGfhSusjZTvE3BoxEBw",
619+
value="35",
620+
),
621+
auth_accounts=[
622+
AuthAccount(account="rNZdsTBP5tH1M6GHC6bTreHAp6ouP8iZSh"),
623+
AuthAccount(account="rfpFv97Dwu89FTyUwPjtpZBbuZxTqqgTmH"),
624+
AuthAccount(account="rzzYHPGb8Pa64oqxCzmuffm122bitq3Vb"),
625+
AuthAccount(account="rhwxHxaHok86fe4LykBom1jSJ3RYQJs1h4"),
626+
],
627+
)
628+
expected = {
629+
"Account": "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ",
630+
"Asset": {"currency": "XRP"},
631+
"Asset2": {
632+
"currency": "ETH",
633+
"issuer": "rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW",
634+
},
635+
"BidMin": {
636+
"currency": "5475B6C930B7BDD81CDA8FBA5CED962B11218E5A",
637+
"issuer": "r3628pXjRqfw5zfwGfhSusjZTvE3BoxEBw",
638+
"value": "25",
639+
},
640+
"BidMax": {
641+
"currency": "5475B6C930B7BDD81CDA8FBA5CED962B11218E5A",
642+
"issuer": "r3628pXjRqfw5zfwGfhSusjZTvE3BoxEBw",
643+
"value": "35",
644+
},
645+
"AuthAccounts": [
646+
{
647+
"AuthAccount": {
648+
"Account": "rNZdsTBP5tH1M6GHC6bTreHAp6ouP8iZSh",
649+
}
650+
},
651+
{
652+
"AuthAccount": {
653+
"Account": "rfpFv97Dwu89FTyUwPjtpZBbuZxTqqgTmH",
654+
}
655+
},
656+
{
657+
"AuthAccount": {
658+
"Account": "rzzYHPGb8Pa64oqxCzmuffm122bitq3Vb",
659+
}
660+
},
661+
{
662+
"AuthAccount": {
663+
"Account": "rhwxHxaHok86fe4LykBom1jSJ3RYQJs1h4",
664+
}
665+
},
666+
],
667+
"TransactionType": "AMMBid",
668+
"SigningPubKey": "",
669+
"Flags": 0,
670+
}
671+
self.assertEqual(tx.to_xrpl(), expected)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from unittest import TestCase
2+
3+
from xrpl.models.amounts import IssuedCurrencyAmount
4+
from xrpl.models.currencies import XRP, IssuedCurrency
5+
from xrpl.models.exceptions import XRPLModelException
6+
from xrpl.models.transactions import AMMBid, AuthAccount
7+
8+
_ACCOUNT = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ"
9+
_ASSET = XRP()
10+
_ASSET2 = IssuedCurrency(currency="ETH", issuer="rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW")
11+
_AUTH_ACCOUNTS = [
12+
AuthAccount(
13+
account="rNZdsTBP5tH1M6GHC6bTreHAp6ouP8iZSh",
14+
),
15+
AuthAccount(
16+
account="rfpFv97Dwu89FTyUwPjtpZBbuZxTqqgTmH",
17+
),
18+
AuthAccount(
19+
account="rzzYHPGb8Pa64oqxCzmuffm122bitq3Vb",
20+
),
21+
AuthAccount(
22+
account="rhwxHxaHok86fe4LykBom1jSJ3RYQJs1h4",
23+
),
24+
]
25+
_LPTOKEN_CURRENCY = "5475B6C930B7BDD81CDA8FBA5CED962B11218E5A"
26+
_LPTOKEN_ISSUER = "r3628pXjRqfw5zfwGfhSusjZTvE3BoxEBw"
27+
28+
29+
class TestAMMBid(TestCase):
30+
def test_tx_valid(self):
31+
tx = AMMBid(
32+
account=_ACCOUNT,
33+
asset=_ASSET,
34+
asset2=_ASSET2,
35+
bid_min=IssuedCurrencyAmount(
36+
currency=_LPTOKEN_CURRENCY,
37+
issuer=_LPTOKEN_ISSUER,
38+
value="25",
39+
),
40+
bid_max=IssuedCurrencyAmount(
41+
currency=_LPTOKEN_CURRENCY,
42+
issuer=_LPTOKEN_ISSUER,
43+
value="35",
44+
),
45+
auth_accounts=_AUTH_ACCOUNTS,
46+
)
47+
self.assertTrue(tx.is_valid())
48+
49+
def test_auth_accounts_length_error(self):
50+
auth_accounts = _AUTH_ACCOUNTS.copy()
51+
auth_accounts.append(
52+
AuthAccount(
53+
account="r3X6noRsvaLapAKCG78zAtWcbhB3sggS1s",
54+
),
55+
)
56+
with self.assertRaises(XRPLModelException) as error:
57+
AMMBid(
58+
account=_ACCOUNT,
59+
asset=_ASSET,
60+
asset2=_ASSET2,
61+
auth_accounts=auth_accounts,
62+
)
63+
self.assertEqual(
64+
error.exception.args[0],
65+
"{'auth_accounts': 'Length must not be greater than 4'}",
66+
)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from sys import maxsize
2+
from unittest import TestCase
3+
4+
from xrpl.models.amounts import IssuedCurrencyAmount
5+
from xrpl.models.exceptions import XRPLModelException
6+
from xrpl.models.transactions import AMMCreate
7+
8+
_ACCOUNT = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ"
9+
_IOU_ISSUER = "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"
10+
11+
12+
class TestAMMCreate(TestCase):
13+
def test_tx_is_valid(self):
14+
tx = AMMCreate(
15+
account=_ACCOUNT,
16+
amount="1000",
17+
amount2=IssuedCurrencyAmount(
18+
currency="USD", issuer=_IOU_ISSUER, value="1000"
19+
),
20+
trading_fee=12,
21+
)
22+
self.assertTrue(tx.is_valid())
23+
24+
def test_trading_fee_too_high(self):
25+
with self.assertRaises(XRPLModelException) as error:
26+
AMMCreate(
27+
account=_ACCOUNT,
28+
amount="1000",
29+
amount2=IssuedCurrencyAmount(
30+
currency="USD", issuer=_IOU_ISSUER, value="1000"
31+
),
32+
trading_fee=maxsize,
33+
)
34+
self.assertEqual(
35+
error.exception.args[0],
36+
"{'trading_fee': 'Must be between 0 and 1000'}",
37+
)
38+
39+
def test_trading_fee_negative_number(self):
40+
with self.assertRaises(XRPLModelException) as error:
41+
AMMCreate(
42+
account=_ACCOUNT,
43+
amount="1000",
44+
amount2=IssuedCurrencyAmount(
45+
currency="USD", issuer=_IOU_ISSUER, value="1000"
46+
),
47+
trading_fee=-1,
48+
)
49+
self.assertEqual(
50+
error.exception.args[0],
51+
"{'trading_fee': 'Must be between 0 and 1000'}",
52+
)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from unittest import TestCase
2+
3+
from xrpl.models.currencies import XRP, IssuedCurrency
4+
from xrpl.models.transactions import AMMDelete
5+
6+
7+
class TestAMMDeposit(TestCase):
8+
def test_tx_valid(self):
9+
tx = AMMDelete(
10+
account="r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ",
11+
sequence=1337,
12+
asset=XRP(),
13+
asset2=IssuedCurrency(
14+
currency="ETH", issuer="rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW"
15+
),
16+
)
17+
self.assertTrue(tx.is_valid())
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
from unittest import TestCase
2+
3+
from xrpl.models.amounts import IssuedCurrencyAmount
4+
from xrpl.models.currencies import XRP, IssuedCurrency
5+
from xrpl.models.exceptions import XRPLModelException
6+
from xrpl.models.transactions import AMMDeposit
7+
from xrpl.models.transactions.amm_deposit import AMMDepositFlag
8+
9+
_ACCOUNT = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ"
10+
_ASSET = XRP()
11+
_ASSET2 = IssuedCurrency(currency="ETH", issuer="rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW")
12+
_AMOUNT = "1000"
13+
_LPTOKEN_CURRENCY = "B3813FCAB4EE68B3D0D735D6849465A9113EE048"
14+
_LPTOKEN_ISSUER = "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg"
15+
16+
17+
class TestAMMDeposit(TestCase):
18+
def test_tx_valid_xrpl_lptokenout(self):
19+
tx = AMMDeposit(
20+
account=_ACCOUNT,
21+
sequence=1337,
22+
asset=_ASSET,
23+
asset2=_ASSET2,
24+
lp_token_out=IssuedCurrencyAmount(
25+
currency=_LPTOKEN_CURRENCY,
26+
issuer=_LPTOKEN_ISSUER,
27+
value=_AMOUNT,
28+
),
29+
flags=AMMDepositFlag.TF_LP_TOKEN,
30+
)
31+
self.assertTrue(tx.is_valid())
32+
33+
def test_tx_valid_amount(self):
34+
tx = AMMDeposit(
35+
account=_ACCOUNT,
36+
sequence=1337,
37+
asset=_ASSET,
38+
asset2=_ASSET2,
39+
amount=_AMOUNT,
40+
flags=AMMDepositFlag.TF_SINGLE_ASSET,
41+
)
42+
self.assertTrue(tx.is_valid())
43+
44+
def test_tx_valid_amount_amount2(self):
45+
tx = AMMDeposit(
46+
account=_ACCOUNT,
47+
sequence=1337,
48+
asset=_ASSET,
49+
asset2=_ASSET2,
50+
amount=_AMOUNT,
51+
amount2=IssuedCurrencyAmount(
52+
currency=_ASSET2.currency, issuer=_ASSET2.issuer, value="500"
53+
),
54+
flags=AMMDepositFlag.TF_TWO_ASSET,
55+
)
56+
self.assertTrue(tx.is_valid())
57+
58+
def test_tx_valid_amount_lptokenout(self):
59+
tx = AMMDeposit(
60+
account=_ACCOUNT,
61+
sequence=1337,
62+
asset=_ASSET,
63+
asset2=_ASSET2,
64+
amount=_AMOUNT,
65+
lp_token_out=IssuedCurrencyAmount(
66+
currency=_LPTOKEN_CURRENCY,
67+
issuer=_LPTOKEN_ISSUER,
68+
value="500",
69+
),
70+
flags=AMMDepositFlag.TF_ONE_ASSET_LP_TOKEN,
71+
)
72+
self.assertTrue(tx.is_valid())
73+
74+
def test_tx_valid_amount_eprice(self):
75+
tx = AMMDeposit(
76+
account=_ACCOUNT,
77+
sequence=1337,
78+
asset=_ASSET,
79+
asset2=_ASSET2,
80+
amount=_AMOUNT,
81+
e_price="25",
82+
flags=AMMDepositFlag.TF_LIMIT_LP_TOKEN,
83+
)
84+
self.assertTrue(tx.is_valid())
85+
86+
def test_undefined_amount_undefined_lptokenout_invalid_combo(self):
87+
with self.assertRaises(XRPLModelException) as error:
88+
AMMDeposit(
89+
account=_ACCOUNT,
90+
sequence=1337,
91+
asset=_ASSET,
92+
asset2=_ASSET2,
93+
)
94+
self.assertEqual(
95+
error.exception.args[0],
96+
"{'AMMDeposit': 'Must set at least `lp_token_out` or `amount`'}",
97+
)
98+
99+
def test_undefined_amount_defined_amount2_invalid_combo(self):
100+
with self.assertRaises(XRPLModelException) as error:
101+
AMMDeposit(
102+
account=_ACCOUNT,
103+
sequence=1337,
104+
asset=_ASSET,
105+
asset2=_ASSET2,
106+
amount2=IssuedCurrencyAmount(
107+
currency=_ASSET2.currency, issuer=_ASSET2.issuer, value="500"
108+
),
109+
)
110+
self.assertEqual(
111+
error.exception.args[0],
112+
"{'AMMDeposit': 'Must set `amount` with `amount2`'}",
113+
)
114+
115+
def test_undefined_amount_defined_eprice_invalid_combo(self):
116+
with self.assertRaises(XRPLModelException) as error:
117+
AMMDeposit(
118+
account=_ACCOUNT,
119+
sequence=1337,
120+
asset=_ASSET,
121+
asset2=_ASSET2,
122+
e_price="25",
123+
)
124+
self.assertEqual(
125+
error.exception.args[0],
126+
"{'AMMDeposit': 'Must set `amount` with `e_price`'}",
127+
)

0 commit comments

Comments
 (0)