Skip to content

Transaction model, unit and integ tests for Delegate XLS-74d amendment #833

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
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .ci-config/rippled.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ fixEnforceNFTokenTrustline
fixReducedOffersV2
DeepFreeze
PermissionedDomains
PermissionDelegation

# This section can be used to simulate various FeeSettings scenarios for rippled node in standalone mode
[voting]
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- Improved validation for models to also check param types
- Support for `Account Permission` and `Account Permission Delegation` (XLS-74d, XLS-75d)

## [4.1.0] - 2025-2-13

Expand Down
121 changes: 121 additions & 0 deletions tests/integration/transactions/test_delegate_set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
from tests.integration.integration_test_case import IntegrationTestCase
from tests.integration.it_utils import (
fund_wallet_async,
sign_and_reliable_submission_async,
test_async_and_sync,
)
from xrpl.models.requests import LedgerEntry
from xrpl.models.requests.ledger_entry import Delegate
from xrpl.models.response import ResponseStatus
from xrpl.models.transactions import DelegateSet, Payment
from xrpl.models.transactions.delegate_set import Permission
from xrpl.utils import xrp_to_drops
from xrpl.wallet.main import Wallet


class TestDelegateSet(IntegrationTestCase):
@test_async_and_sync(globals())
async def test_delegation_with_no_permission(self, client):
# Note: Using WALLET, DESTINATION accounts could pollute the test results
alice = Wallet.create()
await fund_wallet_async(alice)
bob = Wallet.create()
await fund_wallet_async(bob)
carol = Wallet.create()
await fund_wallet_async(carol)

# Use bob's account to execute a transaction on behalf of alice
payment = Payment(
account=alice.address,
amount=xrp_to_drops(1),
destination=carol.address,
delegate=bob.address,
)
response = await sign_and_reliable_submission_async(
payment, bob, client, check_fee=False
)
self.assertEqual(response.status, ResponseStatus.SUCCESS)

# The lack of AccountPermissionSet transaction will result in a tecNO_PERMISSION
self.assertEqual(response.result["engine_result"], "tecNO_PERMISSION")

@test_async_and_sync(globals())
async def test_delegate_set_workflow(self, client):
# Note: Using WALLET, DESTINATION accounts could pollute the test results
alice = Wallet.create()
await fund_wallet_async(alice)
bob = Wallet.create()
await fund_wallet_async(bob)
carol = Wallet.create()
await fund_wallet_async(carol)

delegate_set = DelegateSet(
account=alice.address,
authorize=bob.address,
# Authorize bob account to execute Payment transactions on
# behalf of alice's account.
# Note: Payment transaction has a TransactionType of 0
permissions=[Permission(permission_value=(1 + 0))],
)
response = await sign_and_reliable_submission_async(
delegate_set, alice, client, check_fee=False
)
self.assertEqual(response.status, ResponseStatus.SUCCESS)
self.assertEqual(response.result["engine_result"], "tesSUCCESS")

# Use the bob's account to execute a transaction on behalf of alice
payment = Payment(
account=alice.address,
amount=xrp_to_drops(1),
destination=carol.address,
delegate=bob.address,
)
response = await sign_and_reliable_submission_async(
payment, bob, client, check_fee=False
)
self.assertEqual(response.status, ResponseStatus.SUCCESS)
self.assertEqual(response.result["engine_result"], "tesSUCCESS")

# Validate that the transaction was signed by bob
self.assertEqual(response.result["tx_json"]["Account"], alice.address)
self.assertEqual(response.result["tx_json"]["Delegate"], bob.address)
self.assertEqual(response.result["tx_json"]["SigningPubKey"], bob.public_key)

@test_async_and_sync(globals())
async def test_fetch_delegate_ledger_entry(self, client):
# Note: Using WALLET, DESTINATION accounts could pollute the test results
alice = Wallet.create()
await fund_wallet_async(alice)
bob = Wallet.create()
await fund_wallet_async(bob)

delegate_set = DelegateSet(
account=alice.address,
authorize=bob.address,
# Authorize bob's account to execute Payment transactions on
# behalf of alice's account.
# Note: Payment transaction has a TransactionType of 0
permissions=[Permission(permission_value=(1 + 0))],
)
response = await sign_and_reliable_submission_async(
delegate_set, alice, client, check_fee=False
)
self.assertEqual(response.status, ResponseStatus.SUCCESS)
self.assertEqual(response.result["engine_result"], "tesSUCCESS")

