Skip to content

Commit 44bd2b9

Browse files
authored
fix: allow simulating with empty signatures like TS supports (#196)
1 parent 0d62039 commit 44bd2b9

File tree

2 files changed

+77
-11
lines changed

2 files changed

+77
-11
lines changed

src/algokit_utils/transactions/transaction_composer.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1812,7 +1812,7 @@ def build(self) -> TransactionComposerBuildResult:
18121812
txn_with_signers: list[TransactionWithSignerAndContext] = []
18131813

18141814
for txn in self._txns:
1815-
txn_with_signers.extend(self._build_txn(txn, suggested_params))
1815+
txn_with_signers.extend(self._build_txn(txn, suggested_params, include_signer=True))
18161816

18171817
for ts in txn_with_signers:
18181818
self._atc.add_transaction(ts)
@@ -1852,9 +1852,9 @@ def build_transactions(self) -> BuiltTransactions:
18521852
txn_with_signers: list[TransactionWithSigner] = []
18531853

18541854
if isinstance(txn, MethodCallParams):
1855-
txn_with_signers.extend(self._build_method_call(txn, suggested_params))
1855+
txn_with_signers.extend(self._build_method_call(txn, suggested_params, include_signer=False))
18561856
else:
1857-
txn_with_signers.extend(self._build_txn(txn, suggested_params))
1857+
txn_with_signers.extend(self._build_txn(txn, suggested_params, include_signer=False))
18581858

18591859
for ts in txn_with_signers:
18601860
transactions.append(ts.txn)
@@ -2129,7 +2129,11 @@ def _common_txn_build_step( # noqa: C901
21292129
)
21302130

21312131
def _build_method_call( # noqa: C901, PLR0912, PLR0915
2132-
self, params: MethodCallParams, suggested_params: algosdk.transaction.SuggestedParams
2132+
self,
2133+
params: MethodCallParams,
2134+
suggested_params: algosdk.transaction.SuggestedParams,
2135+
*,
2136+
include_signer: bool,
21332137
) -> list[TransactionWithSignerAndContext]:
21342138
method_args: list[ABIValue | TransactionWithSigner] = []
21352139
txns_for_group: list[TransactionWithSignerAndContext] = []
@@ -2159,7 +2163,9 @@ def _build_method_call( # noqa: C901, PLR0912, PLR0915
21592163
method_args.append(
21602164
TransactionWithSignerAndContext(
21612165
txn=arg,
2162-
signer=signer if signer is not None else self._get_signer(params.sender),
2166+
signer=signer
2167+
if signer is not None
2168+
else (NULL_SIGNER if not include_signer else self._get_signer(params.sender)),
21632169
context=TransactionContext(abi_method=None),
21642170
)
21652171
)
@@ -2171,7 +2177,9 @@ def _build_method_call( # noqa: C901, PLR0912, PLR0915
21712177
| AppUpdateMethodCallParams()
21722178
| AppDeleteMethodCallParams()
21732179
):
2174-
temp_txn_with_signers = self._build_method_call(arg, suggested_params)
2180+
temp_txn_with_signers = self._build_method_call(
2181+
arg, suggested_params, include_signer=include_signer
2182+
)
21752183
# Add all transactions except the last one in reverse order
21762184
txns_for_group.extend(temp_txn_with_signers[:-1])
21772185
# Add the last transaction to method_args
@@ -2208,7 +2216,7 @@ def _build_method_call( # noqa: C901, PLR0912, PLR0915
22082216
method_args.append(
22092217
TransactionWithSignerAndContext(
22102218
txn=txn.txn,
2211-
signer=signer or self._get_signer(params.sender),
2219+
signer=signer or (NULL_SIGNER if not include_signer else self._get_signer(params.sender)),
22122220
context=TransactionContext(abi_method=params.method),
22132221
)
22142222
)
@@ -2255,7 +2263,8 @@ def _build_method_call( # noqa: C901, PLR0912, PLR0915
22552263
"sp": suggested_params,
22562264
"signer": params.signer
22572265
if params.signer is not None
2258-
else self._get_signer(params.sender) or algosdk.atomic_transaction_composer.EmptySigner(),
2266+
else (NULL_SIGNER if not include_signer else self._get_signer(params.sender))
2267+
or algosdk.atomic_transaction_composer.EmptySigner(),
22592268
"method_args": list(reversed(method_args)),
22602269
"on_complete": params.on_complete or algosdk.transaction.OnComplete.NoOpOC,
22612270
"boxes": [AppManager.get_box_reference(ref) for ref in params.box_references]
@@ -2496,6 +2505,8 @@ def _build_txn( # noqa: C901, PLR0912, PLR0911
24962505
self,
24972506
txn: TransactionWithSigner | TxnParams | AtomicTransactionComposer,
24982507
suggested_params: algosdk.transaction.SuggestedParams,
2508+
*,
2509+
include_signer: bool,
24992510
) -> list[TransactionWithSignerAndContext]:
25002511
match txn:
25012512
case TransactionWithSigner():
@@ -2505,18 +2516,18 @@ def _build_txn( # noqa: C901, PLR0912, PLR0911
25052516
case AtomicTransactionComposer():
25062517
return self._build_atc(txn)
25072518
case algosdk.transaction.Transaction():
2508-
signer = self._get_signer(txn.sender)
2519+
signer = NULL_SIGNER if not include_signer else self._get_signer(txn.sender)
25092520
return [TransactionWithSignerAndContext(txn=txn, signer=signer, context=TransactionContext.empty())]
25102521
case (
25112522
AppCreateMethodCallParams()
25122523
| AppCallMethodCallParams()
25132524
| AppUpdateMethodCallParams()
25142525
| AppDeleteMethodCallParams()
25152526
):
2516-
return self._build_method_call(txn, suggested_params)
2527+
return self._build_method_call(txn, suggested_params, include_signer=include_signer)
25172528

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

25212532
match txn:
25222533
case PaymentParams():

tests/transactions/test_transaction_composer.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,61 @@ def test_simulate(algorand: AlgorandClient, funded_account: SigningAccount) -> N
247247
assert simulate_response
248248

249249

250+
def test_simulate_without_signer(algorand: AlgorandClient, funded_secondary_account: SigningAccount) -> None:
251+
"""Test that simulate works without a signer being available when skip_signatures=True."""
252+
253+
# No signer is loaded for funded_secondary_account
254+
composer = algorand.new_group()
255+
composer.add_payment(
256+
PaymentParams(
257+
sender=funded_secondary_account.address,
258+
receiver=funded_secondary_account.address,
259+
amount=AlgoAmount.from_algo(1),
260+
)
261+
)
262+
263+
simulate_response = composer.simulate(skip_signatures=True)
264+
assert simulate_response
265+
assert len(simulate_response.transactions) == 1
266+
267+
268+
def test_build_transactions_without_signer(algorand: AlgorandClient, funded_secondary_account: SigningAccount) -> None:
269+
"""Test that build_transactions work without a signer being available"""
270+
271+
# No signer is loaded for funded_secondary_account
272+
composer = algorand.new_group()
273+
composer.add_payment(
274+
PaymentParams(
275+
sender=funded_secondary_account.address,
276+
receiver=funded_secondary_account.address,
277+
amount=AlgoAmount.from_algo(1),
278+
)
279+
)
280+
281+
built = composer.build_transactions()
282+
assert len(built.transactions) == 1
283+
assert len(built.signers) == 0
284+
285+
286+
def test_fails_to_build_without_signers(algorand: AlgorandClient, funded_secondary_account: SigningAccount) -> None:
287+
"""Test that build does not work without a signer being available"""
288+
289+
# No signer is loaded for funded_secondary_account
290+
composer = algorand.new_group()
291+
composer.add_payment(
292+
PaymentParams(
293+
sender=funded_secondary_account.address,
294+
receiver=funded_secondary_account.address,
295+
amount=AlgoAmount.from_algo(1),
296+
)
297+
)
298+
299+
with pytest.raises(Exception) as e: # noqa: PT011
300+
composer.build()
301+
302+
assert str(e.value) == f"No signer found for address {funded_secondary_account.address}"
303+
304+
250305
def test_send(algorand: AlgorandClient, funded_account: SigningAccount) -> None:
251306
composer = TransactionComposer(
252307
algod=algorand.client.algod,

0 commit comments

Comments
 (0)