Skip to content

Commit

Permalink
feat: Add SorobanDataBuilder to prepare soroban_data easily. (#760)
Browse files Browse the repository at this point in the history
  • Loading branch information
overcat authored Aug 26, 2023
1 parent 5677469 commit 19ebe70
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 5 deletions.
1 change: 1 addition & 0 deletions stellar_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from .server_async import *
from .signer import *
from .signer_key import *
from .soroban_data_builder import *
from .soroban_server import *
from .strkey import *
from .time_bounds import *
Expand Down
112 changes: 112 additions & 0 deletions stellar_sdk/soroban_data_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
from __future__ import annotations

from typing import List, Union

from . import xdr as stellar_xdr

__all__ = ["SorobanDataBuilder"]


class SorobanDataBuilder:
"""Supports building :class:`Memo <stellar_sdk.xdr.SorobanTransactionData>` structures
with various items set to specific values.
This is recommended for when you are building :class:`RestoreFootprint <stellar_sdk.operation.RestoreFootprint>`,
:class:`BumpFootprintExpiration <stellar_sdk.operation.BumpFootprintExpiration>` operations to avoid (re)building
the entire data structure from scratch.
By default, an empty instance will be created.
"""

def __init__(self):
self._data = stellar_xdr.SorobanTransactionData(
ext=stellar_xdr.ExtensionPoint(0),
refundable_fee=stellar_xdr.Int64(0),
resources=stellar_xdr.SorobanResources(
footprint=stellar_xdr.LedgerFootprint(
read_only=[],
read_write=[],
),
extended_meta_data_size_bytes=stellar_xdr.Uint32(0),
read_bytes=stellar_xdr.Uint32(0),
write_bytes=stellar_xdr.Uint32(0),
instructions=stellar_xdr.Uint32(0),
),
)

@classmethod
def from_xdr(
cls, soroban_data: Union[str, stellar_xdr.SorobanTransactionData]
) -> SorobanDataBuilder:
"""Create a new :class:`SorobanDataBuilder` object from an XDR object.
:param soroban_data: The XDR object that represents a SorobanTransactionData.
:return: This builder.
"""
data = cls()
if isinstance(soroban_data, str):
data._data = stellar_xdr.SorobanTransactionData.from_xdr(soroban_data)
else:
data._data = stellar_xdr.SorobanTransactionData.from_xdr_bytes(
soroban_data.to_xdr_bytes()
)
return data

def set_refundable_fee(self, fee: int) -> SorobanDataBuilder:
"""Sets the "refundable" fee portion of the Soroban data.
:param fee: The refundable fee to set (int64)
:return: This builder.
"""
self._data.refundable_fee = stellar_xdr.Int64(fee)
return self

def set_read_only(
self, read_only: List[stellar_xdr.LedgerKey]
) -> SorobanDataBuilder:
"""Sets the read-only portion of the storage access footprint to be a certain set of ledger keys.
:param read_only: The read-only ledger keys to set.
:return: This builder.
"""
self._data.resources.footprint.read_only = read_only or []
return self

def set_read_write(
self, read_write: List[stellar_xdr.LedgerKey]
) -> SorobanDataBuilder:
"""Sets the read-write portion of the storage access footprint to be a certain set of ledger keys.
:param read_write: The read-write ledger keys to set.
:return: This builder.
"""
self._data.resources.footprint.read_write = read_write or []
return self

def set_resources(
self, instructions: int, read_bytes: int, write_bytes: int, metadata_bytes: int
) -> SorobanDataBuilder:
"""Sets up the resource metrics.
You should almost NEVER need this, as its often generated / provided to you
by transaction simulation/preflight from a Soroban RPC server.
:param instructions: Number of CPU instructions (uint32)
:param read_bytes: Number of bytes being read (uint32)
:param write_bytes: Number of bytes being written (uint32)
:param metadata_bytes: Number of extended metadata bytes (uint32)
:return: This builder.
"""
self._data.resources.instructions = stellar_xdr.Uint32(instructions)
self._data.resources.read_bytes = stellar_xdr.Uint32(read_bytes)
self._data.resources.write_bytes = stellar_xdr.Uint32(write_bytes)
self._data.resources.extended_meta_data_size_bytes = stellar_xdr.Uint32(
metadata_bytes
)
return self

def build(self):
""":return: a copy of the final data structure."""
return stellar_xdr.SorobanTransactionData.from_xdr_bytes(
self._data.to_xdr_bytes()
)
5 changes: 4 additions & 1 deletion stellar_sdk/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .operation.create_claimable_balance import CreateClaimableBalance
from .operation.operation import Operation
from .preconditions import Preconditions
from .soroban_data_builder import SorobanDataBuilder
from .strkey import StrKey
from .time_bounds import TimeBounds
from .utils import sha256
Expand Down Expand Up @@ -86,7 +87,9 @@ def __init__(
self.memo: Memo = memo
self.fee: int = fee
self.preconditions: Optional[Preconditions] = preconditions
self.soroban_data: Optional[stellar_xdr.SorobanTransactionData] = soroban_data
self.soroban_data: Optional[stellar_xdr.SorobanTransactionData] = (
SorobanDataBuilder.from_xdr(soroban_data).build() if soroban_data else None
)
self.v1: bool = v1

def get_claimable_balance_id(self, operation_index: int) -> str:
Expand Down
7 changes: 3 additions & 4 deletions stellar_sdk/transaction_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from .price import Price
from .signer import Signer
from .signer_key import SignedPayloadSigner, SignerKey
from .soroban_data_builder import SorobanDataBuilder
from .time_bounds import TimeBounds
from .transaction import Transaction
from .transaction_envelope import TransactionEnvelope
Expand Down Expand Up @@ -348,7 +349,7 @@ def set_min_sequence_ledger_gap(
return self

def set_soroban_data(
self, soroban_data: Union[stellar_xdr.SorobanTransactionData]
self, soroban_data: Union[stellar_xdr.SorobanTransactionData, str]
) -> "TransactionBuilder":
"""Set the SorobanTransactionData. For non-contract(non-Soroban) transactions, this setting has no effect.
Expand All @@ -360,9 +361,7 @@ def set_soroban_data(
:param soroban_data: The SorobanTransactionData as XDR object or base64 encoded string.
:return: This builder instance.
"""
if isinstance(soroban_data, str):
soroban_data = stellar_xdr.SorobanTransactionData.from_xdr(soroban_data)
self.soroban_data = soroban_data
self.soroban_data = SorobanDataBuilder.from_xdr(soroban_data).build()
return self

def add_extra_signer(
Expand Down
143 changes: 143 additions & 0 deletions tests/test_soroban_data_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
from stellar_sdk import Keypair
from stellar_sdk import xdr as stellar_xdr
from stellar_sdk.soroban_data_builder import SorobanDataBuilder


class TestSorobanDataBuilder:
empty_instance = stellar_xdr.SorobanTransactionData(
ext=stellar_xdr.ExtensionPoint(0),
refundable_fee=stellar_xdr.Int64(0),
resources=stellar_xdr.SorobanResources(
footprint=stellar_xdr.LedgerFootprint(
read_only=[],
read_write=[],
),
extended_meta_data_size_bytes=stellar_xdr.Uint32(0),
read_bytes=stellar_xdr.Uint32(0),
write_bytes=stellar_xdr.Uint32(0),
instructions=stellar_xdr.Uint32(0),
),
)

def test_init(self):
builder = SorobanDataBuilder()
assert builder._data == self.empty_instance
assert builder.build() == self.empty_instance

def test_from_xdr_object(self):
xdr_obj = stellar_xdr.SorobanTransactionData(
ext=stellar_xdr.ExtensionPoint(0),
refundable_fee=stellar_xdr.Int64(0),
resources=stellar_xdr.SorobanResources(
footprint=stellar_xdr.LedgerFootprint(
read_only=[],
read_write=[],
),
extended_meta_data_size_bytes=stellar_xdr.Uint32(1),
read_bytes=stellar_xdr.Uint32(2),
write_bytes=stellar_xdr.Uint32(3),
instructions=stellar_xdr.Uint32(4),
),
)
builder = SorobanDataBuilder.from_xdr(xdr_obj)
assert builder._data == xdr_obj
assert builder.build() == xdr_obj
assert id(builder._data) != id(xdr_obj)

def test_from_xdr_base64(self):
xdr_obj = stellar_xdr.SorobanTransactionData(
ext=stellar_xdr.ExtensionPoint(0),
refundable_fee=stellar_xdr.Int64(0),
resources=stellar_xdr.SorobanResources(
footprint=stellar_xdr.LedgerFootprint(
read_only=[],
read_write=[],
),
extended_meta_data_size_bytes=stellar_xdr.Uint32(1),
read_bytes=stellar_xdr.Uint32(2),
write_bytes=stellar_xdr.Uint32(3),
instructions=stellar_xdr.Uint32(4),
),
)
builder = SorobanDataBuilder.from_xdr(xdr_obj.to_xdr())
assert builder._data == xdr_obj
assert builder.build() == xdr_obj
assert id(builder._data) != id(xdr_obj)

def test_set_refundable_fee(self):
builder = SorobanDataBuilder()
builder.set_refundable_fee(100)
assert builder.build().refundable_fee.int64 == 100

def test_set_resources(self):
data = SorobanDataBuilder().set_resources(1, 2, 3, 4).build()
assert data == stellar_xdr.SorobanTransactionData(
ext=stellar_xdr.ExtensionPoint(0),
refundable_fee=stellar_xdr.Int64(0),
resources=stellar_xdr.SorobanResources(
footprint=stellar_xdr.LedgerFootprint(
read_only=[],
read_write=[],
),
instructions=stellar_xdr.Uint32(1),
read_bytes=stellar_xdr.Uint32(2),
write_bytes=stellar_xdr.Uint32(3),
extended_meta_data_size_bytes=stellar_xdr.Uint32(4),
),
)

def test_set_read_only(self):
ledger_key = stellar_xdr.LedgerKey(
stellar_xdr.LedgerEntryType.ACCOUNT,
account=stellar_xdr.LedgerKeyAccount(
Keypair.from_public_key(
"GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ"
).xdr_account_id()
),
)
data = SorobanDataBuilder().set_read_only([ledger_key]).build()
assert data == stellar_xdr.SorobanTransactionData(
ext=stellar_xdr.ExtensionPoint(0),
refundable_fee=stellar_xdr.Int64(0),
resources=stellar_xdr.SorobanResources(
footprint=stellar_xdr.LedgerFootprint(
read_only=[ledger_key],
read_write=[],
),
extended_meta_data_size_bytes=stellar_xdr.Uint32(0),
read_bytes=stellar_xdr.Uint32(0),
write_bytes=stellar_xdr.Uint32(0),
instructions=stellar_xdr.Uint32(0),
),
)

def test_set_read_write(self):
ledger_key = stellar_xdr.LedgerKey(
stellar_xdr.LedgerEntryType.ACCOUNT,
account=stellar_xdr.LedgerKeyAccount(
Keypair.from_public_key(
"GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ"
).xdr_account_id()
),
)
data = SorobanDataBuilder().set_read_write([ledger_key]).build()
assert data == stellar_xdr.SorobanTransactionData(
ext=stellar_xdr.ExtensionPoint(0),
refundable_fee=stellar_xdr.Int64(0),
resources=stellar_xdr.SorobanResources(
footprint=stellar_xdr.LedgerFootprint(
read_only=[],
read_write=[ledger_key],
),
extended_meta_data_size_bytes=stellar_xdr.Uint32(0),
read_bytes=stellar_xdr.Uint32(0),
write_bytes=stellar_xdr.Uint32(0),
instructions=stellar_xdr.Uint32(0),
),
)

def test_copy(self):
builder = SorobanDataBuilder()
assert builder._data == self.empty_instance
assert builder.build() == self.empty_instance
assert id(builder._data) != id(builder.build())

0 comments on commit 19ebe70

Please sign in to comment.