Skip to content
Merged
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
33 changes: 22 additions & 11 deletions src/algokit_utils/transactions/transaction_composer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1812,7 +1812,7 @@ def build(self) -> TransactionComposerBuildResult:
txn_with_signers: list[TransactionWithSignerAndContext] = []

for txn in self._txns:
txn_with_signers.extend(self._build_txn(txn, suggested_params))
txn_with_signers.extend(self._build_txn(txn, suggested_params, include_signer=True))

for ts in txn_with_signers:
self._atc.add_transaction(ts)
Expand Down Expand Up @@ -1852,9 +1852,9 @@ def build_transactions(self) -> BuiltTransactions:
txn_with_signers: list[TransactionWithSigner] = []

if isinstance(txn, MethodCallParams):
txn_with_signers.extend(self._build_method_call(txn, suggested_params))
txn_with_signers.extend(self._build_method_call(txn, suggested_params, include_signer=False))
else:
txn_with_signers.extend(self._build_txn(txn, suggested_params))
txn_with_signers.extend(self._build_txn(txn, suggested_params, include_signer=False))

for ts in txn_with_signers:
transactions.append(ts.txn)
Expand Down Expand Up @@ -2129,7 +2129,11 @@ def _common_txn_build_step( # noqa: C901
)

def _build_method_call( # noqa: C901, PLR0912, PLR0915
self, params: MethodCallParams, suggested_params: algosdk.transaction.SuggestedParams
self,
params: MethodCallParams,
suggested_params: algosdk.transaction.SuggestedParams,
*,
include_signer: bool,
) -> list[TransactionWithSignerAndContext]:
method_args: list[ABIValue | TransactionWithSigner] = []
txns_for_group: list[TransactionWithSignerAndContext] = []
Expand Down Expand Up @@ -2159,7 +2163,9 @@ def _build_method_call( # noqa: C901, PLR0912, PLR0915
method_args.append(
TransactionWithSignerAndContext(
txn=arg,
signer=signer if signer is not None else self._get_signer(params.sender),
signer=signer
if signer is not None
else (NULL_SIGNER if not include_signer else self._get_signer(params.sender)),
context=TransactionContext(abi_method=None),
)
)
Expand All @@ -2171,7 +2177,9 @@ def _build_method_call( # noqa: C901, PLR0912, PLR0915
| AppUpdateMethodCallParams()
| AppDeleteMethodCallParams()
):
temp_txn_with_signers = self._build_method_call(arg, suggested_params)
temp_txn_with_signers = self._build_method_call(
arg, suggested_params, include_signer=include_signer
)
# Add all transactions except the last one in reverse order
txns_for_group.extend(temp_txn_with_signers[:-1])
# Add the last transaction to method_args
Expand Down Expand Up @@ -2208,7 +2216,7 @@ def _build_method_call( # noqa: C901, PLR0912, PLR0915
method_args.append(
TransactionWithSignerAndContext(
txn=txn.txn,
signer=signer or self._get_signer(params.sender),
signer=signer or (NULL_SIGNER if not include_signer else self._get_signer(params.sender)),
context=TransactionContext(abi_method=params.method),
)
)
Expand Down Expand Up @@ -2255,7 +2263,8 @@ def _build_method_call( # noqa: C901, PLR0912, PLR0915
"sp": suggested_params,
"signer": params.signer
if params.signer is not None
else self._get_signer(params.sender) or algosdk.atomic_transaction_composer.EmptySigner(),
else (NULL_SIGNER if not include_signer else self._get_signer(params.sender))
or algosdk.atomic_transaction_composer.EmptySigner(),
"method_args": list(reversed(method_args)),
"on_complete": params.on_complete or algosdk.transaction.OnComplete.NoOpOC,
"boxes": [AppManager.get_box_reference(ref) for ref in params.box_references]
Expand Down Expand Up @@ -2496,6 +2505,8 @@ def _build_txn( # noqa: C901, PLR0912, PLR0911
self,
txn: TransactionWithSigner | TxnParams | AtomicTransactionComposer,
suggested_params: algosdk.transaction.SuggestedParams,
*,
include_signer: bool,
) -> list[TransactionWithSignerAndContext]:
match txn:
case TransactionWithSigner():
Expand All @@ -2505,18 +2516,18 @@ def _build_txn( # noqa: C901, PLR0912, PLR0911
case AtomicTransactionComposer():
return self._build_atc(txn)
case algosdk.transaction.Transaction():
signer = self._get_signer(txn.sender)
signer = NULL_SIGNER if not include_signer else self._get_signer(txn.sender)
return [TransactionWithSignerAndContext(txn=txn, signer=signer, context=TransactionContext.empty())]
case (
AppCreateMethodCallParams()
| AppCallMethodCallParams()
| AppUpdateMethodCallParams()
| AppDeleteMethodCallParams()
):
return self._build_method_call(txn, suggested_params)
return self._build_method_call(txn, suggested_params, include_signer=include_signer)

signer = txn.signer.signer if isinstance(txn.signer, TransactionSignerAccountProtocol) else txn.signer # type: ignore[assignment]
signer = signer or self._get_signer(txn.sender)
signer = signer or (NULL_SIGNER if not include_signer else self._get_signer(txn.sender))

match txn:
case PaymentParams():
Expand Down
55 changes: 55 additions & 0 deletions tests/transactions/test_transaction_composer.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,61 @@ def test_simulate(algorand: AlgorandClient, funded_account: SigningAccount) -> N
assert simulate_response


def test_simulate_without_signer(algorand: AlgorandClient, funded_secondary_account: SigningAccount) -> None:
"""Test that simulate works without a signer being available when skip_signatures=True."""

# No signer is loaded for funded_secondary_account
composer = algorand.new_group()
composer.add_payment(
PaymentParams(
sender=funded_secondary_account.address,
receiver=funded_secondary_account.address,
amount=AlgoAmount.from_algo(1),
)
)

simulate_response = composer.simulate(skip_signatures=True)
assert simulate_response
assert len(simulate_response.transactions) == 1


def test_build_transactions_without_signer(algorand: AlgorandClient, funded_secondary_account: SigningAccount) -> None:
"""Test that build_transactions work without a signer being available"""

# No signer is loaded for funded_secondary_account
composer = algorand.new_group()
composer.add_payment(
PaymentParams(
sender=funded_secondary_account.address,
receiver=funded_secondary_account.address,
amount=AlgoAmount.from_algo(1),
)
)

built = composer.build_transactions()
assert len(built.transactions) == 1
assert len(built.signers) == 0


def test_fails_to_build_without_signers(algorand: AlgorandClient, funded_secondary_account: SigningAccount) -> None:
"""Test that build does not work without a signer being available"""

# No signer is loaded for funded_secondary_account
composer = algorand.new_group()
composer.add_payment(
PaymentParams(
sender=funded_secondary_account.address,
receiver=funded_secondary_account.address,
amount=AlgoAmount.from_algo(1),
)
)

with pytest.raises(Exception) as e: # noqa: PT011
composer.build()

assert str(e.value) == f"No signer found for address {funded_secondary_account.address}"


def test_send(algorand: AlgorandClient, funded_account: SigningAccount) -> None:
composer = TransactionComposer(
algod=algorand.client.algod,
Expand Down
Loading