Skip to content

Commit d91391b

Browse files
committed
Merge remote-tracking branch 'origin/main' into fine_william___fine
2 parents ace36ba + 4b412bd commit d91391b

File tree

9 files changed

+428
-329
lines changed

9 files changed

+428
-329
lines changed

chia/_tests/cmds/wallet/test_wallet.py

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
)
2727
from chia.cmds.cmds_util import TransactionBundle
2828
from chia.protocols.outbound_message import NodeType
29-
from chia.types.blockchain_format.program import Program
3029
from chia.types.signing_mode import SigningMode
3130
from chia.util.bech32m import encode_puzzle_hash
3231
from chia.wallet.conditions import Condition, ConditionValidTimes
@@ -47,6 +46,7 @@
4746
CATAssetIDToNameResponse,
4847
CATSetName,
4948
CATSetNameResponse,
49+
CATSpend,
5050
CATSpendResponse,
5151
ClawbackPuzzleDecoratorOverride,
5252
CreateOfferForIDsResponse,
@@ -385,31 +385,24 @@ async def send_transaction(
385385

386386
async def cat_spend(
387387
self,
388-
wallet_id: int,
388+
request: CATSpend,
389389
tx_config: TXConfig,
390-
amount: Optional[uint64] = None,
391-
inner_address: Optional[str] = None,
392-
fee: uint64 = uint64(0),
393-
memos: Optional[list[str]] = None,
394-
additions: Optional[list[dict[str, Any]]] = None,
395-
removals: Optional[list[Coin]] = None,
396-
cat_discrepancy: Optional[tuple[int, Program, Program]] = None, # (extra_delta, tail_reveal, tail_solution)
397-
push: bool = True,
390+
extra_conditions: tuple[Condition, ...] = tuple(),
398391
timelock_info: ConditionValidTimes = ConditionValidTimes(),
399392
) -> CATSpendResponse:
400393
self.add_to_log(
401394
"cat_spend",
402395
(
403-
wallet_id,
396+
request.wallet_id,
404397
tx_config,
405-
amount,
406-
inner_address,
407-
fee,
408-
memos,
409-
additions,
410-
removals,
411-
cat_discrepancy,
412-
push,
398+
request.amount,
399+
request.inner_address,
400+
request.fee,
401+
request.memos,
402+
request.additions,
403+
request.coins,
404+
request.cat_discrepancy,
405+
request.push,
413406
timelock_info,
414407
),
415408
)

chia/_tests/wallet/rpc/test_wallet_rpc.py

Lines changed: 87 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import json
77
import logging
88
import random
9+
import re
910
from collections.abc import AsyncIterator
1011
from operator import attrgetter
1112
from typing import Any, Optional
@@ -108,6 +109,7 @@
108109
CATGetAssetID,
109110
CATGetName,
110111
CATSetName,
112+
CATSpend,
111113
CheckDeleteKey,
112114
CheckOfferValidity,
113115
ClawbackPuzzleDecoratorOverride,
@@ -1255,18 +1257,69 @@ async def test_cat_endpoints(wallet_environments: WalletTestFramework, wallet_ty
12551257
# Test CAT spend without a fee
12561258
with pytest.raises(ValueError):
12571259
await env_0.rpc_client.cat_spend(
1258-
cat_0_id,
1259-
DEFAULT_TX_CONFIG.override(
1260+
CATSpend(
1261+
wallet_id=cat_0_id,
1262+
amount=uint64(4),
1263+
inner_address=addr_1,
1264+
fee=uint64(0),
1265+
memos=["the cat memo"],
1266+
push=False,
1267+
),
1268+
tx_config=wallet_environments.tx_config.override(
12601269
excluded_coin_amounts=[uint64(100)],
12611270
excluded_coin_ids=[bytes32.zeros],
12621271
),
1263-
uint64(4),
1264-
addr_1,
1265-
uint64(0),
1266-
["the cat memo"],
12671272
)
1273+
1274+
# Test some validation errors
1275+
with pytest.raises(
1276+
ValueError,
1277+
match=re.escape('Must specify "additions" or "amount"+"inner_address"+"memos", but not both.'),
1278+
):
1279+
await env_0.rpc_client.cat_spend(
1280+
CATSpend(
1281+
wallet_id=cat_0_id,
1282+
amount=uint64(4),
1283+
inner_address=addr_1,
1284+
memos=["the cat memo"],
1285+
additions=[],
1286+
),
1287+
tx_config=wallet_environments.tx_config,
1288+
)
1289+
1290+
with pytest.raises(ValueError, match=re.escape('Must specify "amount" and "inner_address" together.')):
1291+
await env_0.rpc_client.cat_spend(
1292+
CATSpend(
1293+
wallet_id=cat_0_id,
1294+
amount=uint64(4),
1295+
inner_address=None,
1296+
),
1297+
tx_config=wallet_environments.tx_config,
1298+
)
1299+
1300+
with pytest.raises(
1301+
ValueError,
1302+
match=re.escape('Must specify \\"extra_delta\\", \\"tail_reveal\\" and \\"tail_solution\\" together.'),
1303+
):
1304+
await env_0.rpc_client.cat_spend(
1305+
CATSpend(
1306+
wallet_id=cat_0_id,
1307+
additions=[],
1308+
extra_delta="1",
1309+
),
1310+
tx_config=wallet_environments.tx_config,
1311+
)
1312+
12681313
tx_res = await env_0.rpc_client.cat_spend(
1269-
cat_0_id, wallet_environments.tx_config, uint64(4), addr_1, uint64(0), ["the cat memo"]
1314+
CATSpend(
1315+
wallet_id=cat_0_id,
1316+
amount=uint64(4),
1317+
inner_address=addr_1,
1318+
fee=uint64(0),
1319+
memos=["the cat memo"],
1320+
push=True,
1321+
),
1322+
tx_config=wallet_environments.tx_config,
12701323
)
12711324

12721325
spend_bundle = tx_res.transaction.spend_bundle
@@ -1312,7 +1365,15 @@ async def test_cat_endpoints(wallet_environments: WalletTestFramework, wallet_ty
13121365

13131366
# Test CAT spend with a fee
13141367
tx_res = await env_0.rpc_client.cat_spend(
1315-
cat_0_id, wallet_environments.tx_config, uint64(1), addr_1, uint64(5_000_000), ["the cat memo"]
1368+
CATSpend(
1369+
wallet_id=cat_0_id,
1370+
amount=uint64(1),
1371+
inner_address=addr_1,
1372+
fee=uint64(5_000_000),
1373+
memos=["the cat memo"],
1374+
push=True,
1375+
),
1376+
wallet_environments.tx_config,
13161377
)
13171378

13181379
spend_bundle = tx_res.transaction.spend_bundle
@@ -1379,13 +1440,16 @@ async def test_cat_endpoints(wallet_environments: WalletTestFramework, wallet_ty
13791440
)
13801441
)
13811442
tx_res = await env_0.rpc_client.cat_spend(
1382-
cat_0_id,
1443+
CATSpend(
1444+
wallet_id=cat_0_id,
1445+
amount=uint64(1),
1446+
inner_address=addr_1,
1447+
fee=uint64(5_000_000),
1448+
memos=["the cat memo"],
1449+
coins=select_coins_response.coins,
1450+
push=True,
1451+
),
13831452
wallet_environments.tx_config,
1384-
uint64(1),
1385-
addr_1,
1386-
uint64(5_000_000),
1387-
["the cat memo"],
1388-
removals=select_coins_response.coins,
13891453
)
13901454

13911455
spend_bundle = tx_res.transaction.spend_bundle
@@ -3109,11 +3173,16 @@ async def test_cat_spend_run_tail(wallet_rpc_environment: WalletRpcTestEnvironme
31093173
# Attempt to melt it fully
31103174
tx = (
31113175
await client.cat_spend(
3112-
cat_wallet_id,
3113-
amount=uint64(0),
3176+
CATSpend(
3177+
wallet_id=cat_wallet_id,
3178+
amount=uint64(0),
3179+
inner_address=encode_puzzle_hash(our_ph, "txch"),
3180+
extra_delta=str(tx_amount * -1),
3181+
tail_reveal=b"\x80",
3182+
tail_solution=b"\x80",
3183+
push=True,
3184+
),
31143185
tx_config=DEFAULT_TX_CONFIG,
3115-
inner_address=encode_puzzle_hash(our_ph, "txch"),
3116-
cat_discrepancy=(tx_amount * -1, Program.to(None), Program.to(None)),
31173186
)
31183187
).transaction
31193188
transaction_id = tx.name

chia/_tests/wallet/vc_wallet/test_vc_wallet.py

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from chia.wallet.wallet import Wallet
3232
from chia.wallet.wallet_node import WalletNode
3333
from chia.wallet.wallet_request_types import (
34+
CATSpend,
3435
GetTransactions,
3536
GetWallets,
3637
VCAddProofs,
@@ -383,12 +384,15 @@ async def test_vc_lifecycle(wallet_environments: WalletTestFramework) -> None:
383384
wallet_1_addr = encode_puzzle_hash(wallet_1_ph, "txch")
384385
txs = (
385386
await client_0.cat_spend(
386-
cr_cat_wallet_0.id(),
387+
CATSpend(
388+
wallet_id=cr_cat_wallet_0.id(),
389+
amount=uint64(90),
390+
inner_address=wallet_1_addr,
391+
fee=uint64(2000000000),
392+
memos=["hey"],
393+
push=True,
394+
),
387395
wallet_environments.tx_config,
388-
uint64(90),
389-
wallet_1_addr,
390-
uint64(2000000000),
391-
memos=["hey"],
392396
)
393397
).transactions
394398
await wallet_environments.process_pending_states(
@@ -557,23 +561,30 @@ async def test_vc_lifecycle(wallet_environments: WalletTestFramework) -> None:
557561
# (Negative test) Try to spend a CR-CAT that we don't have a valid VC for
558562
with pytest.raises(ValueError):
559563
await client_0.cat_spend(
560-
cr_cat_wallet_0.id(),
561-
wallet_environments.tx_config,
562-
uint64(10),
563-
wallet_1_addr,
564+
CATSpend(
565+
wallet_id=cr_cat_wallet_0.id(),
566+
amount=uint64(10),
567+
inner_address=wallet_1_addr,
568+
),
569+
tx_config=wallet_environments.tx_config,
564570
)
565571

566572
# Test melting a CRCAT
567573
# This is intended to trigger an edge case where the output and change are the same forcing a new puzhash
568574
with wallet_environments.new_puzzle_hashes_allowed():
569575
tx = (
570576
await client_1.cat_spend(
571-
env_1.dealias_wallet_id("crcat"),
572-
wallet_environments.tx_config,
573-
uint64(20),
574-
wallet_1_addr,
575-
uint64(0),
576-
cat_discrepancy=(-50, Program.to(None), Program.to(None)),
577+
CATSpend(
578+
wallet_id=env_1.dealias_wallet_id("crcat"),
579+
amount=uint64(20),
580+
inner_address=wallet_1_addr,
581+
fee=uint64(0),
582+
extra_delta=str(-50),
583+
tail_reveal=b"\x80",
584+
tail_solution=b"\x80",
585+
push=True,
586+
),
587+
tx_config=wallet_environments.tx_config,
577588
)
578589
).transaction
579590
[tx] = await wallet_node_1.wallet_state_manager.add_pending_transactions([tx])

chia/cmds/wallet_funcs.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
CATAssetIDToNameResponse,
5050
CATGetName,
5151
CATSetName,
52+
CATSpend,
5253
CATSpendResponse,
5354
ClawbackPuzzleDecoratorOverride,
5455
DeleteNotifications,
@@ -401,18 +402,20 @@ async def send(
401402
elif typ in {WalletType.CAT, WalletType.CRCAT, WalletType.RCAT}:
402403
print("Submitting transaction...")
403404
res = await wallet_client.cat_spend(
404-
wallet_id,
405-
CMDTXConfigLoader(
405+
CATSpend(
406+
wallet_id=uint32(wallet_id),
407+
amount=final_amount,
408+
inner_address=address.original_address,
409+
fee=fee,
410+
memos=memos,
411+
push=push,
412+
),
413+
tx_config=CMDTXConfigLoader(
406414
min_coin_amount=min_coin_amount,
407415
max_coin_amount=max_coin_amount,
408416
excluded_coin_ids=list(excluded_coin_ids),
409417
reuse_puzhash=reuse_puzhash,
410418
).to_tx_config(mojo_per_unit, config, fingerprint),
411-
final_amount,
412-
address.original_address,
413-
fee,
414-
memos,
415-
push=push,
416419
timelock_info=condition_valid_times,
417420
)
418421
else:

chia/wallet/wallet_request_types.py

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1379,6 +1379,61 @@ class CombineCoinsResponse(TransactionEndpointResponse):
13791379
pass
13801380

13811381

1382+
# utility for CATSpend
1383+
# unfortunate that we can't use CreateCoin but the memos are taken as strings not bytes
1384+
@streamable
1385+
@dataclass(frozen=True)
1386+
class Addition(Streamable):
1387+
amount: uint64
1388+
puzzle_hash: bytes32
1389+
memos: Optional[list[str]] = None
1390+
1391+
1392+
@streamable
1393+
@kw_only_dataclass
1394+
class CATSpend(TransactionEndpointRequest):
1395+
wallet_id: uint32 = field(default_factory=default_raise)
1396+
additions: Optional[list[Addition]] = None
1397+
amount: Optional[uint64] = None
1398+
inner_address: Optional[str] = None
1399+
memos: Optional[list[str]] = None
1400+
coins: Optional[list[Coin]] = None
1401+
extra_delta: Optional[str] = None # str to support negative ints :(
1402+
tail_reveal: Optional[bytes] = None
1403+
tail_solution: Optional[bytes] = None
1404+
1405+
def __post_init__(self) -> None:
1406+
if (
1407+
self.additions is not None
1408+
and (self.amount is not None or self.inner_address is not None or self.memos is not None)
1409+
) or (self.additions is None and self.amount is None and self.inner_address is None and self.memos is None):
1410+
raise ValueError('Must specify "additions" or "amount"+"inner_address"+"memos", but not both.')
1411+
elif self.additions is None and None in {self.amount, self.inner_address}:
1412+
raise ValueError('Must specify "amount" and "inner_address" together.')
1413+
super().__post_init__()
1414+
1415+
@property
1416+
def cat_discrepancy(self) -> Optional[tuple[int, Program, Program]]:
1417+
if self.extra_delta is None and self.tail_reveal is None and self.tail_solution is None:
1418+
return None
1419+
elif None in {self.extra_delta, self.tail_reveal, self.tail_solution}:
1420+
raise ValueError('Must specify "extra_delta", "tail_reveal" and "tail_solution" together.')
1421+
else:
1422+
# Curious that mypy doesn't see the elif and know that none of these are None
1423+
return (
1424+
int(self.extra_delta), # type: ignore[arg-type]
1425+
Program.from_bytes(self.tail_reveal), # type: ignore[arg-type]
1426+
Program.from_bytes(self.tail_solution), # type: ignore[arg-type]
1427+
)
1428+
1429+
1430+
@streamable
1431+
@dataclass(frozen=True)
1432+
class CATSpendResponse(TransactionEndpointResponse):
1433+
transaction: TransactionRecord
1434+
transaction_id: bytes32
1435+
1436+
13821437
@streamable
13831438
@dataclass(frozen=True, kw_only=True)
13841439
class DIDMessageSpend(TransactionEndpointRequest):
@@ -1771,13 +1826,6 @@ class CreateSignedTransactionsResponse(TransactionEndpointResponse):
17711826
signed_tx: TransactionRecord
17721827

17731828

1774-
@streamable
1775-
@dataclass(frozen=True)
1776-
class CATSpendResponse(TransactionEndpointResponse):
1777-
transaction: TransactionRecord
1778-
transaction_id: bytes32
1779-
1780-
17811829
@streamable
17821830
@dataclass(frozen=True)
17831831
class _OfferEndpointResponse(TransactionEndpointResponse):

0 commit comments

Comments
 (0)