Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CHIA-1719] Add coin announcement restriction #18853

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 64 additions & 1 deletion chia/_tests/clvm/test_restrictions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
from chia.types.mempool_inclusion_status import MempoolInclusionStatus
from chia.util.errors import Err
from chia.util.ints import uint64
from chia.wallet.conditions import CreateCoinAnnouncement
from chia.wallet.puzzles.custody.custody_architecture import DelegatedPuzzleAndSolution, PuzzleWithRestrictions
from chia.wallet.puzzles.custody.restriction_puzzles.restrictions import Timelock
from chia.wallet.puzzles.custody.restriction_puzzles.restrictions import ForceCoinAnnouncement, Timelock
from chia.wallet.puzzles.custody.restriction_utilities import ValidatorStackRestriction
from chia.wallet.wallet_spend_bundle import WalletSpendBundle

Expand Down Expand Up @@ -136,3 +137,65 @@ async def test_timelock_wrapper(cost_logger: CostLogger) -> None:

# memo format assertion for coverage sake
assert restriction.memo(0) == Program.to([None])


@pytest.mark.anyio
async def test_force_coin_announcement_wrapper(cost_logger: CostLogger) -> None:
async with sim_and_client() as (sim, client):
restriction = ValidatorStackRestriction([ForceCoinAnnouncement()])
pwr = PuzzleWithRestrictions(0, [restriction], ACSMember())

# Farm and find coin
await sim.farm_block(pwr.puzzle_hash())
coin = (await client.get_coin_records_by_puzzle_hashes([pwr.puzzle_hash()], include_spent_coins=False))[0].coin

# Attempt to just use any old dpuz
any_old_dpuz = DelegatedPuzzleAndSolution(puzzle=Program.to((1, [[1, "foo"]])), solution=Program.to(None))
wrapped_dpuz = restriction.modify_delegated_puzzle_and_solution(any_old_dpuz, [Program.to(None)])
not_timelocked_attempt = WalletSpendBundle(
[
make_spend(
coin,
pwr.puzzle_reveal(),
pwr.solve(
[], [Program.to([any_old_dpuz.puzzle.get_tree_hash()])], Program.to([[1, "bar"]]), any_old_dpuz
),
)
],
G2Element(),
)
result = await client.push_tx(not_timelocked_attempt)
assert result == (MempoolInclusionStatus.FAILED, Err.GENERATOR_RUNTIME_ERROR)

# Now actually put a coin announcement in the dpuz
announcement = CreateCoinAnnouncement(bytes32.zeros, coin_id=coin.name())
announcement_dpuz = DelegatedPuzzleAndSolution(
puzzle=Program.to(
(1, [announcement.to_program(), announcement.corresponding_assertion().to_program(), [1, "foo"]])
),
solution=Program.to(None),
)
wrapped_dpuz = restriction.modify_delegated_puzzle_and_solution(announcement_dpuz, [Program.to(None)])
sb = cost_logger.add_cost(
"Minimal puzzle with restrictions w/ coin announcement forcing wrapper",
WalletSpendBundle(
[
make_spend(
coin,
pwr.puzzle_reveal(),
pwr.solve(
[],
[Program.to([announcement_dpuz.puzzle.get_tree_hash()])],
Program.to([[1, "bar"]]),
wrapped_dpuz,
),
)
],
G2Element(),
),
)
result = await client.push_tx(sb)
assert result == (MempoolInclusionStatus.SUCCESS, None)

# memo format assertion for coverage sake
assert restriction.memo(0) == Program.to([None])
21 changes: 21 additions & 0 deletions chia/wallet/puzzles/custody/restriction_puzzles/restrictions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
TIMELOCK_WRAPPER = load_clvm_maybe_recompile(
"timelock.clsp", package_or_requirement="chia.wallet.puzzles.custody.restriction_puzzles.wrappers"
)
FORCE_COIN_ANNOUNCEMENT_WRAPPER = load_clvm_maybe_recompile(
"force_assert_coin_announcement.clsp",
package_or_requirement="chia.wallet.puzzles.custody.restriction_puzzles.wrappers",
)
FORCE_COIN_ANNOUNCEMENT_WRAPPER_HASH = FORCE_COIN_ANNOUNCEMENT_WRAPPER.get_tree_hash()


@dataclass(frozen=True)
Expand All @@ -28,3 +33,19 @@ def puzzle(self, nonce: int) -> Program:

def puzzle_hash(self, nonce: int) -> bytes32:
return self.puzzle(nonce).get_tree_hash()


@dataclass(frozen=True)
class ForceCoinAnnouncement:
@property
def member_not_dpuz(self) -> bool:
return False

def memo(self, nonce: int) -> Program:
return Program.to(None)

def puzzle(self, nonce: int) -> Program:
return FORCE_COIN_ANNOUNCEMENT_WRAPPER

def puzzle_hash(self, nonce: int) -> bytes32:
return FORCE_COIN_ANNOUNCEMENT_WRAPPER_HASH
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
; This is a restriction on delegated puzzles intended to force a coin announcement
;
; The idea behind enforcing this is that it makes the current coin spend non-replayable since it will be implictly
; asserting that a specific coin ID is spent in tandem.
(mod
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a comment to the top of the file explaining what this file does?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

(
Conditions
)

(include condition_codes.clib)

(defun check_conditions (conditions)
; If we run out of conditions without finding an assertion, this will raise with "path into atom"
(if (= (f (f conditions)) ASSERT_COIN_ANNOUNCEMENT)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that this is failing if (f (f conditions)) is called when conditions is empty, but might be worth adding a comment explaining this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

conditions
(c (f conditions) (check_conditions (r conditions)))
)
)

(check_conditions Conditions)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ff02ffff01ff02ff06ffff04ff02ffff04ff05ff80808080ffff04ffff01ff3dff02ffff03ffff09ff11ff0480ffff0105ffff01ff04ff09ffff02ff06ffff04ff02ffff04ff0dff808080808080ff0180ff018080
1 change: 1 addition & 0 deletions chia/wallet/puzzles/deployed_puzzle_hashes.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"everything_with_signature": "1720d13250a7c16988eaf530331cefa9dd57a76b2c82236bec8bbbff91499b89",
"exigent_metadata_layer": "d5fd32e069fda83e230ccd8f6a7c4f652231aed5c755514b3d996cbeff4182b8",
"flag_proofs_checker": "fe2e3c631562fbb9be095297f762bf573705a0197164e9361ad5d50e045ba241",
"force_assert_coin_announcement": "ca0daca027e5ebd4a61fad7e32cfe1e984ad5b561c2fc08dea30accf3a191fab",
"genesis_by_coin_id": "493afb89eed93ab86741b2aa61b8f5de495d33ff9b781dfc8919e602b2afa150",
"genesis_by_coin_id_or_singleton": "40170305e3a71c3e7523f37fbcfc3188f9f949da0818a6331f28251e76e8c56f",
"genesis_by_puzzle_hash": "de5a6e06d41518be97ff6365694f4f89475dda773dede267caa33da63b434e36",
Expand Down
Loading