pivot from string to clvm for dict entries

This commit is contained in:
Matt Hauff 2022-04-21 16:09:43 -07:00 committed by Amine Khaldi
parent ca57b578f5
commit 20aa9b3476
No known key found for this signature in database
GPG Key ID: B1C074FFC904E2D9
9 changed files with 171 additions and 103 deletions

View File

@ -32,7 +32,8 @@ from chia.wallet.derive_keys import (
match_address_to_sk,
)
from chia.wallet.did_wallet.did_wallet import DIDWallet
from chia.wallet.outer_puzzles import AssetType, PuzzleInfo
from chia.wallet.outer_puzzles import AssetType
from chia.wallet.puzzle_drivers import PuzzleInfo
from chia.wallet.rl_wallet.rl_wallet import RLWallet
from chia.wallet.trade_record import TradeRecord
from chia.wallet.trading.offer import Offer
@ -925,7 +926,7 @@ class WalletRpcApi:
driver_dict: Dict[bytes32, PuzzleInfo] = {}
if driver_dict_str is None:
for key in offer:
if len(key) == 32:
if len(key) == 64:
asset_id = bytes32.from_hexstr(key)
driver_dict[asset_id] = PuzzleInfo({"type": AssetType.CAT, "tail": asset_id})
else:

View File

