Skip to content

Commit 5f3e3df

Browse files
committed
Add a restriction that forces create coins to a specific destination
1 parent 6031c60 commit 5f3e3df

File tree

5 files changed

+119
-1
lines changed

5 files changed

+119
-1
lines changed

chia/_tests/clvm/test_restrictions.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
UnknownRestriction,
2525
)
2626
from chia.wallet.puzzles.custody.restriction_utilities import ValidatorStackRestriction
27-
from chia.wallet.puzzles.custody.restrictions import Force1of2wRestrictedVariable, Timelock
27+
from chia.wallet.puzzles.custody.restrictions import FixedCreateCoinDestinations, Force1of2wRestrictedVariable, Timelock
2828
from chia.wallet.puzzles.load_clvm import load_clvm_maybe_recompile
2929
from chia.wallet.wallet_spend_bundle import WalletSpendBundle
3030

@@ -159,6 +159,77 @@ async def test_timelock_wrapper(cost_logger: CostLogger) -> None:
159159
assert restriction.memo(0) == Program.to([None])
160160

161161

162+
@pytest.mark.anyio
163+
async def test_fixed_create_coin_wrapper(cost_logger: CostLogger) -> None:
164+
async with sim_and_client() as (sim, client):
165+
restriction = ValidatorStackRestriction(
166+
required_wrappers=[FixedCreateCoinDestinations(allowed_ph=bytes32.zeros)]
167+
)
168+
pwr = PuzzleWithRestrictions(nonce=0, restrictions=[restriction], puzzle=ACSMember())
169+
170+
# Farm and find coin
171+
await sim.farm_block(pwr.puzzle_hash())
172+
coin = (await client.get_coin_records_by_puzzle_hashes([pwr.puzzle_hash()], include_spent_coins=False))[0].coin
173+
174+
# Attempt to create a coin somewhere else
175+
any_old_dpuz = DelegatedPuzzleAndSolution(
176+
puzzle=Program.to((1, [CreateCoin(bytes32([1] * 32), uint64(1)).to_program()])), solution=Program.to(None)
177+
)
178+
wrapped_dpuz = restriction.modify_delegated_puzzle_and_solution(any_old_dpuz, [Program.to(None)])
179+
escape_attempt = WalletSpendBundle(
180+
[
181+
make_spend(
182+
coin,
183+
pwr.puzzle_reveal(),
184+
pwr.solve(
185+
[], [Program.to([any_old_dpuz.puzzle.get_tree_hash()])], Program.to([[1, "bar"]]), any_old_dpuz
186+
),
187+
)
188+
],
189+
G2Element(),
190+
)
191+
result = await client.push_tx(escape_attempt)
192+
assert result == (MempoolInclusionStatus.FAILED, Err.GENERATOR_RUNTIME_ERROR)
193+
194+
# Now send it to the correct place
195+
correct_dpuz = DelegatedPuzzleAndSolution(
196+
puzzle=Program.to(
197+
(1, [CreateCoin(bytes32.zeros, uint64(1)).to_program(), Remark(Program.to("foo")).to_program()])
198+
),
199+
solution=Program.to(None),
200+
)
201+
wrapped_dpuz = restriction.modify_delegated_puzzle_and_solution(correct_dpuz, [Program.to(None)])
202+
sb = cost_logger.add_cost(
203+
"Minimal puzzle with restrictions w/ fixed create coin wrapper",
204+
WalletSpendBundle(
205+
[
206+
make_spend(
207+
coin,
208+
pwr.puzzle_reveal(),
209+
pwr.solve(
210+
[],
211+
[Program.to([correct_dpuz.puzzle.get_tree_hash()])],
212+
Program.to([Remark(Program.to("bar")).to_program()]),
213+
wrapped_dpuz,
214+
),
215+
)
216+
],
217+
G2Element(),
218+
),
219+
)
220+
result = await client.push_tx(sb)
221+
assert result == (MempoolInclusionStatus.SUCCESS, None)
222+
223+
conditions = parse_conditions_non_consensus(
224+
run(sb.coin_spends[0].puzzle_reveal, sb.coin_spends[0].solution).as_iter()
225+
)
226+
assert Remark(Program.to("foo")) in conditions
227+
assert Remark(Program.to("bar")) in conditions
228+
229+
# memo format assertion for coverage sake
230+
assert restriction.memo(0) == Program.to([None])
231+
232+
162233
@dataclass(frozen=True)
163234
class SelfDestructRestriction:
164235
@property
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
(mod
2+
(
3+
ALLOWED_PH
4+
Conditions
5+
)
6+
7+
(defconstant CREATE_COIN 51)
8+
9+
(defun check_conditions (ALLOWED_PH conditions return)
10+
(if conditions
11+
(if (= (f (f conditions)) CREATE_COIN)
12+
(if (= (f (r (f conditions))) ALLOWED_PH)
13+
(check_conditions ALLOWED_PH (r conditions) return)
14+
(x)
15+
)
16+
(check_conditions ALLOWED_PH (r conditions) return)
17+
)
18+
return
19+
)
20+
)
21+
22+
(check_conditions ALLOWED_PH Conditions Conditions)
23+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ff02ffff01ff02ff06ffff04ff02ffff04ff05ffff04ff0bffff04ff0bff808080808080ffff04ffff01ff33ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff0480ffff01ff02ffff03ffff09ff53ff0580ffff01ff02ff06ffff04ff02ffff04ff05ffff04ff1bffff04ff17ff808080808080ffff01ff088080ff0180ffff01ff02ff06ffff04ff02ffff04ff05ffff04ff1bffff04ff17ff80808080808080ff0180ffff011780ff0180ff018080

chia/wallet/puzzles/custody/restrictions.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,14 @@
2121
RestrictionHint,
2222
UnknownRestriction,
2323
)
24+
from chia.wallet.puzzles.load_clvm import load_clvm_maybe_recompile
2425
from chia.wallet.util.merkle_tree import hash_an_atom
2526

