190 lines
8.1 KiB
Python
190 lines
8.1 KiB
Python
from clvm_tools import binutils
|
|
|
|
from chia.types.blockchain_format.program import Program, INFINITE_COST
|
|
from chia.types.announcement import Announcement
|
|
from chia.types.blockchain_format.coin import Coin
|
|
from chia.types.blockchain_format.sized_bytes import bytes32
|
|
from chia.util.condition_tools import parse_sexp_to_conditions
|
|
from chia.wallet.puzzles.load_clvm import load_clvm
|
|
|
|
SINGLETON_MOD = load_clvm("singleton_top_layer.clvm")
|
|
LAUNCHER_PUZZLE = load_clvm("singleton_launcher.clvm")
|
|
P2_SINGLETON_MOD = load_clvm("p2_singleton.clvm")
|
|
POOL_MEMBER_MOD = load_clvm("pool_member_innerpuz.clvm")
|
|
POOL_ESCAPING_MOD = load_clvm("pool_escaping_innerpuz.clvm")
|
|
|
|
LAUNCHER_PUZZLE_HASH = LAUNCHER_PUZZLE.get_tree_hash()
|
|
SINGLETON_MOD_HASH = SINGLETON_MOD.get_tree_hash()
|
|
|
|
POOL_REWARD_PREFIX_MAINNET = bytes32.fromhex("ccd5bb71183532bff220ba46c268991a00000000000000000000000000000000")
|
|
|
|
LAUNCHER_ID = Program.to(b"launcher-id").get_tree_hash()
|
|
|
|
|
|
def singleton_puzzle(launcher_id: Program, launcher_puzzle_hash: bytes32, inner_puzzle: Program) -> Program:
|
|
return SINGLETON_MOD.curry(SINGLETON_MOD_HASH, launcher_id, launcher_puzzle_hash, inner_puzzle)
|
|
|
|
|
|
def p2_singleton_puzzle(launcher_id: Program, launcher_puzzle_hash: bytes32) -> Program:
|
|
return P2_SINGLETON_MOD.curry(SINGLETON_MOD_HASH, launcher_id, launcher_puzzle_hash)
|
|
|
|
|
|
def singleton_puzzle_hash(launcher_id: Program, launcher_puzzle_hash: bytes32, inner_puzzle: Program) -> bytes32:
|
|
return singleton_puzzle(launcher_id, launcher_puzzle_hash, inner_puzzle).get_tree_hash()
|
|
|
|
|
|
def p2_singleton_puzzle_hash(launcher_id: Program, launcher_puzzle_hash: bytes32) -> bytes32:
|
|
return p2_singleton_puzzle(launcher_id, launcher_puzzle_hash).get_tree_hash()
|
|
|
|
|
|
def test_only_odd_coins():
|
|
did_core_hash = SINGLETON_MOD.get_tree_hash()
|
|
# (MOD_HASH LAUNCHER_ID INNERPUZ parent_info my_amount inner_solution)
|
|
solution = Program.to(
|
|
[
|
|
did_core_hash,
|
|
LAUNCHER_ID,
|
|
LAUNCHER_PUZZLE_HASH,
|
|
Program.to(binutils.assemble("(q (51 0xcafef00d 200))")),
|
|
[0xDEADBEEF, 0xCAFEF00D, 200],
|
|
200,
|
|
[],
|
|
]
|
|
)
|
|
try:
|
|
cost, result = SINGLETON_MOD.run_with_cost(INFINITE_COST, solution)
|
|
except Exception as e:
|
|
assert e.args == ("clvm raise",)
|
|
else:
|
|
assert False
|
|
|
|
solution = Program.to(
|
|
[
|
|
did_core_hash,
|
|
LAUNCHER_ID,
|
|
LAUNCHER_PUZZLE_HASH,
|
|
1,
|
|
[0xDEADBEEF, 0xCAFED00D, 210],
|
|
205,
|
|
[[51, 0xCAFEF00D, 205]],
|
|
]
|
|
)
|
|
try:
|
|
cost, result = SINGLETON_MOD.run_with_cost(INFINITE_COST, solution)
|
|
except Exception:
|
|
assert False
|
|
|
|
|
|
def test_only_one_odd_coin_created():
|
|
did_core_hash = SINGLETON_MOD.get_tree_hash()
|
|
solution = Program.to(
|
|
[
|
|
did_core_hash,
|
|
LAUNCHER_ID,
|
|
LAUNCHER_PUZZLE_HASH,
|
|
1,
|
|
[0xDEADBEEF, 0xCAFEF00D, 411],
|
|
411,
|
|
[[51, 0xCAFEF00D, 203], [51, 0xFADEDDAB, 203]],
|
|
]
|
|
)
|
|
try:
|
|
cost, result = SINGLETON_MOD.run_with_cost(INFINITE_COST, solution)
|
|
except Exception as e:
|
|
assert e.args == ("clvm raise",)
|
|
else:
|
|
assert False
|
|
solution = Program.to(
|
|
[
|
|
did_core_hash,
|
|
LAUNCHER_ID,
|
|
LAUNCHER_PUZZLE_HASH,
|
|
1,
|
|
[0xDEADBEEF, 0xCAFEF00D, 411],
|
|
411,
|
|
[[51, 0xCAFEF00D, 203], [51, 0xFADEDDAB, 202], [51, 0xFADEDDAB, 4]],
|
|
]
|
|
)
|
|
try:
|
|
cost, result = SINGLETON_MOD.run_with_cost(INFINITE_COST, solution)
|
|
except Exception:
|
|
assert False
|
|
|
|
|
|
def test_p2_singleton():
|
|
# create a singleton. This should call driver code.
|
|
launcher_id = LAUNCHER_ID
|
|
innerpuz = Program.to(1)
|
|
singleton_full_puzzle = singleton_puzzle(launcher_id, LAUNCHER_PUZZLE_HASH, innerpuz)
|
|
|
|
# create a fake coin id for the `p2_singleton`
|
|
p2_singleton_coin_id = Program.to(["test_hash"]).get_tree_hash()
|
|
expected_announcement = Announcement(singleton_full_puzzle.get_tree_hash(), p2_singleton_coin_id).name()
|
|
|
|
# create a `p2_singleton` puzzle. This should call driver code.
|
|
p2_singleton_full = p2_singleton_puzzle(launcher_id, LAUNCHER_PUZZLE_HASH)
|
|
solution = Program.to([innerpuz.get_tree_hash(), p2_singleton_coin_id])
|
|
cost, result = p2_singleton_full.run_with_cost(INFINITE_COST, solution)
|
|
err, conditions = parse_sexp_to_conditions(result)
|
|
assert err is None
|
|
|
|
p2_singleton_full = p2_singleton_puzzle(launcher_id, LAUNCHER_PUZZLE_HASH)
|
|
solution = Program.to([innerpuz.get_tree_hash(), p2_singleton_coin_id])
|
|
cost, result = p2_singleton_full.run_with_cost(INFINITE_COST, solution)
|
|
assert result.first().rest().first().as_atom() == expected_announcement
|
|
assert conditions[0].vars[0] == expected_announcement
|
|
|
|
|
|
def test_pool_puzzles():
|
|
# create a singleton with id `launcher_id`
|
|
launcher_parent_id = Program.to(b"launcher-parent").get_tree_hash()
|
|
launcher_coin = Coin(launcher_parent_id, LAUNCHER_PUZZLE.get_tree_hash(), 200)
|
|
launcher_id = launcher_coin.name()
|
|
|
|
# create a `p2_singleton` that's provably a block reward
|
|
genesis_challenge = bytes.fromhex("ccd5bb71183532bff220ba46c268991a3ff07eb358e8255a65c30a2dce0e5fbb")
|
|
block_height = 101 # 0x65
|
|
pool_reward_parent_id = bytes32(genesis_challenge[:16] + block_height.to_bytes(16, "big"))
|
|
|
|
p2_singleton_full_puzhash = p2_singleton_puzzle_hash(launcher_id, LAUNCHER_PUZZLE_HASH)
|
|
p2_singleton_coin_amount = 2000000000
|
|
p2_singleton_coin_id = Coin(pool_reward_parent_id, p2_singleton_full_puzhash, p2_singleton_coin_amount).name()
|
|
|
|
# here are some pool parameters
|
|
pool_puzzle_hash = 0xD34DB33F
|
|
relative_lock_height = 600
|
|
owner_pubkey = 0xFADEDDAB
|
|
|
|
# Curry params are POOL_PUZHASH, RELATIVE_LOCK_HEIGHT, OWNER_PUBKEY, P2_SINGLETON_PUZHASH
|
|
escape_innerpuz = POOL_ESCAPING_MOD.curry(
|
|
pool_puzzle_hash, p2_singleton_full_puzhash, owner_pubkey, POOL_REWARD_PREFIX_MAINNET, relative_lock_height
|
|
)
|
|
# Curry params are POOL_PUZHASH, RELATIVE_LOCK_HEIGHT, ESCAPE_MODE_PUZHASH, P2_SINGLETON_PUZHASH, PUBKEY
|
|
escape_innerpuz_hash = escape_innerpuz.get_tree_hash()
|
|
committed_innerpuz = POOL_MEMBER_MOD.curry(
|
|
pool_puzzle_hash, p2_singleton_full_puzhash, owner_pubkey, POOL_REWARD_PREFIX_MAINNET, escape_innerpuz_hash
|
|
)
|
|
|
|
# the singleton is committed to the pool
|
|
singleton_full = singleton_puzzle(launcher_id, LAUNCHER_PUZZLE_HASH, committed_innerpuz)
|
|
singleton_amount = 3
|
|
singleton_coin = Coin(launcher_id, singleton_full.get_tree_hash(), singleton_amount)
|
|
|
|
# innersol = spend_type pool_reward_amount pool_reward_height extra_data
|
|
inner_sol = Program.to([0, p2_singleton_coin_amount, block_height, "bonus data"])
|
|
# full_sol = parent_info, my_amount, inner_solution
|
|
full_sol = Program.to([[launcher_coin.parent_coin_info, launcher_coin.amount], singleton_amount, inner_sol])
|
|
cost, result = singleton_full.run_with_cost(INFINITE_COST, full_sol)
|
|
|
|
conditions = result.as_python()
|
|
assert bytes32(result.first().rest().first().as_atom()) == singleton_coin.name()
|
|
assert (
|
|
bytes32(result.rest().rest().rest().rest().first().rest().first().as_atom())
|
|
== Announcement(p2_singleton_coin_id, bytes.fromhex("80")).name()
|
|
)
|
|
assert conditions[-1][1] == Announcement(p2_singleton_coin_id, bytes.fromhex("80")).name()
|
|
assert bytes32(conditions[1][1]) == singleton_full.get_tree_hash()
|
|
|
|
# new_result = '((70 0xe5a82aba773956ba319c74bae988ef5690d23d515d305640e963274e35ed6d44) (51 0x22e106ec75eaa42c63fa92fd36f68be0617d1200474df06cf88cb83d31b42938 3) (51 0x00d34db33f 0x77359400) (62 0xe6953e9190bdc44f47e95fbcbd56c3e444960097b9764e2b396141dff194e77c) (61 0x548607847d230749d38064b7ff43d390cafea3c51ac4cf15d86114dcd957c264))' # noqa
|
|
# result = '((70 0x90b2708399fadb0f35aaf0d7a0973045214180ce45d6968a5cdaba749dbf0ca6) (61 0x93e1b4675d94b6edf31bcfdca9bb2ce3c3da28715fe628b577c48a787dc3e440) (62 0x881b7255ced9576bd8782b6efec2ec9ce46b8b08500125092a69645c754d09cb) (51 0x00d34db33f 0x77359400) (51 0xa4878510fee591565037bf60c38e58445d1d451a7374d8d7f44db2c8a3107806 3))' # noqa
|