@ -0,0 +1,71 @@
from dataclasses import dataclass
from typing import Any, Optional
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.coin_spend import CoinSpend
from chia.util.ints import uint64
from chia.wallet.cat_wallet.cat_utils import (
CAT_MOD,
SpendableCAT,
construct_cat_puzzle,
match_cat_puzzle,
unsigned_spend_bundle_for_spendable_cats,
)
from chia.wallet.lineage_proof import LineageProof
from chia.wallet.puzzle_drivers import PuzzleInfo, Solver
@dataclass(frozen=True)
class CATOuterPuzzle:
_match: Any
_asset_id: Any
_construct: Any
_solve: Any
def match(self, puzzle: Program) -> Optional[PuzzleInfo]:
matched, curried_args = match_cat_puzzle(puzzle)
if matched:
_, tail_hash, inner_puzzle = curried_args
constructor_dict = {
"type": "CAT",
"tail": "0x" + tail_hash.as_python().hex(),
}
next_constructor = self._match(inner_puzzle)
if next_constructor is not None:
constructor_dict["and"] = next_constructor.info
return PuzzleInfo(constructor_dict)
else:
return None
def asset_id(self, constructor: PuzzleInfo) -> Optional[bytes32]:
return bytes32(constructor["tail"])
def construct(self, constructor: PuzzleInfo, inner_puzzle: Program) -> Program:
if constructor.also() is not None:
inner_puzzle = self._construct(constructor.also(), inner_puzzle)
return construct_cat_puzzle(CAT_MOD, constructor["tail"], inner_puzzle)
def solve(self, constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program:
tail_hash: bytes32 = constructor["tail"]
coin_bytes: bytes = solver["coin"]
coin: Coin = Coin(bytes32(coin_bytes[0:32]), bytes32(coin_bytes[32:64]), uint64.from_bytes(coin_bytes[64:72]))
parent_spend: CoinSpend = CoinSpend.from_bytes(solver["parent_spend"])
parent_coin: Coin = parent_spend.coin
if constructor.also() is not None:
inner_puzzle = self._construct(constructor.also(), inner_puzzle)
inner_solution = self._solve(constructor.also(), solver, inner_puzzle, inner_solution)
matched, curried_args = match_cat_puzzle(parent_spend.puzzle_reveal.to_program())
assert matched
_, _, parent_inner_puzzle = curried_args
spendable_cat = SpendableCAT(
coin,
tail_hash,
inner_puzzle,
inner_solution,
lineage_proof=LineageProof(
parent_coin.parent_coin_info, parent_inner_puzzle.get_tree_hash(), parent_coin.amount
),
)
return unsigned_spend_bundle_for_spendable_cats(CAT_MOD, [spendable_cat]).coin_spends[0].solution.to_program()

View File

@ -37,7 +37,8 @@ from chia.wallet.cat_wallet.lineage_store import CATLineageStore
from chia.wallet.coin_selection import select_coins
from chia.wallet.derivation_record import DerivationRecord
from chia.wallet.lineage_proof import LineageProof
from chia.wallet.outer_puzzles import AssetType, PuzzleInfo
from chia.wallet.outer_puzzles import AssetType
from chia.wallet.puzzle_drivers import PuzzleInfo
from chia.wallet.payment import Payment
from chia.wallet.puzzles.tails import ALL_LIMITATIONS_PROGRAMS
from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import (
@ -218,7 +219,7 @@ class CATWallet:
return await cls.create_wallet_for_cat(
wallet_state_manager,
wallet,
puzzle_driver.info["tail"].hex(),
puzzle_driver["tail"].hex(),
name,
in_transaction,
)
@ -811,8 +812,8 @@ class CATWallet:
def match_puzzle_info(self, puzzle_driver: PuzzleInfo) -> bool:
if (
puzzle_driver.info["type"] == AssetType.CAT
and puzzle_driver.info["tail"] == bytes.fromhex(self.get_asset_id())
AssetType(puzzle_driver.type()) == AssetType.CAT
and puzzle_driver["tail"] == bytes.fromhex(self.get_asset_id())
and "and" not in puzzle_driver.info
):
return True

View File

@ -1,91 +1,16 @@
from enum import Enum
from dataclasses import dataclass
from typing import Any, Dict, Optional
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.coin_spend import CoinSpend
from chia.wallet.cat_wallet.cat_utils import (
CAT_MOD,
SpendableCAT,
construct_cat_puzzle,
match_cat_puzzle,
unsigned_spend_bundle_for_spendable_cats,
)
from chia.wallet.lineage_proof import LineageProof
from chia.wallet.cat_wallet.cat_outer_puzzle import CATOuterPuzzle
from chia.wallet.puzzle_drivers import PuzzleInfo, Solver
class AssetType(Enum):
CAT = "CAT"
@dataclass(frozen=True)
class PuzzleInfo:
info: Dict[str, Any]
@dataclass(frozen=True)
class Solver:
info: Dict[str, Any]
class CATOuterPuzzle:
@classmethod
def match(cls, puzzle: Program) -> Optional[PuzzleInfo]:
matched, curried_args = match_cat_puzzle(puzzle)
if matched:
_, tail_hash, inner_puzzle = curried_args
constructor_dict = {
"type": AssetType.CAT,
"tail": bytes32(tail_hash.as_python()),
}
next_constructor = match_puzzle(inner_puzzle)
if next_constructor is not None:
constructor_dict["and"] = next_constructor.info
return PuzzleInfo(constructor_dict)
else:
return None
@classmethod
def asset_id(cls, constructor: PuzzleInfo) -> Optional[bytes32]:
return bytes32(constructor.info["tail"])
@classmethod
def construct(cls, constructor: PuzzleInfo, inner_puzzle: Program) -> Program:
if "and" in constructor.info:
inner_puzzle = construct_puzzle(constructor.info["and"], inner_puzzle)
return construct_cat_puzzle(CAT_MOD, constructor.info["tail"], inner_puzzle)
@classmethod
def solve(cls, constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program:
tail_hash: bytes32 = constructor.info["tail"]
coin: Coin = solver.info["coin"]
parent_spend: CoinSpend = solver.info["parent_spend"]
parent_coin: Coin = parent_spend.coin
if "and" in constructor.info:
inner_puzzle = construct_puzzle(PuzzleInfo(constructor.info["and"]), inner_puzzle)
inner_solution = solve_puzzle(PuzzleInfo(constructor.info["and"]), solver, inner_puzzle, inner_solution)
matched, curried_args = match_cat_puzzle(parent_spend.puzzle_reveal.to_program())
assert matched
_, _, parent_inner_puzzle = curried_args
spendable_cat = SpendableCAT(
coin,
tail_hash,
inner_puzzle,
inner_solution,
lineage_proof=LineageProof(
parent_coin.parent_coin_info, parent_inner_puzzle.get_tree_hash(), parent_coin.amount
),
)
return unsigned_spend_bundle_for_spendable_cats(CAT_MOD, [spendable_cat]).coin_spends[0].solution.to_program()
driver_lookup: Dict[AssetType, Any] = {
AssetType.CAT: CATOuterPuzzle,
}
def match_puzzle(puzzle: Program) -> Optional[PuzzleInfo]:
for driver in driver_lookup.values():
potential_info: Optional[PuzzleInfo] = driver.match(puzzle)
@ -95,14 +20,21 @@ def match_puzzle(puzzle: Program) -> Optional[PuzzleInfo]:
def construct_puzzle(constructor: PuzzleInfo, inner_puzzle: Program) -> Program:
return driver_lookup[constructor.info["type"]].construct(constructor, inner_puzzle) # type: ignore
return driver_lookup[AssetType(constructor.type())].construct(constructor, inner_puzzle) # type: ignore
def solve_puzzle(constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program:
return driver_lookup[constructor.info["type"]].solve( # type: ignore
return driver_lookup[AssetType(constructor.type())].solve( # type: ignore
constructor, solver, inner_puzzle, inner_solution
)
def create_asset_id(constructor: PuzzleInfo) -> bytes32:
return driver_lookup[constructor.info["type"]].asset_id(constructor) # type: ignore
return driver_lookup[AssetType(constructor.type())].asset_id(constructor) # type: ignore
function_args = [match_puzzle, construct_puzzle, solve_puzzle, create_asset_id]
driver_lookup: Dict[AssetType, Any] = {
AssetType.CAT: CATOuterPuzzle(*function_args),
}

View File

@ -0,0 +1,52 @@
from clvm.casts import int_from_bytes
from clvm_tools.binutils import assemble, type_for_atom
from dataclasses import dataclass
from ir.Type import Type
from typing import Any, Dict, Optional
@dataclass(frozen=True)
class PuzzleInfo:
info: Dict[str, Any]
def __post_init__(self) -> None:
if "type" not in self.info:
raise ValueError("A type is required to initialize a puzzle driver")
def __getitem__(self, item: str) -> Any:
value = self.info[decode_info_value(PuzzleInfo, item)]
return decode_info_value(PuzzleInfo, value)
def type(self) -> str:
return str(self.info["type"])
def also(self) -> Optional["PuzzleInfo"]:
if "also" in self.info:
return PuzzleInfo(self.info["also"])
else:
return None
@dataclass(frozen=True)
class Solver:
info: Dict[str, Any]
def __getitem__(self, item: str) -> Any:
value = self.info[decode_info_value(Solver, item)]
return decode_info_value(Solver, value)
def decode_info_value(cls: Any, value: Any) -> Any:
if isinstance(value, dict):
return cls(value)
elif isinstance(value, list):
return [decode_info_value(cls, v) for v in value]
else:
atom: bytes = assemble(value).as_atom() # type: ignore
typ = type_for_atom(atom)
if typ == Type.QUOTES:
return bytes(atom).decode("utf8")
elif typ == Type.INT:
return int_from_bytes(atom)
else:
return atom

View File

@ -12,7 +12,8 @@ from chia.types.spend_bundle import SpendBundle
from chia.util.db_wrapper import DBWrapper
from chia.util.hash import std_hash
from chia.util.ints import uint32, uint64
from chia.wallet.outer_puzzles import AssetType, PuzzleInfo
from chia.wallet.outer_puzzles import AssetType
from chia.wallet.puzzle_drivers import PuzzleInfo
from chia.wallet.payment import Payment
from chia.wallet.trade_record import TradeRecord
from chia.wallet.trading.offer import Offer, NotarizedPayment
@ -302,13 +303,13 @@ class TradeManager:
elif wallet.type() == WalletType.CAT:
key = bytes32(bytes.fromhex(wallet.get_asset_id()))
memos = [p2_ph]
if key in driver_dict and driver_dict[key].info["type"] != AssetType.CAT:
if key in driver_dict and AssetType(driver_dict[key].type()) != AssetType.CAT:
raise ValueError(
f"driver_dict specified {driver_dict[key].info['type']},"
f"driver_dict specified {AssetType(driver_dict[key].type())},"
f"was expecting {AssetType.CAT}"
)
else:
driver_dict[key] = PuzzleInfo({"type": AssetType.CAT, "tail": key})
driver_dict[key] = PuzzleInfo({"type": AssetType.CAT.value, "tail": "0x" + key.hex()})
else:
raise ValueError(f"Offers are not implemented for {wallet.type()}")
else:
@ -327,13 +328,15 @@ class TradeManager:
# ATTENTION: new wallets
if wallet.type() == WalletType.CAT:
asset_id = bytes32(bytes.fromhex(wallet.get_asset_id()))
if asset_id in driver_dict and driver_dict[asset_id].info["type"] != AssetType.CAT:
if asset_id in driver_dict and AssetType(driver_dict[asset_id].type()) != AssetType.CAT:
raise ValueError(
f"driver_dict specified {driver_dict[asset_id].info['type']},"
f"driver_dict specified {AssetType(driver_dict[asset_id].type())},"
f"was expecting {AssetType.CAT}"
)
else:
driver_dict[asset_id] = PuzzleInfo({"type": AssetType.CAT, "tail": asset_id})
driver_dict[asset_id] = PuzzleInfo(
{"type": AssetType.CAT.value, "tail": "0x" + asset_id.hex()}
)
elif amount == 0:
raise ValueError("You cannot offer nor request 0 amount of something")

View File

@ -16,13 +16,12 @@ from chia.wallet.util.puzzle_compression import (
lowest_best_version,
)
from chia.wallet.outer_puzzles import (
PuzzleInfo,
Solver,
create_asset_id,
construct_puzzle,
match_puzzle,
solve_puzzle,
)
from chia.wallet.puzzle_drivers import PuzzleInfo, Solver
from chia.wallet.puzzles.load_clvm import load_clvm
from chia.wallet.payment import Payment
@ -303,8 +302,11 @@ class Offer:
self.driver_dict[asset_id],
Solver(
{
"coin": coin,
"parent_spend": parent_spend,
"coin": "0x"
+ coin.parent_coin_info.hex()
+ coin.puzzle_hash.hex()
+ bytes(coin.amount).hex(),
"parent_spend": "0x" + bytes(parent_spend).hex(),
}
),
OFFER_MOD,

View File

@ -37,7 +37,8 @@ from chia.wallet.cat_wallet.cat_constants import DEFAULT_CATS
from chia.wallet.derivation_record import DerivationRecord
from chia.wallet.derive_keys import master_sk_to_wallet_sk, master_sk_to_wallet_sk_unhardened
from chia.wallet.key_val_store import KeyValStore
from chia.wallet.outer_puzzles import AssetType, PuzzleInfo
from chia.wallet.outer_puzzles import AssetType
from chia.wallet.puzzle_drivers import PuzzleInfo
from chia.wallet.puzzles.cat_loader import CAT_MOD
from chia.wallet.rl_wallet.rl_wallet import RLWallet
from chia.wallet.settings.user_settings import UserSettings
@ -1149,9 +1150,9 @@ class WalletStateManager:
return None
async def create_wallet_for_puzzle_info(self, puzzle_driver: PuzzleInfo, name=None, in_transaction=False):
if puzzle_driver.info["type"] in self.asset_to_wallet_map:
if AssetType(puzzle_driver.type()) in self.asset_to_wallet_map:
async with self.lock:
await self.asset_to_wallet_map[puzzle_driver.info["type"]].create_from_puzzle_info(
await self.asset_to_wallet_map[AssetType(puzzle_driver.type())].create_from_puzzle_info(
self,
self.main_wallet,
puzzle_driver,

View File

@ -17,7 +17,8 @@ from chia.wallet.cat_wallet.cat_utils import (
SpendableCAT,
unsigned_spend_bundle_for_spendable_cats,
)
from chia.wallet.outer_puzzles import AssetType, PuzzleInfo
from chia.wallet.outer_puzzles import AssetType
from chia.wallet.puzzle_drivers import PuzzleInfo
from chia.wallet.payment import Payment
from chia.wallet.trading.offer import Offer, NotarizedPayment
from tests.clvm.benchmark_costs import cost_of_spend_bundle
@ -180,8 +181,12 @@ class TestOfferLifecycle:
blue_coins: List[Coin] = all_coins["blue"]
driver_dict: Dict[bytes32, PuzzleInfo] = {
str_to_tail_hash("red"): PuzzleInfo({"type": AssetType.CAT, "tail": str_to_tail_hash("red")}),
str_to_tail_hash("blue"): PuzzleInfo({"type": AssetType.CAT, "tail": str_to_tail_hash("blue")}),
str_to_tail_hash("red"): PuzzleInfo(
{"type": AssetType.CAT.value, "tail": "0x" + str_to_tail_hash("red").hex()}
),
str_to_tail_hash("blue"): PuzzleInfo(
{"type": AssetType.CAT.value, "tail": "0x" + str_to_tail_hash("blue").hex()}
),
}
# Create an XCH Offer for RED