Skip to content

Commit 7cddd5d

Browse files
authored
Enable adding scripts with redeemers for withdrawals (#308)
* Add support for withdrawal scripts * Add proper redeemer index computation for withdrawals * Fix handling of reward tag results in transaction builder
1 parent 1a4cc5d commit 7cddd5d

File tree

1 file changed

+63
-12
lines changed

1 file changed

+63
-12
lines changed

pycardano/txbuilder.py

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ class TransactionBuilder:
138138
init=False, default_factory=lambda: []
139139
)
140140

141+
_withdrawal_script_to_redeemers: List[
142+
Tuple[ScriptType, Optional[Redeemer]]
143+
] = field(init=False, default_factory=lambda: [])
144+
141145
_inputs_to_scripts: Dict[UTxO, ScriptType] = field(
142146
init=False, default_factory=lambda: {}
143147
)
@@ -291,6 +295,40 @@ def add_minting_script(
291295
self._minting_script_to_redeemers.append((script, redeemer))
292296
return self
293297

298+
def add_withdrawal_script(
299+
self,
300+
script: Union[UTxO, NativeScript, PlutusV1Script, PlutusV2Script],
301+
redeemer: Optional[Redeemer] = None,
302+
) -> TransactionBuilder:
303+
"""Add a withdrawal script along with its redeemer to this transaction.
304+
305+
Args:
306+
script (Union[UTxO, PlutusV1Script, PlutusV2Script]): A plutus script.
307+
redeemer (Optional[Redeemer]): A plutus redeemer to unlock the UTxO.
308+
309+
Returns:
310+
TransactionBuilder: Current transaction builder.
311+
"""
312+
if redeemer:
313+
if redeemer.tag is not None and redeemer.tag != RedeemerTag.REWARD:
314+
raise InvalidArgumentException(
315+
f"Expect the redeemer tag's type to be {RedeemerTag.REWARD}, "
316+
f"but got {redeemer.tag} instead."
317+
)
318+
redeemer.tag = RedeemerTag.REWARD
319+
self._consolidate_redeemer(redeemer)
320+
321+
if isinstance(script, UTxO):
322+
assert script.output.script is not None
323+
self._withdrawal_script_to_redeemers.append(
324+
(script.output.script, redeemer)
325+
)
326+
self.reference_inputs.add(script)
327+
self._reference_scripts.append(script.output.script)
328+
else:
329+
self._withdrawal_script_to_redeemers.append((script, redeemer))
330+
return self
331+
294332
def add_input_address(self, address: Union[Address, str]) -> TransactionBuilder:
295333
"""Add an address to transaction's input address.
296334
Unlike :meth:`add_input`, which deterministically adds a UTxO to the transaction's inputs, `add_input_address`
@@ -376,6 +414,9 @@ def all_scripts(self) -> List[ScriptType]:
376414
for s, _ in self._minting_script_to_redeemers:
377415
scripts[script_hash(s)] = s
378416

417+
for s, _ in self._withdrawal_script_to_redeemers:
418+
scripts[script_hash(s)] = s
419+
379420
return list(scripts.values())
380421

381422
@property
@@ -397,9 +438,11 @@ def datums(self) -> Dict[DatumHash, Datum]:
397438

398439
@property
399440
def redeemers(self) -> List[Redeemer]:
400-
return [r for r in self._inputs_to_redeemers.values() if r is not None] + [
401-
r for _, r in self._minting_script_to_redeemers if r is not None
402-
]
441+
return (
442+
[r for r in self._inputs_to_redeemers.values() if r is not None]
443+
+ [r for _, r in self._minting_script_to_redeemers if r is not None]
444+
+ [r for _, r in self._withdrawal_script_to_redeemers if r is not None]
445+
)
403446

404447
@property
405448
def script_data_hash(self) -> Optional[ScriptDataHash]:
@@ -735,26 +778,31 @@ def _set_redeemer_index(self):
735778
sorted_mint_policies = sorted(self.mint.keys(), key=lambda x: x.to_cbor())
736779
else:
737780
sorted_mint_policies = []
781+
if self.withdrawals:
782+
sorted_withdrawals = sorted(self.withdrawals.keys())
783+
else:
784+
sorted_withdrawals = []
738785

739786
for i, utxo in enumerate(self.inputs):
740787
if (
741788
utxo in self._inputs_to_redeemers
742789
and self._inputs_to_redeemers[utxo].tag == RedeemerTag.SPEND
743790
):
744791
self._inputs_to_redeemers[utxo].index = i
745-
elif (
746-
utxo in self._inputs_to_redeemers
747-
and self._inputs_to_redeemers[utxo].tag == RedeemerTag.MINT
748-
):
749-
redeemer = self._inputs_to_redeemers[utxo]
750-
redeemer.index = sorted_mint_policies.index(
751-
script_hash(self._inputs_to_scripts[utxo])
752-
)
753792

754793
for script, redeemer in self._minting_script_to_redeemers:
755794
if redeemer is not None:
756795
redeemer.index = sorted_mint_policies.index(script_hash(script))
757796

797+
for script, redeemer in self._withdrawal_script_to_redeemers:
798+
if redeemer is not None:
799+
script_staking_credential = Address(
800+
staking_part=script_hash(script), network=self.context.network
801+
)
802+
redeemer.index = sorted_withdrawals.index(
803+
script_staking_credential.to_primitive()
804+
)
805+
758806
self.redeemers.sort(key=lambda r: r.index)
759807

760808
def _build_tx_body(self) -> TransactionBody:
@@ -1203,7 +1251,10 @@ def _update_execution_units(
12031251
assert (
12041252
r.tag is not None
12051253
), "Expected tag of redeemer to be set, but found None"
1206-
key = f"{r.tag.name.lower()}:{r.index}"
1254+
tagname = (
1255+
r.tag.name.lower() if r.tag != RedeemerTag.REWARD else "withdrawal"
1256+
)
1257+
key = f"{tagname}:{r.index}"
12071258
if (
12081259
key not in estimated_execution_units
12091260
or estimated_execution_units[key] is None

0 commit comments

Comments
 (0)