diff --git a/docs/en/api.rst b/docs/en/api.rst index a914aea6..9e000a3f 100644 --- a/docs/en/api.rst +++ b/docs/en/api.rst @@ -425,6 +425,25 @@ SetOptions .. autoclass:: stellar_sdk.operation.set_options.Flag :members: +CreateClaimableBalance +---------------------- +.. autoclass:: stellar_sdk.operation.CreateClaimableBalance + :members: to_xdr_object, from_xdr_object + +.. autoclass:: stellar_sdk.operation.Claimant + :members: + +.. autoclass:: stellar_sdk.operation.ClaimPredicate + :members: + +.. autoclass:: stellar_sdk.operation.create_claimable_balance.ClaimPredicateType + :members: + +ClaimClaimableBalance +--------------------- +.. autoclass:: stellar_sdk.operation.ClaimClaimableBalance + :members: to_xdr_object, from_xdr_object + Price ^^^^^ diff --git a/docs/zh_CN/api.rst b/docs/zh_CN/api.rst index f7e0b4ad..35533631 100644 --- a/docs/zh_CN/api.rst +++ b/docs/zh_CN/api.rst @@ -425,6 +425,25 @@ SetOptions .. autoclass:: stellar_sdk.operation.set_options.Flag :members: +CreateClaimableBalance +---------------------- +.. autoclass:: stellar_sdk.operation.CreateClaimableBalance + :members: to_xdr_object, from_xdr_object + +.. autoclass:: stellar_sdk.operation.Claimant + :members: + +.. autoclass:: stellar_sdk.operation.ClaimPredicate + :members: + +.. autoclass:: stellar_sdk.operation.create_claimable_balance.ClaimPredicateType + :members: + +ClaimClaimableBalance +--------------------- +.. autoclass:: stellar_sdk.operation.ClaimClaimableBalance + :members: to_xdr_object, from_xdr_object + Price ^^^^^ diff --git a/stellar_sdk/operation/__init__.py b/stellar_sdk/operation/__init__.py index c9f85abe..159d2c9c 100644 --- a/stellar_sdk/operation/__init__.py +++ b/stellar_sdk/operation/__init__.py @@ -2,7 +2,9 @@ from .allow_trust import AllowTrust, TrustLineEntryFlag from .bump_sequence import BumpSequence from .change_trust import ChangeTrust +from .claim_claimable_balance import ClaimClaimableBalance from .create_account import CreateAccount +from .create_claimable_balance import CreateClaimableBalance, Claimant, ClaimPredicate from .create_passive_sell_offer import CreatePassiveSellOffer from .inflation import Inflation from .manage_buy_offer import ManageBuyOffer @@ -21,7 +23,11 @@ "AllowTrust", "BumpSequence", "ChangeTrust", + "ClaimClaimableBalance", "CreateAccount", + "CreateClaimableBalance", + "Claimant", + "ClaimPredicate", "CreatePassiveSellOffer", "Inflation", "ManageBuyOffer", diff --git a/stellar_sdk/operation/claim_claimable_balance.py b/stellar_sdk/operation/claim_claimable_balance.py new file mode 100644 index 00000000..b6076d43 --- /dev/null +++ b/stellar_sdk/operation/claim_claimable_balance.py @@ -0,0 +1,59 @@ +import base64 +import binascii + +from .operation import Operation +from ..xdr import Xdr +from ..xdr.StellarXDR_type import ClaimableBalanceID + + +class ClaimClaimableBalance(Operation): + """The :class:`ClaimClaimableBalance` object, which represents a ClaimClaimableBalance + operation on Stellar's network. + + Claims a ClaimableBalanceEntry and adds the amount of asset on the entry to the source account. + + See `Claim Claimable Balance Documentation + _`. + + Threshold: Low + + :param balance_id: The claimable balance id to be claimed. + :param source: The source account (defaults to transaction source). + """ + + def __init__(self, balance_id: str, source: str = None,) -> None: + super().__init__(source) + self.balance_id: str = balance_id + + @classmethod + def type_code(cls) -> int: + return Xdr.const.CLAIM_CLAIMABLE_BALANCE + + def _to_operation_body(self) -> Xdr.nullclass: + body = Xdr.nullclass() + body.type = Xdr.const.CLAIM_CLAIMABLE_BALANCE + + balance_id_bytes: bytes = binascii.unhexlify(self.balance_id) + balance_id = ClaimableBalanceID.from_xdr(base64.b64encode(balance_id_bytes)) + claim_claimable_balance_op = Xdr.types.ClaimClaimableBalanceOp( + balanceID=balance_id + ) + + body.claimClaimableBalanceOp = claim_claimable_balance_op + return body + + @classmethod + def from_xdr_object( + cls, operation_xdr_object: Xdr.types.Operation + ) -> "ClaimClaimableBalance": + """Creates a :class:`ClaimClaimableBalance` object from an XDR Operation + object. + """ + source = Operation.get_source_from_xdr_obj(operation_xdr_object) + balance_id = base64.b64decode( + operation_xdr_object.body.claimClaimableBalanceOp.to_xdr() + ) + balance_id = binascii.hexlify(balance_id).decode() + op = cls(balance_id=balance_id, source=source) + op._source_muxed = Operation.get_source_muxed_from_xdr_obj(operation_xdr_object) + return op diff --git a/stellar_sdk/operation/create_claimable_balance.py b/stellar_sdk/operation/create_claimable_balance.py new file mode 100644 index 00000000..eec089de --- /dev/null +++ b/stellar_sdk/operation/create_claimable_balance.py @@ -0,0 +1,349 @@ +from decimal import Decimal +from enum import IntEnum +from typing import Union, List, Optional + +from .operation import Operation +from ..asset import Asset +from ..keypair import Keypair +from ..strkey import StrKey +from ..utils import pack_xdr_array +from ..xdr import Xdr +from ..exceptions import ValueError +from .utils import check_amount + +__all__ = ["ClaimPredicate", "Claimant", "CreateClaimableBalance"] + + +class ClaimPredicateType(IntEnum): + """Currently supported claim predicate types. + """ + + CLAIM_PREDICATE_UNCONDITIONAL = 0 + CLAIM_PREDICATE_AND = 1 + CLAIM_PREDICATE_OR = 2 + CLAIM_PREDICATE_NOT = 3 + CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME = 4 + CLAIM_PREDICATE_BEFORE_RELATIVE_TIME = 5 + + +class ClaimPredicate: + """The :class:`ClaimPredicate` object, which represents a ClaimPredicate on Stellar's network. + + **We do not recommend that you build it through the constructor, please use the helper function.** + + :param claim_predicate_type: Type of ClaimPredicate. + :param and_predicates: The ClaimPredicates. + :param or_predicates: The ClaimPredicates. + :param not_predicate: The ClaimPredicate. + :param abs_before: Unix epoch. + :param rel_before: seconds since closeTime of the ledger in which the ClaimableBalanceEntry was created. + """ + + def __init__( + self, + claim_predicate_type: ClaimPredicateType, + and_predicates: Optional[List["ClaimPredicate"]], + or_predicates: Optional[List["ClaimPredicate"]], + not_predicate: Optional["ClaimPredicate"], + abs_before: Optional[int], + rel_before: Optional[int], + ) -> None: + self.claim_predicate_type = claim_predicate_type + self.and_predicates = and_predicates + self.or_predicates = or_predicates + self.not_predicate = not_predicate + self.abs_before = abs_before + self.rel_before = rel_before + + @classmethod + def predicate_and( + cls, left: "ClaimPredicate", right: "ClaimPredicate" + ) -> "ClaimPredicate": + """Returns an `and` claim predicate + + :param left: a ClaimPredicate. + :param right: a ClaimPredicate. + :return: an `and` claim predicate. + """ + return cls( + claim_predicate_type=ClaimPredicateType.CLAIM_PREDICATE_AND, + and_predicates=[left, right], + or_predicates=None, + not_predicate=None, + abs_before=None, + rel_before=None, + ) + + @classmethod + def predicate_or( + cls, left: "ClaimPredicate", right: "ClaimPredicate" + ) -> "ClaimPredicate": + """Returns an `or` claim predicate + + :param left: a ClaimPredicate. + :param right: a ClaimPredicate. + :return: an `or` claim predicate. + """ + return cls( + claim_predicate_type=ClaimPredicateType.CLAIM_PREDICATE_OR, + and_predicates=None, + or_predicates=[left, right], + not_predicate=None, + abs_before=None, + rel_before=None, + ) + + @classmethod + def predicate_not(cls, predicate: "ClaimPredicate") -> "ClaimPredicate": + """Returns a `not` claim predicate. + + :param predicate: a ClaimPredicate. + :return: a `not` claim predicate. + """ + return cls( + claim_predicate_type=ClaimPredicateType.CLAIM_PREDICATE_NOT, + and_predicates=None, + or_predicates=None, + not_predicate=predicate, + abs_before=None, + rel_before=None, + ) + + @classmethod + def predicate_before_absolute_time(cls, abs_before: int) -> "ClaimPredicate": + """Returns a `before_absolute_time` claim predicate. + + This predicate will be fulfilled if the closing time of the ledger that includes + the :class:`CreateClaimableBalance` operation is less than this (absolute) Unix timestamp. + + :param abs_before: Unix epoch. + :return: a `before_absolute_time` claim predicate. + """ + return cls( + claim_predicate_type=ClaimPredicateType.CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME, + and_predicates=None, + or_predicates=None, + not_predicate=None, + abs_before=abs_before, + rel_before=None, + ) + + @classmethod + def predicate_before_relative_time(cls, seconds: int) -> "ClaimPredicate": + """Returns a `before_relative_time` claim predicate. + + This predicate will be fulfilled if the closing time of the ledger that + includes the :class:`CreateClaimableBalance` operation plus this relative time delta (in seconds) + is less than the current time. + + :param seconds: seconds since closeTime of the ledger in which the ClaimableBalanceEntry was created. + :return: a `before_relative_time` claim predicate. + """ + return cls( + claim_predicate_type=ClaimPredicateType.CLAIM_PREDICATE_BEFORE_RELATIVE_TIME, + and_predicates=None, + or_predicates=None, + not_predicate=None, + abs_before=None, + rel_before=seconds, + ) + + @classmethod + def predicate_unconditional(cls) -> "ClaimPredicate": + """Returns an unconditional claim predicate. + + :return: an unconditional claim predicate. + """ + return cls( + claim_predicate_type=ClaimPredicateType.CLAIM_PREDICATE_UNCONDITIONAL, + and_predicates=None, + or_predicates=None, + not_predicate=None, + abs_before=None, + rel_before=None, + ) + + def to_xdr_object(self) -> Xdr.nullclass: + data = Xdr.nullclass() + if ( + self.claim_predicate_type + == ClaimPredicateType.CLAIM_PREDICATE_UNCONDITIONAL + ): + data.type = Xdr.const.CLAIM_PREDICATE_UNCONDITIONAL + return data + elif ( + self.claim_predicate_type + == ClaimPredicateType.CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME + ): + data.type = Xdr.const.CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME + data.absBefore = self.abs_before + return data + elif ( + self.claim_predicate_type + == ClaimPredicateType.CLAIM_PREDICATE_BEFORE_RELATIVE_TIME + ): + data.type = Xdr.const.CLAIM_PREDICATE_BEFORE_RELATIVE_TIME + data.relBefore = self.rel_before + return data + elif self.claim_predicate_type == ClaimPredicateType.CLAIM_PREDICATE_NOT: + data.type = Xdr.const.CLAIM_PREDICATE_NOT + data.notPredicate = pack_xdr_array(self.not_predicate.to_xdr_object()) + return data + elif self.claim_predicate_type == ClaimPredicateType.CLAIM_PREDICATE_AND: + data.type = Xdr.const.CLAIM_PREDICATE_AND + data.andPredicates = [ + self.and_predicates[0].to_xdr_object(), + self.and_predicates[1].to_xdr_object(), + ] + return data + elif self.claim_predicate_type == ClaimPredicateType.CLAIM_PREDICATE_OR: + data.type = Xdr.const.CLAIM_PREDICATE_OR + data.orPredicates = [ + self.or_predicates[0].to_xdr_object(), + self.or_predicates[1].to_xdr_object(), + ] + return data + else: + raise ValueError( + f"{self.claim_predicate_type} is not a valid ClaimPredicateType." + ) + + @classmethod + def from_xdr_object(cls, xdr_object: Xdr.nullclass) -> "ClaimPredicate": + claim_predicate_type = ClaimPredicateType(xdr_object.type) + if claim_predicate_type == ClaimPredicateType.CLAIM_PREDICATE_UNCONDITIONAL: + return cls.predicate_unconditional() + elif ( + claim_predicate_type + == ClaimPredicateType.CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME + ): + abs_before = xdr_object.absBefore + return cls.predicate_before_absolute_time(abs_before) + elif ( + claim_predicate_type + == ClaimPredicateType.CLAIM_PREDICATE_BEFORE_RELATIVE_TIME + ): + rel_before = xdr_object.relBefore + return cls.predicate_before_relative_time(rel_before) + elif claim_predicate_type == ClaimPredicateType.CLAIM_PREDICATE_NOT: + not_pedicate = xdr_object.notPredicate + pedicate = cls.from_xdr_object(not_pedicate[0]) + return cls.predicate_not(pedicate) + elif claim_predicate_type == ClaimPredicateType.CLAIM_PREDICATE_AND: + and_predicates = xdr_object.andPredicates + left = cls.from_xdr_object(and_predicates[0]) + right = cls.from_xdr_object(and_predicates[1]) + return cls.predicate_and(left, right) + elif claim_predicate_type == ClaimPredicateType.CLAIM_PREDICATE_OR: + or_predicates = xdr_object.orPredicates + left = cls.from_xdr_object(or_predicates[0]) + right = cls.from_xdr_object(or_predicates[1]) + return cls.predicate_or(left, right) + else: + raise ValueError( + "{} is an unsupported ClaimPredicateType." + ) # pragma: no cover + + +class Claimant: + """The :class:`Claimant` object represents a claimable balance claimant. + + :param destination: The destination account ID. + :param predicate: The claim predicate. It is optional, it defaults to unconditional if none is specified. + """ + + def __init__(self, destination: str, predicate: ClaimPredicate = None) -> None: + self.destination = destination + if predicate is None: + predicate = ClaimPredicate.predicate_unconditional() + self.predicate: ClaimPredicate = predicate + + def to_xdr_object(self) -> Xdr.nullclass: + v0 = Xdr.nullclass() + v0.destination = Keypair.from_public_key(self.destination).xdr_account_id() + v0.predicate = self.predicate.to_xdr_object() + data = Xdr.nullclass() + + data.type = Xdr.const.CLAIMANT_TYPE_V0 + data.v0 = v0 + return data + + @classmethod + def from_xdr_object(cls, xdr_object: Xdr.nullclass) -> "Claimant": + destination = StrKey.encode_ed25519_public_key( + xdr_object.v0.destination.ed25519 + ) + predicate = ClaimPredicate.from_xdr_object(xdr_object.v0.predicate) + return cls(destination=destination, predicate=predicate) + + +class CreateClaimableBalance(Operation): + """The :class:`CreateClaimableBalance` object, which represents a CreateClaimableBalance + operation on Stellar's network. + + Creates a ClaimableBalanceEntry. See `Claimable Balance + _` for more information on parameters and usage. + + See `Create Claimable Balance + _`. + + Threshold: Medium + + :param asset: The asset for the claimable balance. + :param amount: the amount of the asset. + :param claimants: A list of Claimants. + :param source: The source account (defaults to transaction source). + """ + + def __init__( + self, + asset: Asset, + amount: Union[str, Decimal], + claimants: List[Claimant], + source: str = None, + ) -> None: + super().__init__(source) + check_amount(amount) + self.asset = asset + self.amount = amount + self.claimants = claimants + self.source = source + + @classmethod + def type_code(cls) -> int: + return Xdr.const.CREATE_CLAIMABLE_BALANCE + + def _to_operation_body(self) -> Xdr.nullclass: + body = Xdr.nullclass() + body.type = Xdr.const.CREATE_CLAIMABLE_BALANCE + asset = self.asset.to_xdr_object() + amount = Operation.to_xdr_amount(self.amount) + claimants = [claimant.to_xdr_object() for claimant in self.claimants] + create_claimable_balance_op = Xdr.types.CreateClaimableBalanceOp( + asset=asset, amount=amount, claimants=claimants + ) + body.createClaimableBalanceOp = create_claimable_balance_op + return body + + @classmethod + def from_xdr_object( + cls, operation_xdr_object: Xdr.types.Operation + ) -> "CreateClaimableBalance": + """Creates a :class:`CreateClaimableBalance` object from an XDR Operation + object. + """ + source = Operation.get_source_from_xdr_obj(operation_xdr_object) + asset = Asset.from_xdr_object( + operation_xdr_object.body.createClaimableBalanceOp.asset + ) + amount = Operation.from_xdr_amount( + operation_xdr_object.body.createClaimableBalanceOp.amount + ) + claimants = [] + for ( + claimant_xdr_obj + ) in operation_xdr_object.body.createClaimableBalanceOp.claimants: + claimants.append(Claimant.from_xdr_object(claimant_xdr_obj)) + op = cls(asset=asset, amount=amount, claimants=claimants, source=source) + op._source_muxed = Operation.get_source_muxed_from_xdr_obj(operation_xdr_object) + return op diff --git a/stellar_sdk/transaction_builder.py b/stellar_sdk/transaction_builder.py index 107da5d2..35f9e0d8 100644 --- a/stellar_sdk/transaction_builder.py +++ b/stellar_sdk/transaction_builder.py @@ -828,3 +828,33 @@ def append_bump_sequence_op( """ op = BumpSequence(bump_to, source) return self.append_operation(op) + + def append_create_claimable_balance_op( + self, + asset: Asset, + amount: Union[str, Decimal], + claimants: List[Claimant], + source: str = None, + ) -> "TransactionBuilder": + """Append a :class:`CreateClaimableBalance ` + operation to the list of operations. + + :param asset: The asset for the claimable balance. + :param amount: the amount of the asset. + :param claimants: A list of Claimants. + :param source: The source account (defaults to transaction source). + """ + op = CreateClaimableBalance(asset, amount, claimants, source) + return self.append_operation(op) + + def append_claim_claimable_balance_op( + self, balance_id: str, source: str = None + ) -> "TransactionBuilder": + """Append a :class:`ClaimClaimableBalance ` + operation to the list of operations. + + :param balance_id: The claimable balance id to be claimed. + :param source: The source account (defaults to transaction source). + """ + op = ClaimClaimableBalance(balance_id, source) + return self.append_operation(op) diff --git a/tests/test_operation.py b/tests/test_operation.py index 11d08a19..0de031cb 100644 --- a/tests/test_operation.py +++ b/tests/test_operation.py @@ -1,10 +1,13 @@ +import base64 from decimal import Decimal import pytest +from stellar_sdk.operation.claim_claimable_balance import ClaimClaimableBalance from stellar_sdk import Price, Asset, Keypair from stellar_sdk.exceptions import Ed25519PublicKeyInvalidError, AssetCodeInvalidError from stellar_sdk.operation import Operation, CreateAccount +from stellar_sdk.operation.create_claimable_balance import * from stellar_sdk.operation.account_merge import AccountMerge from stellar_sdk.operation.allow_trust import AllowTrust, TrustLineEntryFlag from stellar_sdk.operation.bump_sequence import BumpSequence @@ -28,6 +31,7 @@ ) from stellar_sdk.signer import Signer from stellar_sdk.utils import sha256 +from stellar_sdk.xdr import StellarXDR_pack as XdrPacker class TestBaseOperation: @@ -301,6 +305,32 @@ def test_from_xdr_obj(self): assert op.asset == asset +class TestClaimClaimableBalance: + def test_to_xdr_obj(self): + source = "GDL635DMMORJHKEHHQIIB4VPYM6YGEMPLORYHHM2DEHAUOUXLSTMHQDV" + balance_id = ( + "00000000da0d57da7d4850e7fc10d2a9d0ebc731f7afb40574c03395b17d49149b91f5be" + ) + op = ClaimClaimableBalance(balance_id=balance_id, source=source) + assert ( + op.to_xdr_object().to_xdr() + == "AAAAAQAAAADX7fRsY6KTqIc8EIDyr8M9gxGPW6ODnZoZDgo6l1ymwwAAAA8AAAAA2g1X2n1IUOf8ENKp0OvHMfevtAV0wDOVsX1JFJuR9b4=" + ) + + def test_from_xdr_obj(self): + source = "GDL635DMMORJHKEHHQIIB4VPYM6YGEMPLORYHHM2DEHAUOUXLSTMHQDV" + balance_id = ( + "00000000da0d57da7d4850e7fc10d2a9d0ebc731f7afb40574c03395b17d49149b91f5be" + ) + origin_xdr_obj = ClaimClaimableBalance( + balance_id=balance_id, source=source + ).to_xdr_object() + op = Operation.from_xdr_object(origin_xdr_obj) + assert isinstance(op, ClaimClaimableBalance) + assert op.source == source + assert op.balance_id == balance_id + + class TestPayment: def test_to_xdr_obj(self): source = "GDL635DMMORJHKEHHQIIB4VPYM6YGEMPLORYHHM2DEHAUOUXLSTMHQDV" @@ -1212,3 +1242,195 @@ def test_check_asset_code(self, asset_code): def test_check_asset_code_raise(self, asset_code): with pytest.raises(AssetCodeInvalidError, match="Asset code is invalid"): check_asset_code(asset_code) + + +class TestClaimPredicate: + @staticmethod + def to_xdr(predicate): + packer = XdrPacker.StellarXDRPacker() + packer.pack_ClaimPredicate(predicate.to_xdr_object()) + return base64.b64encode(packer.get_buffer()).decode() + + def test_predicate_unconditional(self): + xdr = "AAAAAA==" + predicate = ClaimPredicate.predicate_unconditional() + assert xdr == self.to_xdr(predicate) + assert xdr == self.to_xdr( + ClaimPredicate.from_xdr_object(predicate.to_xdr_object()) + ) + + def test_predicate_before_relative_time(self): + xdr = "AAAABQAAAAAAAAPo" + predicate = ClaimPredicate.predicate_before_relative_time(1000) + assert xdr == self.to_xdr(predicate) + assert xdr == self.to_xdr( + ClaimPredicate.from_xdr_object(predicate.to_xdr_object()) + ) + + def test_predicate_before_absolute_time(self): + xdr = "AAAABAAAAABfc0qi" + predicate = ClaimPredicate.predicate_before_absolute_time(1601391266) + assert xdr == self.to_xdr(predicate) + assert xdr == self.to_xdr( + ClaimPredicate.from_xdr_object(predicate.to_xdr_object()) + ) + + def test_predicate_not(self): + xdr = "AAAAAwAAAAEAAAAEAAAAAF9zSqI=" + predicate_abs = ClaimPredicate.predicate_before_absolute_time(1601391266) + predicate = ClaimPredicate.predicate_not(predicate_abs) + assert xdr == self.to_xdr(predicate) + assert xdr == self.to_xdr( + ClaimPredicate.from_xdr_object(predicate.to_xdr_object()) + ) + + def test_predicate_and_1(self): + xdr = "AAAAAQAAAAIAAAAEAAAAAF9zSqIAAAAFAAAAAAAAA+g=" + predicate_abs = ClaimPredicate.predicate_before_absolute_time(1601391266) + predicate_rel = ClaimPredicate.predicate_before_relative_time(1000) + predicate = ClaimPredicate.predicate_and(predicate_abs, predicate_rel) + assert xdr == self.to_xdr(predicate) + assert xdr == self.to_xdr( + ClaimPredicate.from_xdr_object(predicate.to_xdr_object()) + ) + + def test_predicate_and_2(self): + xdr = "AAAAAQAAAAIAAAAFAAAAAAAAA+gAAAAEAAAAAF9zSqI=" + predicate_abs = ClaimPredicate.predicate_before_absolute_time(1601391266) + predicate_rel = ClaimPredicate.predicate_before_relative_time(1000) + predicate = ClaimPredicate.predicate_and(predicate_rel, predicate_abs) + assert xdr == self.to_xdr(predicate) + assert xdr == self.to_xdr( + ClaimPredicate.from_xdr_object(predicate.to_xdr_object()) + ) + + def test_predicate_or_1(self): + xdr = "AAAAAgAAAAIAAAAEAAAAAF9zSqIAAAAFAAAAAAAAA+g=" + predicate_abs = ClaimPredicate.predicate_before_absolute_time(1601391266) + predicate_rel = ClaimPredicate.predicate_before_relative_time(1000) + predicate = ClaimPredicate.predicate_or(predicate_abs, predicate_rel) + assert xdr == self.to_xdr(predicate) + assert xdr == self.to_xdr( + ClaimPredicate.from_xdr_object(predicate.to_xdr_object()) + ) + + def test_predicate_or_2(self): + xdr = "AAAAAgAAAAIAAAAFAAAAAAAAA+gAAAAEAAAAAF9zSqI=" + predicate_abs = ClaimPredicate.predicate_before_absolute_time(1601391266) + predicate_rel = ClaimPredicate.predicate_before_relative_time(1000) + predicate = ClaimPredicate.predicate_or(predicate_rel, predicate_abs) + assert xdr == self.to_xdr(predicate) + assert xdr == self.to_xdr( + ClaimPredicate.from_xdr_object(predicate.to_xdr_object()) + ) + + def test_predicate_mix(self): + xdr = "AAAAAQAAAAIAAAABAAAAAgAAAAQAAAAAX14QAAAAAAAAAAACAAAAAgAAAAUAAAAAAADDUAAAAAMAAAABAAAABAAAAABlU/EA" + predicate_left = ClaimPredicate.predicate_and( + ClaimPredicate.predicate_before_absolute_time(1600000000), + ClaimPredicate.predicate_unconditional(), + ) + predicate_right = ClaimPredicate.predicate_or( + ClaimPredicate.predicate_before_relative_time(50000), + ClaimPredicate.predicate_not( + ClaimPredicate.predicate_before_absolute_time(1700000000) + ), + ) + predicate = ClaimPredicate.predicate_and(predicate_left, predicate_right) + assert xdr == self.to_xdr(predicate) + assert xdr == self.to_xdr( + ClaimPredicate.from_xdr_object(predicate.to_xdr_object()) + ) + + def test_predicate_invalid_type_raise(self): + predicate = ClaimPredicate( + claim_predicate_type="invalid", + and_predicates=None, + or_predicates=None, + not_predicate=None, + abs_before=None, + rel_before=1, + ) + with pytest.raises( + ValueError, match=f"invalid is not a valid ClaimPredicateType." + ): + predicate.to_xdr_object() + + +class TestClaimant: + @staticmethod + def to_xdr(claimant): + packer = XdrPacker.StellarXDRPacker() + packer.pack_Claimant(claimant.to_xdr_object()) + return base64.b64encode(packer.get_buffer()).decode() + + def test_claimant(self): + xdr = "AAAAAAAAAACJmyhA7VY2xW3cXxSyOXX3nxuiOI0mlOTFbs3dyWDl7wAAAAEAAAACAAAAAQAAAAIAAAAEAAAAAF9eEAAAAAAAAAAAAgAAAAIAAAAFAAAAAAAAw1AAAAADAAAAAQAAAAQAAAAAZVPxAA==" + destination = "GCEZWKCA5VLDNRLN3RPRJMRZOX3Z6G5CHCGSNFHEYVXM3XOJMDS674JZ" + predicate_left = ClaimPredicate.predicate_and( + ClaimPredicate.predicate_before_absolute_time(1600000000), + ClaimPredicate.predicate_unconditional(), + ) + predicate_right = ClaimPredicate.predicate_or( + ClaimPredicate.predicate_before_relative_time(50000), + ClaimPredicate.predicate_not( + ClaimPredicate.predicate_before_absolute_time(1700000000) + ), + ) + predicate = ClaimPredicate.predicate_and(predicate_left, predicate_right) + claimant = Claimant(destination=destination, predicate=predicate) + assert self.to_xdr(claimant) == xdr + assert xdr == self.to_xdr(Claimant.from_xdr_object(claimant.to_xdr_object())) + + def test_claimant_default(self): + xdr = "AAAAAAAAAACJmyhA7VY2xW3cXxSyOXX3nxuiOI0mlOTFbs3dyWDl7wAAAAA=" + destination = "GCEZWKCA5VLDNRLN3RPRJMRZOX3Z6G5CHCGSNFHEYVXM3XOJMDS674JZ" + claimant = Claimant(destination=destination) + assert self.to_xdr(claimant) == xdr + assert xdr == self.to_xdr(Claimant.from_xdr_object(claimant.to_xdr_object())) + + +class TestCreateClaimableBalance: + def test_xdr(self): + xdr = "AAAAAQAAAADX7fRsY6KTqIc8EIDyr8M9gxGPW6ODnZoZDgo6l1ymwwAAAA4AAAAAAAAAAEqTzAAAAAADAAAAAAAAAACJmyhA7VY2xW3cXxSyOXX3nxuiOI0mlOTFbs3dyWDl7wAAAAEAAAACAAAAAQAAAAIAAAAEAAAAAF9eEAAAAAAAAAAAAgAAAAIAAAAFAAAAAAAAw1AAAAADAAAAAQAAAAQAAAAAZVPxAAAAAAAAAAAAYyi+wCa8rss9LBoofzuttQ+74vczrrbpvZfDhNL/7/EAAAAAAAAAAAAAAACuYyIkw8jWz2vBYj6jUhgWWzNtUpaID2NifbYvrdlNxwAAAAQAAAAAX3NKog==" + source = "GDL635DMMORJHKEHHQIIB4VPYM6YGEMPLORYHHM2DEHAUOUXLSTMHQDV" + + predicate_left = ClaimPredicate.predicate_and( + ClaimPredicate.predicate_before_absolute_time(1600000000), + ClaimPredicate.predicate_unconditional(), + ) + predicate_right = ClaimPredicate.predicate_or( + ClaimPredicate.predicate_before_relative_time(50000), + ClaimPredicate.predicate_not( + ClaimPredicate.predicate_before_absolute_time(1700000000) + ), + ) + predicate1 = ClaimPredicate.predicate_and(predicate_left, predicate_right) + claimant1 = Claimant( + destination="GCEZWKCA5VLDNRLN3RPRJMRZOX3Z6G5CHCGSNFHEYVXM3XOJMDS674JZ", + predicate=predicate1, + ) + + predicate2 = ClaimPredicate.predicate_unconditional() + claimant2 = Claimant( + destination="GBRSRPWAE26K5SZ5FQNCQ7Z3VW2Q7O7C64Z25NXJXWL4HBGS77X7CWTG", + predicate=predicate2, + ) + + predicate3 = ClaimPredicate.predicate_before_absolute_time(1601391266) + claimant3 = Claimant( + destination="GCXGGIREYPENNT3LYFRD5I2SDALFWM3NKKLIQD3DMJ63ML5N3FG4OQQG", + predicate=predicate3, + ) + + op = CreateClaimableBalance( + asset=Asset.native(), + amount="125.12", + claimants=[claimant1, claimant2, claimant3], + source=source, + ) + assert op.to_xdr_object().to_xdr() == xdr + assert ( + Operation.from_xdr_object(op.to_xdr_object()).to_xdr_object().to_xdr() + == xdr + ) diff --git a/tests/test_transaction_builder.py b/tests/test_transaction_builder.py index ee213028..457b7035 100644 --- a/tests/test_transaction_builder.py +++ b/tests/test_transaction_builder.py @@ -15,6 +15,7 @@ IdMemo, HashMemo, ReturnHashMemo, + Claimant, ) @@ -165,13 +166,24 @@ def test_to_xdr_v0(self): .append_account_merge_op( "GAXN7HZQTHIPW7N2HGPAXMR42LPJ5VLYXMCCOX4D3JC4CQZGID3UYUPF" ) + .append_claim_claimable_balance_op( + "00000000da0d57da7d4850e7fc10d2a9d0ebc731f7afb40574c03395b17d49149b91f5be" + ) + .append_create_claimable_balance_op( + Asset.native(), + "100", + [ + Claimant( + destination="GCXGGIREYPENNT3LYFRD5I2SDALFWM3NKKLIQD3DMJ63ML5N3FG4OQQG" + ) + ], + ) .build() ) - - xdr = "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" + xdr = "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" assert te.to_xdr() == xdr - xdr_signed = "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" + xdr_signed = "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" signer = Keypair.from_secret( "SCCS5ZBI7WVIJ4SW36WGOQQIWJYCL3VOAULSXX3FB57USIO25EDOYQHH" ) @@ -335,6 +347,18 @@ def test_to_xdr_v1(self): .append_account_merge_op( "GAXN7HZQTHIPW7N2HGPAXMR42LPJ5VLYXMCCOX4D3JC4CQZGID3UYUPF" ) + .append_claim_claimable_balance_op( + "00000000da0d57da7d4850e7fc10d2a9d0ebc731f7afb40574c03395b17d49149b91f5be" + ) + .append_create_claimable_balance_op( + Asset.native(), + "100", + [ + Claimant( + destination="GCXGGIREYPENNT3LYFRD5I2SDALFWM3NKKLIQD3DMJ63ML5N3FG4OQQG" + ) + ], + ) .build() )