Description
Describe the bug
I am trying to extract the script context from a script invocation in PlutusV3 including Proposal Procedures. However, using the script I wrote for this purpose, the log indicates that the proposal procedure is not included correctly in the script context during evaluation. Closer investigation revealed that deepcopy on the NonEmptyOrderedSet of proposal procedures appears to return an empty set.
I was able to resolve this by adding the following method to NonEmptyOrderedSet (however am now faced with "invalid reward address header"):
def __deepcopy__(self, memodict={}):
return NonEmptyOrderedSet(deepcopy(list(x for x in self)))
To Reproduce
The following script always tries to unroll the script context parameter, raising an error in ogmios that dumps the script context
{"type": "PlutusScriptV3", "description": "", "cborHex": "58a458a20100003232232323232323374a90001bb1498c8d4008400401448c8c92610013330063758a00246eb400452f580264c66ae712410c4e616d654572726f723a207a004984c98cd5ce2481144e616d654572726f723a2076616c696461746f72004984c98cd5ce24810d4e616d654572726f723a20723300498888cc8c014894ccd55cf8008a802099aba0300335742002660040046ae8800400800c8c8c0040040041"}
script.cbor
58a20100003232232323232323374a90001bb1498c8d4008400401448c8c92610013330063758a00246eb400452f580264c66ae712410c4e616d654572726f723a207a004984c98cd5ce2481144e616d654572726f723a2076616c696461746f72004984c98cd5ce24810d4e616d654572726f723a20723300498888cc8c014894ccd55cf8008a802099aba0300335742002660040046ae8800400800c8c8c0040040041
It has following address:
addr_test1wpq4ft6y2tzhj5dq6n5ml5mwlqyuday7mznxjel9rj7ux9qqlmqnw
- Check out the opshin pioneer program: https://github.com/OpShin/opshin-pioneer-program
- Build week 2 and override the gift contract address and script cbor with the above script
- Use make gift to deposit funds at the address: https://github.com/OpShin/opshin-pioneer-program/blob/main/src/week02/scripts/make_gift.py
- Use this modified collect_gift to retrieve the script context. note the deposit integer does not appear anywhere in the logged error
# collect_gift.py
import click
import pycardano
from pycardano import (
Address,
TransactionBuilder,
UTxO,
PlutusV3Script,
plutus_script_hash,
Redeemer, AssetName, MultiAsset, Asset, Voter, VoterType, GovActionId, Anchor, Vote, GovAction,
ParameterChangeAction, InfoAction, ProtocolParamUpdate
)
from src.utils import get_address, get_signing_info, network, get_chain_context
from src.utils.network import show_tx
from src.week02 import assets_dir
@click.command()
@click.argument("name")
@click.option(
"--script",
type=click.Choice(
["burn", "custom_types", "fourty_two", "fourty_two_typed", "gift"]
),
default="gift",
help="Which lecture script address to attempt to spend.",
)
def main(name: str, script: str):
"""
Obtain deposited funds (using make_gift) from a smart contract
"""
# Load chain context
context = get_chain_context()
# Load script info
# We need `plutus_script: PlutusV2Script` and `script_address: Address`.
# There are multiple ways to get there but the simplest is to use "script.cbor"
with open(assets_dir.joinpath(script, "script.cbor"), "r") as f:
cbor_hex = f.read()
cbor = bytes.fromhex(cbor_hex)
# with open(assets_dir.joinpath(script, "script.plutus"), "r") as f:
# script_plutus = json.load(f)
# script_hex = script_plutus["cborHex"]
# cbor_wrapped = cbor2.dumps(cbor)
# cbor_wrapped_hex = cbor_wrapped.hex()
# assert script_hex == cbor_wrapped_hex
plutus_script = PlutusV3Script(cbor)
script_hash = plutus_script_hash(plutus_script)
script_address = Address(script_hash, network=network)
# with open(assets_dir.joinpath(script, "testnet.addr")) as f:
# addr = Address.from_primitive(f.read())
# assert script_address == addr
# Get payment address
payment_address = get_address(name)
# Find a script UTxO
utxo_to_spend = None
for utxo in context.utxos(script_address):
if utxo.output.datum:
utxo_to_spend = utxo
break
assert isinstance(utxo_to_spend, UTxO), f"No script UTxOs found! Execute make gift with --script {script} to deposit funds at the address."
# Build the transaction
# no output is specified since everything minus fees is sent to change address
from src.week02.lecture.custom_types import MySillyRedeemer
if script in ["fourty_two", "fourty_two_typed"]:
redeemer = Redeemer(42)
elif script == "custom_types":
from src.week02.lecture.custom_types import MySillyRedeemer
redeemer = Redeemer(MySillyRedeemer(42))
else:
redeemer = Redeemer(0)
builder = TransactionBuilder(context)
builder.add_script_input(utxo_to_spend, script=plutus_script, redeemer=redeemer)
builder.ttl = context.last_block_slot + 10
# Sign the transaction
payment_vkey, payment_skey, payment_address = get_signing_info(name)
builder.add_proposal(
deposit=1234122,
reward_account=payment_vkey.hash().payload,
gov_action=ParameterChangeAction(
gov_action_id=GovActionId(gov_action_index=0, transaction_id=utxo_to_spend.input.transaction_id),
protocol_param_update=ProtocolParamUpdate(
min_fee_b=1000,
),
policy_hash=None,
),
anchor = None
)
# TODO: deepcopy somehow removes the proposals, breaking this -> can fix by implementing __deepcopy__ in nonemptyorderedset
print(builder.proposal_procedures)
signed_tx = builder.build_and_sign(
signing_keys=[payment_skey],
change_address=payment_address,
)
# Submit the transaction
context.submit_tx(signed_tx)
show_tx(signed_tx)
if __name__ == "__main__":
main()
Logs
$ python3 src/week02/scripts/collect_gift.py niels
NonEmptyOrderedSet([{
'anchor': None,
'deposit': 1234122,
'gov_action': {
'gov_action_id': {
'gov_action_index': 0,
'transaction_id': TransactionId(hex='ec7874002d9b55a81e64eefcb3b41f8897eb36ad61c3392661f8a7c5a39879a5'),
},
'policy_hash': None,
'protocol_param_update': {
'ada_per_utxo_byte': None,
'collateral_percentage': None,
'committee_term_limit': None,
'cost_models': None,
'drep_deposit': None,
'drep_inactivity_period': None,
'drep_voting_thresholds': None,
'execution_costs': None,
'expansion_rate': None,
'governance_action_deposit': None,
'governance_action_validity_period': None,
'key_deposit': None,
'max_block_body_size': None,
'max_block_ex_units': None,
'max_block_header_size': None,
'max_collateral_inputs': None,
'max_transaction_size': None,
'max_tx_ex_units': None,
'max_value_size': None,
'maximum_epoch': None,
'min_committee_size': None,
'min_fee_a': None,
'min_fee_b': 1000,
'min_fee_ref_script_cost': None,
'min_pool_cost': None,
'n_opt': None,
'pool_deposit': None,
'pool_pledge_influence': None,
'pool_voting_thresholds': None,
'treasury_growth_rate': None,
},
},
'reward_account': b'a)\x94X\xbdm0\x11f\x9a\xc53\xb5 \xab\x07\xb9Mt(\x90?aqK\xab\xb5\x82',
}])
2025-04-24 23:00:46,020 - PyCardano - WARNING - Class: <class 'pycardano.txbuilder.TransactionBuilder'>, method: <function TransactionBuilder.build at 0x111cb4680>, state:
{
'_certificate_script_to_redeemers': [],
'_collateral_return': {
'address': addr_test1vpsjn9zch4knqytxntzn8dfq4vrmjnt59zgr7ct3fw4mtqsndwn90,
'amount': {'coin': 983727741, 'multi_asset': {}},
'datum': None,
'datum_hash': None,
'post_alonzo': False,
'script': None,
},
'_datums': {},
'_excluded_inputs': [],
'_input_addresses': [],
'_inputs': [
{'input': {
'index': 0,
'transaction_id': TransactionId(hex='ec7874002d9b55a81e64eefcb3b41f8897eb36ad61c3392661f8a7c5a39879a5'),
},
'output': {
'address': addr_test1wpq4ft6y2tzhj5dq6n5ml5mwlqyuday7mznxjel9rj7ux9qqlmqnw,
'amount': {'coin': 3000000, 'multi_asset': {}},
'datum': RawCBOR(cbor=b'\xd8y\x80'),
'datum_hash': None,
'post_alonzo': False,
'script': None,
}},
],
'_inputs_to_redeemers': {
{'input': {
'index': 0,
'transaction_id': TransactionId(hex='ec7874002d9b55a81e64eefcb3b41f8897eb36ad61c3392661f8a7c5a39879a5'),
},
'output': {
'address': addr_test1wpq4ft6y2tzhj5dq6n5ml5mwlqyuday7mznxjel9rj7ux9qqlmqnw,
'amount': {'coin': 3000000, 'multi_asset': {}},
'datum': RawCBOR(cbor=b'\xd8y\x80'),
'datum_hash': None,
'post_alonzo': False,
'script': None,
}}: {
'data': 0,
'ex_units': {'mem': 0, 'steps': 0},
'index': 0,
'tag': {
'__objclass__': <enum 'RedeemerTag'>,
'_name_': 'SPEND',
'_sort_order_': 0,
'_value_': 0,
},
},
},
'_inputs_to_scripts': {
{'input': {
'index': 0,
'transaction_id': TransactionId(hex='ec7874002d9b55a81e64eefcb3b41f8897eb36ad61c3392661f8a7c5a39879a5'),
},
'output': {
'address': addr_test1wpq4ft6y2tzhj5dq6n5ml5mwlqyuday7mznxjel9rj7ux9qqlmqnw,
'amount': {'coin': 3000000, 'multi_asset': {}},
'datum': RawCBOR(cbor=b'\xd8y\x80'),
'datum_hash': None,
'post_alonzo': False,
'script': None,
}}: b'X\xa2\x01\x00\x0022######7J\x90\x00\x1b\xb1I\x8c\x8d@\x08@\x04\x01D\x8c\x8c\x92a\x00\x133\x00cu\x8a\x00$n\xb4\x00E/X\x02d\xc6j\xe7\x12A\x0cNameError: z\x00I\x84\xc9\x8c\xd5\xce$\x81\x14NameError: validator\x00I\x84\xc9\x8c\xd5\xce$\x81\rNameError: r3\x00I\x88\x88\xcc\x8c\x01H\x94\xcc\xd5\\\xf8\x00\x8a\x80 \x99\xab\xa00\x035t \x02f\x00@\x04j\xe8\x80\x04\x00\x80\x0c\x8c\x8c\x00@\x04\x00A',
},
'_minting_script_to_redeemers': [],
'_outputs': [],
'_potential_inputs': [],
'_reference_scripts': [],
'_should_estimate_execution_units': False,
'_total_collateral': 3607615,
'_withdrawal_script_to_redeemers': [],
'auxiliary_data': None,
'certificates': None,
'collateral_return_threshold': 1000000,
'collaterals': [
{'input': {
'index': 1,
'transaction_id': TransactionId(hex='ec7874002d9b55a81e64eefcb3b41f8897eb36ad61c3392661f8a7c5a39879a5'),
},
'output': {
'address': addr_test1vpsjn9zch4knqytxntzn8dfq4vrmjnt59zgr7ct3fw4mtqsndwn90,
'amount': {'coin': 987335356, 'multi_asset': {}},
'datum': None,
'datum_hash': None,
'post_alonzo': False,
'script': None,
}},
],
'context': <pycardano.backend.ogmios_v6.OgmiosChainContext object at 0x11245f310>,
'execution_memory_buffer': 0.2,
'execution_step_buffer': 0.2,
'fee_buffer': None,
'initial_stake_pool_registration': False,
'mint': None,
'native_scripts': None,
'proposal_procedures': NonEmptyOrderedSet([{
'anchor': None,
'deposit': 1234122,
'gov_action': {
'gov_action_id': {
'gov_action_index': 0,
'transaction_id': TransactionId(hex='ec7874002d9b55a81e64eefcb3b41f8897eb36ad61c3392661f8a7c5a39879a5'),
},
'policy_hash': None,
'protocol_param_update': {
'ada_per_utxo_byte': None,
'collateral_percentage': None,
'committee_term_limit': None,
'cost_models': None,
'drep_deposit': None,
'drep_inactivity_period': None,
'drep_voting_thresholds': None,
'execution_costs': None,
'expansion_rate': None,
'governance_action_deposit': None,
'governance_action_validity_period': None,
'key_deposit': None,
'max_block_body_size': None,
'max_block_ex_units': None,
'max_block_header_size': None,
'max_collateral_inputs': None,
'max_transaction_size': None,
'max_tx_ex_units': None,
'max_value_size': None,
'maximum_epoch': None,
'min_committee_size': None,
'min_fee_a': None,
'min_fee_b': 1000,
'min_fee_ref_script_cost': None,
'min_pool_cost': None,
'n_opt': None,
'pool_deposit': None,
'pool_pledge_influence': None,
'pool_voting_thresholds': None,
'treasury_growth_rate': None,
},
},
'reward_account': b'a)\x94X\xbdm0\x11f\x9a\xc53\xb5 \xab\x07\xb9Mt(\x90?aqK\xab\xb5\x82',
}]),
'reference_inputs': set(),
'required_signers': [],
'ttl': 4953,
'use_redeemer_map': True,
'utxo_selectors': [
<pycardano.coinselection.RandomImproveMultiAsset object at 0x112485450>,
<pycardano.coinselection.LargestFirstSelector object at 0x1124854d0>,
],
'validity_start': 3943,
'withdrawals': None,
'witness_override': None,
}
Traceback (most recent call last):
File "/Users/niels/git/opshin-pioneer-program/src/week02/scripts/collect_gift.py", line 112, in <module>
main()
File "/Users/niels/miniconda3/envs/opshin-pioneer-program/lib/python3.11/site-packages/click/core.py", line 1157, in __call__
return self.main(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/niels/miniconda3/envs/opshin-pioneer-program/lib/python3.11/site-packages/click/core.py", line 1078, in main
rv = self.invoke(ctx)
^^^^^^^^^^^^^^^^
File "/Users/niels/miniconda3/envs/opshin-pioneer-program/lib/python3.11/site-packages/click/core.py", line 1434, in invoke
return ctx.invoke(self.callback, **ctx.params)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/niels/miniconda3/envs/opshin-pioneer-program/lib/python3.11/site-packages/click/core.py", line 783, in invoke
return __callback(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/niels/git/opshin-pioneer-program/src/week02/scripts/collect_gift.py", line 100, in main
signed_tx = builder.build_and_sign(
^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/niels/miniconda3/envs/opshin-pioneer-program/lib/python3.11/site-packages/pycardano/txbuilder.py", line 1688, in build_and_sign
tx_body = self.build(
^^^^^^^^^^^
File "/Users/niels/miniconda3/envs/opshin-pioneer-program/lib/python3.11/site-packages/pycardano/logging.py", line 37, in wrapper
raise e
File "/Users/niels/miniconda3/envs/opshin-pioneer-program/lib/python3.11/site-packages/pycardano/logging.py", line 28, in wrapper
output = func(obj, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/niels/miniconda3/envs/opshin-pioneer-program/lib/python3.11/site-packages/pycardano/txbuilder.py", line 1455, in build
self._update_execution_units(
File "/Users/niels/miniconda3/envs/opshin-pioneer-program/lib/python3.11/site-packages/pycardano/txbuilder.py", line 1592, in _update_execution_units
estimated_execution_units = self._estimate_execution_units(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/niels/miniconda3/envs/opshin-pioneer-program/lib/python3.11/site-packages/pycardano/txbuilder.py", line 1639, in _estimate_execution_units
return self.context.evaluate_tx(tx)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/niels/miniconda3/envs/opshin-pioneer-program/lib/python3.11/site-packages/pycardano/backend/base.py", line 209, in evaluate_tx
return self.evaluate_tx_cbor(tx.to_cbor())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/niels/miniconda3/envs/opshin-pioneer-program/lib/python3.11/site-packages/pycardano/backend/ogmios_v6.py", line 329, in evaluate_tx_cbor
result, _ = client.evaluate_transaction.execute(cbor)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/niels/miniconda3/envs/opshin-pioneer-program/lib/python3.11/site-packages/ogmios/txsubmit/EvaluateTransaction.py", line 49, in execute
return self.receive()
^^^^^^^^^^^^^^
File "/Users/niels/miniconda3/envs/opshin-pioneer-program/lib/python3.11/site-packages/ogmios/txsubmit/EvaluateTransaction.py", line 81, in receive
return self._parse_EvaluateTransaction_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/niels/miniconda3/envs/opshin-pioneer-program/lib/python3.11/site-packages/ogmios/txsubmit/EvaluateTransaction.py", line 93, in _parse_EvaluateTransaction_response
raise ResponseError(f"Ogmios responded with error: {response}")
ogmios.errors.ResponseError: Ogmios responded with error: {'jsonrpc': '2.0', 'method': 'evaluateTransaction', 'error': {'code': 3010, 'message': 'Some scripts of the transactions terminated with error(s).', 'data': [{'validator': {'index': 0, 'purpose': 'spend'}, 'error': {'code': 3012, 'message': "Some of the scripts failed to evaluate to a positive outcome. The field 'data.validationError' informs about the nature of the error, and 'data.traces' lists all the execution traces collected during the script execution.", 'data': {'validationError': "An error has occurred:\nThe machine terminated because of an error, either from a built-in function or from an explicit use of 'error'.\nCaused by: [\n (builtin unListData)\n (con\n data\n (Constr 0\n [ Constr 0\n [ List\n [ Constr 0\n [ Constr 0\n [ B #ec7874002d9b55a81e64eefcb3b41f8897eb36ad61c3392661f8a7c5a39879a5\n , I 0 ]\n , Constr 0\n [ Constr 0\n [ Constr 1\n [ B #4154af4452c57951a0d4e9bfd36ef809c6f49ed8a66967e51cbdc314 ]\n , Constr 1 [] ]\n , Map [(B #, Map [(B #, I 3000000)])]\n , Constr 2 [Constr 0 []]\n , Constr 1 [] ] ] ]\n , List []\n , List\n [ Constr 0\n [ Constr 0\n [ Constr 0\n [ B #61299458bd6d3011669ac533b520ab07b94d7428903f61714babb582 ]\n , Constr 1 [] ]\n , Map [(B #, Map [(B #, I 2822355)])]\n , Constr 0 []\n , Constr 1 [] ] ]\n , I 177645\n , Map []\n , List []\n , Map []\n , Constr 0\n [ Constr 0 [Constr 1 [I 1745503135000], Constr 1 []]\n , Constr 0 [Constr 1 [I 1745504145000], Constr 0 []] ]\n , List []\n , Map\n [ ( Constr 1\n [ Constr 0\n [ B #ec7874002d9b55a81e64eefcb3b41f8897eb36ad61c3392661f8a7c5a39879a5\n , I 0 ] ]\n , I 0 ) ]\n , Map []\n , B #570253ea0d1811d46f151ae877f9d988d27c8409b1600cc2924563976d7bdab9\n , Map []\n , List []\n , Constr 1 []\n , Constr 1 [] ]\n , I 0\n , Constr 1\n [ Constr 0\n [ B #ec7874002d9b55a81e64eefcb3b41f8897eb36ad61c3392661f8a7c5a39879a5\n , I 0 ]\n , Constr 0 [Constr 0 []] ] ])\n )\n]", 'traces': ['Expected the List constructor but got a different one']}}}]}, 'id': None}
Expected behavior
Should include the proposal procedures in the script context
Environment and software version (please complete the following information):
- OS: MacOS on a custom devnet (yaci)
- PyCardano Version: 0.13.2