2627
TIMELOCK_WRAPPER = Program.from_bytes(puzzle_mods.TIMELOCK)
2728
FORCE_1_OF_2_W_RESTRICTED_VARIABLE = Program.from_bytes(puzzle_mods.FORCE_1_OF_2_W_RESTRICTED_VARIABLE)
29+
FIXED_CREATE_COIN_DESTINATIONS = load_clvm_maybe_recompile(
30+
"fixed_create_coin_destinations.clsp", package_or_requirement="chia.wallet.puzzles.custody"
31+
)
2832

2933

3034
@dataclass(frozen=True)
@@ -45,6 +49,24 @@ def puzzle_hash(self, nonce: int) -> bytes32:
4549
return self.puzzle(nonce).get_tree_hash()
4650

4751

52+
@dataclass(kw_only=True, frozen=True)
53+
class FixedCreateCoinDestinations:
54+
allowed_ph: bytes32
55+
56+
@property
57+
def member_not_dpuz(self) -> bool:
58+
return False
59+
60+
def memo(self, nonce: int) -> Program:
61+
return Program.to(None)
62+
63+
def puzzle(self, nonce: int) -> Program:
64+
return FIXED_CREATE_COIN_DESTINATIONS.curry(self.allowed_ph)
65+
66+
def puzzle_hash(self, nonce: int) -> bytes32:
67+
return self.puzzle(nonce).get_tree_hash()
68+
69+
4870
@dataclass(kw_only=True, frozen=True)
4971
class Force1of2wRestrictedVariable:
5072
left_side_hash: bytes32

chia/wallet/puzzles/deployed_puzzle_hashes.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"conditions_banned": "e7e26a3fc3a8a35e3e9944e8107bf330e107e884afdb8c5003559b3882b6566b",
3+
"fixed_create_coin": "9b180c90094b82658705d838a954ad31abeeccf513fcb75b9924caa8d795fd29",
34
"self_destruct": "d0dc28ae70b9f9b5d49581a1914ae0844109234a712eda1381846fa358939ffb",
45
"test_generator_deserialize": "52add794fc76e89512e4a063c383418bda084c8a78c74055abe80179e4a7832c",
56
"test_multiple_generator_input_arguments": "156dafbddc3e1d3bfe1f2a84e48e5e46b287b8358bf65c3c091c93e855fbfc5b"

0 commit comments

Comments
 (0)