-
Notifications
You must be signed in to change notification settings - Fork 115
Support for Lending Protocol (XLS-66d) #866
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ckeshava
wants to merge
25
commits into
XRPLF:main
Choose a base branch
from
ckeshava:xls66d
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 7 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
373fb52
Models and unit tests for LP amendment; TODO: Integ tests are remaini…
ckeshava aadadbe
update integration test with LoanSet transaction
ckeshava 1db1744
add integ tests for loan-crud operations; update changelog
ckeshava b062bc3
address first batch of coderabbit AI suggestions
ckeshava faca6ae
Merge branch 'main' into xls66d
ckeshava 9083a84
fix linter errors
ckeshava c672015
update Number internal rippled type into str JSON type
ckeshava 029d65c
add unit tests and validation for loan_broker_set txn
ckeshava 1ddf348
loan_set validation and unit tests
ckeshava 4e5cf35
add hex validation for data field
ckeshava 388553d
update tests for LoanSet txn; remove start_date field
ckeshava 31b699e
integ test for Lending Protocol with IOU
ckeshava e39509f
fix the errors in STIssue codec
ckeshava 88eade6
Merge branch 'updateIssueCodec' into xls66d
ckeshava 3b47b6f
remove debug helper method
ckeshava 9c38b73
integ test for VaultCreate txn with MPToken
ckeshava f6daf47
feature: allow xrpl-py integ tests to run on XRPL Devnet; This commit…
ckeshava 9f27a07
fix: update the order of the encoding arguments in serialization of I…
ckeshava d47410a
add SAV integ test with MPToken as Vault asset
ckeshava bd2f13a
fix: big-endian format to interpret the sequence number in MPTID
ckeshava fc158fb
Update tests/integration/it_utils.py
ckeshava 2183f0a
address code rabbit suggestions
ckeshava 3162f69
Merge branch 'updateIssueCodec' into xls66d
ckeshava 48bd4e7
integ test: LendingProtocol Vault with MPToken asset
ckeshava 98288b8
Merge branch 'main' into xls66d
ckeshava File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
186 changes: 186 additions & 0 deletions
186
tests/integration/transactions/test_lending_protocol.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
import datetime | ||
|
||
from tests.integration.integration_test_case import IntegrationTestCase | ||
from tests.integration.it_utils import ( | ||
LEDGER_ACCEPT_REQUEST, | ||
fund_wallet_async, | ||
sign_and_reliable_submission_async, | ||
test_async_and_sync, | ||
) | ||
from xrpl.asyncio.transaction import autofill_and_sign, submit | ||
from xrpl.core.binarycodec import encode_for_signing | ||
from xrpl.core.keypairs.main import sign | ||
from xrpl.models import ( | ||
AccountObjects, | ||
AccountSet, | ||
AccountSetAsfFlag, | ||
LoanBrokerSet, | ||
LoanDelete, | ||
LoanManage, | ||
LoanPay, | ||
LoanSet, | ||
Transaction, | ||
VaultCreate, | ||
VaultDeposit, | ||
) | ||
from xrpl.models.currencies.xrp import XRP | ||
from xrpl.models.requests.account_objects import AccountObjectType | ||
from xrpl.models.response import ResponseStatus | ||
from xrpl.models.transactions.loan_manage import LoanManageFlag | ||
from xrpl.models.transactions.loan_set import CounterpartySignature | ||
from xrpl.models.transactions.vault_create import WithdrawalPolicy | ||
from xrpl.wallet import Wallet | ||
|
||
|
||
class TestLendingProtocolLifecycle(IntegrationTestCase): | ||
@test_async_and_sync( | ||
globals(), ["xrpl.transaction.autofill_and_sign", "xrpl.transaction.submit"] | ||
) | ||
async def test_lending_protocol_lifecycle(self, client): | ||
|
||
loan_issuer = Wallet.create() | ||
await fund_wallet_async(loan_issuer) | ||
|
||
depositor_wallet = Wallet.create() | ||
await fund_wallet_async(depositor_wallet) | ||
borrower_wallet = Wallet.create() | ||
await fund_wallet_async(borrower_wallet) | ||
|
||
# Step-0: Set up the relevant flags on the loan_issuer account -- This is | ||
# a pre-requisite for a Vault to hold the Issued Currency Asset | ||
response = await sign_and_reliable_submission_async( | ||
Patel-Raj11 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
AccountSet( | ||
account=loan_issuer.classic_address, | ||
set_flag=AccountSetAsfFlag.ASF_DEFAULT_RIPPLE, | ||
), | ||
loan_issuer, | ||
) | ||
self.assertTrue(response.is_successful()) | ||
self.assertEqual(response.result["engine_result"], "tesSUCCESS") | ||
|
||
# Step-1: Create a vault | ||
tx = VaultCreate( | ||
account=loan_issuer.address, | ||
asset=XRP(), | ||
assets_maximum="1000", | ||
withdrawal_policy=WithdrawalPolicy.VAULT_STRATEGY_FIRST_COME_FIRST_SERVE, | ||
) | ||
response = await sign_and_reliable_submission_async(tx, loan_issuer, client) | ||
self.assertEqual(response.status, ResponseStatus.SUCCESS) | ||
self.assertEqual(response.result["engine_result"], "tesSUCCESS") | ||
|
||
account_objects_response = await client.request( | ||
AccountObjects(account=loan_issuer.address, type=AccountObjectType.VAULT) | ||
) | ||
self.assertEqual(len(account_objects_response.result["account_objects"]), 1) | ||
VAULT_ID = account_objects_response.result["account_objects"][0]["index"] | ||
|
||
# Step-2: Create a loan broker | ||
tx = LoanBrokerSet( | ||
account=loan_issuer.address, | ||
vault_id=VAULT_ID, | ||
) | ||
response = await sign_and_reliable_submission_async(tx, loan_issuer, client) | ||
self.assertEqual(response.status, ResponseStatus.SUCCESS) | ||
self.assertEqual(response.result["engine_result"], "tesSUCCESS") | ||
|
||
# Step-2.1: Verify that the LoanBroker was successfully created | ||
response = await client.request( | ||
AccountObjects( | ||
account=loan_issuer.address, type=AccountObjectType.LOAN_BROKER | ||
) | ||
) | ||
self.assertEqual(len(response.result["account_objects"]), 1) | ||
LOAN_BROKER_ID = response.result["account_objects"][0]["index"] | ||
|
||
# Step-3: Deposit funds into the vault | ||
tx = VaultDeposit( | ||
account=depositor_wallet.address, | ||
vault_id=VAULT_ID, | ||
amount="100", | ||
) | ||
response = await sign_and_reliable_submission_async( | ||
tx, depositor_wallet, client | ||
) | ||
self.assertEqual(response.status, ResponseStatus.SUCCESS) | ||
self.assertEqual(response.result["engine_result"], "tesSUCCESS") | ||
|
||
# Step-5: The Loan Broker and Borrower create a Loan object with a LoanSet | ||
# transaction and the requested principal (excluding fees) is transered to | ||
# the Borrower. | ||
|
||
loan_issuer_signed_txn = await autofill_and_sign( | ||
LoanSet( | ||
account=loan_issuer.address, | ||
loan_broker_id=LOAN_BROKER_ID, | ||
principal_requested="100", | ||
start_date=int(datetime.datetime.now().timestamp()), | ||
counterparty=borrower_wallet.address, | ||
), | ||
ckeshava marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
client, | ||
loan_issuer, | ||
) | ||
|
||
# borrower agrees to the terms of the loan | ||
borrower_txn_signature = sign( | ||
encode_for_signing(loan_issuer_signed_txn.to_xrpl()), | ||
borrower_wallet.private_key, | ||
) | ||
|
||
loan_issuer_and_borrower_signature = loan_issuer_signed_txn.to_dict() | ||
loan_issuer_and_borrower_signature["counterparty_signature"] = ( | ||
CounterpartySignature( | ||
signing_pub_key=borrower_wallet.public_key, | ||
txn_signature=borrower_txn_signature, | ||
) | ||
) | ||
|
||
response = await submit( | ||
Transaction.from_dict(loan_issuer_and_borrower_signature), | ||
client, | ||
fail_hard=True, | ||
) | ||
ckeshava marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
self.assertEqual(response.status, ResponseStatus.SUCCESS) | ||
self.assertEqual(response.result["engine_result"], "tesSUCCESS") | ||
|
||
# Wait for the validation of the latest ledger | ||
await client.request(LEDGER_ACCEPT_REQUEST) | ||
|
||
# fetch the Loan object | ||
response = await client.request( | ||
AccountObjects(account=borrower_wallet.address, type=AccountObjectType.LOAN) | ||
) | ||
self.assertEqual(len(response.result["account_objects"]), 1) | ||
LOAN_ID = response.result["account_objects"][0]["index"] | ||
|
||
# Delete the Loan object | ||
tx = LoanDelete( | ||
account=loan_issuer.address, | ||
loan_id=LOAN_ID, | ||
) | ||
response = await sign_and_reliable_submission_async(tx, loan_issuer, client) | ||
self.assertEqual(response.status, ResponseStatus.SUCCESS) | ||
# Loan cannot be deleted until all the remaining payments are completed | ||
self.assertEqual(response.result["engine_result"], "tecHAS_OBLIGATIONS") | ||
|
||
# Test the LoanManage transaction | ||
tx = LoanManage( | ||
account=loan_issuer.address, | ||
loan_id=LOAN_ID, | ||
flags=LoanManageFlag.TF_LOAN_IMPAIR, | ||
) | ||
response = await sign_and_reliable_submission_async(tx, loan_issuer, client) | ||
self.assertEqual(response.status, ResponseStatus.SUCCESS) | ||
self.assertEqual(response.result["engine_result"], "tesSUCCESS") | ||
|
||
# Test the LoanPay transaction | ||
tx = LoanPay( | ||
account=borrower_wallet.address, | ||
loan_id=LOAN_ID, | ||
amount="100", | ||
) | ||
response = await sign_and_reliable_submission_async(tx, borrower_wallet, client) | ||
self.assertEqual(response.status, ResponseStatus.SUCCESS) | ||
# The borrower cannot pay the loan before the start date | ||
self.assertEqual(response.result["engine_result"], "tecTOO_SOON") |
55 changes: 55 additions & 0 deletions
55
tests/unit/models/transactions/test_loan_broker_cover_clawback.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
from unittest import TestCase | ||
kuan121 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
from xrpl.models.amounts import IssuedCurrencyAmount, MPTAmount | ||
from xrpl.models.exceptions import XRPLModelException | ||
from xrpl.models.transactions import LoanBrokerCoverClawback | ||
|
||
_SOURCE = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ" | ||
_ISSUER = "rHxTJLqdVUxjJuZEZvajXYYQJ7q8p4DhHy" | ||
|
||
|
||
class TestLoanBrokerCoverClawback(TestCase): | ||
def test_invalid_no_amount_nor_loan_broker_id_specified(self): | ||
with self.assertRaises(XRPLModelException) as error: | ||
LoanBrokerCoverClawback(account=_SOURCE) | ||
self.assertEqual( | ||
error.exception.args[0], | ||
"{'LoanBrokerCoverClawback': 'No amount or loan broker ID specified.'}", | ||
) | ||
|
||
def test_invalid_xrp_amount(self): | ||
with self.assertRaises(XRPLModelException) as error: | ||
LoanBrokerCoverClawback(account=_SOURCE, amount="10.20") | ||
self.assertEqual( | ||
error.exception.args[0], | ||
"{'amount': \"amount is <class 'str'>, expected " | ||
+ "typing.Union[xrpl.models.amounts.issued_currency_amount" | ||
+ ".IssuedCurrencyAmount, xrpl.models.amounts.mpt_amount.MPTAmount, " | ||
+ "NoneType]\", 'LoanBrokerCoverClawback:Amount': 'Amount cannot be XRP.'}", | ||
) | ||
|
||
def test_invalid_negative_amount(self): | ||
with self.assertRaises(XRPLModelException) as error: | ||
LoanBrokerCoverClawback( | ||
account=_SOURCE, | ||
amount=IssuedCurrencyAmount( | ||
issuer=_ISSUER, | ||
currency="USD", | ||
value="-10", | ||
), | ||
) | ||
self.assertEqual( | ||
error.exception.args[0], | ||
"{'LoanBrokerCoverClawback:Amount': 'Amount must be greater than 0.'}", | ||
) | ||
|
||
def test_valid_loan_broker_cover_clawback(self): | ||
tx = LoanBrokerCoverClawback( | ||
account=_SOURCE, | ||
amount=MPTAmount( | ||
mpt_issuance_id=_ISSUER, | ||
value="10.20", | ||
), | ||
loan_broker_id=_ISSUER, | ||
) | ||
self.assertTrue(tx.is_valid()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import datetime | ||
from unittest import TestCase | ||
|
||
from xrpl.models.exceptions import XRPLModelException | ||
from xrpl.models.transactions import LoanSet | ||
|
||
_SOURCE = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ" | ||
_ISSUER = "rHxTJLqdVUxjJuZEZvajXYYQJ7q8p4DhHy" | ||
|
||
|
||
class TestLoanSet(TestCase): | ||
def test_invalid_payment_interval_shorter_than_grace_period(self): | ||
with self.assertRaises(XRPLModelException) as error: | ||
LoanSet( | ||
account=_SOURCE, | ||
loan_broker_id=_ISSUER, | ||
principal_requested="100000000", | ||
start_date=int(datetime.datetime.now().timestamp()), | ||
payment_interval=65, | ||
grace_period=70, | ||
) | ||
self.assertEqual( | ||
error.exception.args[0], | ||
"{'LoanSet:GracePeriod': 'Grace period must be less than the payment " | ||
+ "interval.'}", | ||
) | ||
|
||
def test_invalid_payment_interval_too_short(self): | ||
with self.assertRaises(XRPLModelException) as error: | ||
LoanSet( | ||
account=_SOURCE, | ||
loan_broker_id=_ISSUER, | ||
principal_requested="100000000", | ||
start_date=int(datetime.datetime.now().timestamp()), | ||
payment_interval=59, | ||
) | ||
self.assertEqual( | ||
error.exception.args[0], | ||
"{'LoanSet:PaymentInterval': 'Payment interval must be at least 60 seconds." | ||
+ "'}", | ||
) | ||
|
||
def test_valid_loan_set(self): | ||
tx = LoanSet( | ||
account=_SOURCE, | ||
loan_broker_id=_ISSUER, | ||
principal_requested="100000000", | ||
start_date=int(datetime.datetime.now().timestamp()), | ||
) | ||
self.assertTrue(tx.is_valid()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.