ledger_entry_response = await client.request(
LedgerEntry(
delegate=Delegate(
account=alice.address,
authorize=bob.address,
),
)
)
self.assertTrue(ledger_entry_response.is_successful())
self.assertEqual(
ledger_entry_response.result["node"]["LedgerEntryType"],
"Delegate",
)
self.assertEqual(ledger_entry_response.result["node"]["Account"], alice.address)
self.assertEqual(ledger_entry_response.result["node"]["Authorize"], bob.address)
self.assertEqual(len(ledger_entry_response.result["node"]["Permissions"]), 1)
78 changes: 78 additions & 0 deletions tests/unit/models/transactions/test_delegate_set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from unittest import TestCase

from xrpl.models.exceptions import XRPLModelException
from xrpl.models.transactions import DelegateSet
from xrpl.models.transactions.delegate_set import (
GRANULAR_PERMISSIONS,
PERMISSION_MAX_LENGTH,
Permission,
)

_ACCOUNT = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ"
_DELEGATED_ACCOUNT = "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW"


class TestAccountPermissionSet(TestCase):
def test_delegate_set(self):
tx = DelegateSet(
account=_ACCOUNT,
authorize=_DELEGATED_ACCOUNT,
permissions=[Permission(permission_value=1)],
)
self.assertTrue(tx.is_valid())

def test_delegate_set_granular_permission(self):
tx = DelegateSet(
account=_ACCOUNT,
authorize=_DELEGATED_ACCOUNT,
permissions=[
Permission(permission_value=GRANULAR_PERMISSIONS["PaymentMint"])
],
)
self.assertTrue(tx.is_valid())

def test_long_permissions_list(self):
with self.assertRaises(XRPLModelException) as error:
DelegateSet(
account=_ACCOUNT,
authorize=_DELEGATED_ACCOUNT,
permissions=[
Permission(permission_value=i)
for i in range(PERMISSION_MAX_LENGTH + 1)
],
)
self.assertEqual(
error.exception.args[0],
"{'permissions': 'Length of `permissions` list is greater than "
+ str(PERMISSION_MAX_LENGTH)
+ ".'}",
)

def test_duplicate_permission_value(self):
with self.assertRaises(XRPLModelException) as error:
DelegateSet(
account=_ACCOUNT,
authorize=_DELEGATED_ACCOUNT,
permissions=[
Permission(permission_value=1),
Permission(permission_value=1),
],
)
self.assertEqual(
error.exception.args[0],
"{'permissions': 'Duplicate permission value in `permissions` list.'}",
)

