Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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)
79 changes: 79 additions & 0 deletions tests/unit/models/transactions/test_delegate_set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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."
+ "'}",
)
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
"""
The account that wants to authorize another account.

:meta hide-value:
"""

authorize: str = REQUIRED # type: ignore
"""
The authorized account.

: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