def test_account_and_delegate_are_the_same(self):
with self.assertRaises(XRPLModelException) as error:
DelegateSet(
account=_ACCOUNT,
authorize=_ACCOUNT,
permissions=[
Permission(permission_value=1),
],
)
self.assertEqual(
error.exception.args[0],
"{'account_addresses': 'Field `authorize` and `account` must be different.'}",
Copy link
Contributor

@coderabbitai coderabbitai bot Apr 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix line length to comply with flake8

The line exceeds the maximum length limit (90 > 88 characters).

-            "{'account_addresses': 'Field `authorize` and `account` must be different.'}",
+            "{'account_addresses': 'Field `authorize` and `account` must be different.'}",

Consider either:

  1. Breaking this line into multiple lines, or
  2. Using a shorter error message in the test
-            "{'account_addresses': 'Field `authorize` and `account` must be different.'}",
+            "{'account_addresses': 'Field `authorize` and `account` must be "
+            "different.'}",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"{'account_addresses': 'Field `authorize` and `account` must be different.'}",
"{'account_addresses': 'Field `authorize` and `account` must be "
"different.'}",
🧰 Tools
🪛 GitHub Actions: Unit test

[error] 77-77: flake8: line too long (90 > 88 characters) (E501)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The linter (and flake8) haven't thrown any errors over this line. This is not a necessary update.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ckeshava I understand you're not seeing this error locally, but the GitHub Actions CI shows a flake8 error for this line: "flake8: line too long (90 > 88 characters) (E501)".

This discrepancy might be due to different flake8 configurations between your local environment and the CI pipeline. The CI is enforcing an 88-character limit, which this line exceeds at 90 characters.

Would you like me to help investigate the flake8 configuration in the project to resolve this inconsistency?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated in 1d4b3d8

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ckeshava Thank you for confirming that you've addressed the line length issue in commit 1d4b3d8. Great work on resolving this flake8 warning!


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

)
41 changes: 41 additions & 0 deletions xrpl/core/binarycodec/definitions/definitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,16 @@
"type": "UInt32"
}
],
[
"PermissionValue",
{
"isSerialized": true,
"isSigningField": true,
"isVLEncoded": false,
"nth": 52,
"type": "UInt32"
}
],
[
"IndexNext",
{
Expand Down Expand Up @@ -1950,6 +1960,16 @@
"type": "AccountID"
}
],
[
"Delegate",
{
"isSerialized": true,
"isSigningField": true,
"isVLEncoded": true,
"nth": 12,
"type": "AccountID"
}
],
[
"HookAccount",
{
Expand Down Expand Up @@ -2160,6 +2180,16 @@
"type": "STObject"
}
],
[
"Permission",
{
"isSerialized": true,
"isSigningField": true,
"isVLEncoded": false,
"nth": 15,
"type": "STObject"
}
],
[
"Signer",
{
Expand Down Expand Up @@ -2549,6 +2579,15 @@
"type": "STArray"
}
],
[
"Permissions", {
"nth": 29,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "STArray"
}
],
[
"CloseResolution",
{
Expand Down Expand Up @@ -2868,6 +2907,7 @@
"Check": 67,
"DID": 73,
"DepositPreauth": 112,
"Delegate": 131,
"DirectoryNode": 100,
"Escrow": 117,
"FeeSettings": 115,
Expand Down Expand Up @@ -3088,6 +3128,7 @@
"CredentialCreate": 58,
"CredentialAccept": 59,
"CredentialDelete": 60,
"DelegateSet": 64,
"DIDDelete": 50,
"DIDSet": 49,
"DepositPreauth": 19,
Expand Down
25 changes: 25 additions & 0 deletions xrpl/models/requests/ledger_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,29 @@ class Credential(BaseModel):
"""The type of the credential, as issued."""


@require_kwargs_on_init
@dataclass(frozen=True, **KW_ONLY_DATACLASS)
class Delegate(BaseModel):
"""
Required fields for requesting a Delegate ledger object if not querying by
object ID.
"""

account: str = REQUIRED # type: ignore
"""
This field is required.

:meta hide-value:
"""

authorize: str = REQUIRED # type: ignore
"""
This field is required.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add description for this field?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added description in 4b1491b


:meta hide-value:
"""


@require_kwargs_on_init
@dataclass(frozen=True, **KW_ONLY_DATACLASS)
class DepositPreauth(BaseModel):
Expand Down Expand Up @@ -304,6 +327,7 @@ class LedgerEntry(Request, LookupByLedgerRequest):
account_root: Optional[str] = None
check: Optional[str] = None
credential: Optional[Union[str, Credential]] = None
delegate: Optional[Union[str, Delegate]] = None
deposit_preauth: Optional[Union[str, DepositPreauth]] = None
did: Optional[str] = None
directory: Optional[Union[str, Directory]] = None
Expand Down Expand Up @@ -338,6 +362,7 @@ def _get_errors(self: Self) -> Dict[str, str]:
self.account_root,
self.check,
self.credential,
self.delegate,
self.deposit_preauth,
self.did,
self.directory,
Expand Down
2 changes: 2 additions & 0 deletions xrpl/models/transactions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from xrpl.models.transactions.credential_accept import CredentialAccept
from xrpl.models.transactions.credential_create import CredentialCreate
from xrpl.models.transactions.credential_delete import CredentialDelete
from xrpl.models.transactions.delegate_set import DelegateSet
from xrpl.models.transactions.deposit_preauth import DepositPreauth
from xrpl.models.transactions.did_delete import DIDDelete
from xrpl.models.transactions.did_set import DIDSet
Expand Down Expand Up @@ -141,6 +142,7 @@
"CredentialCreate",
"CredentialDelete",
"DepositPreauth",
"DelegateSet",
"DIDDelete",
"DIDSet",
"EscrowCancel",
Expand Down
Loading
Loading