|
24 | 24 | UnknownRestriction, |
25 | 25 | ) |
26 | 26 | 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 |
28 | 28 | from chia.wallet.puzzles.load_clvm import load_clvm_maybe_recompile |
29 | 29 | from chia.wallet.wallet_spend_bundle import WalletSpendBundle |
30 | 30 |
|
@@ -159,6 +159,77 @@ async def test_timelock_wrapper(cost_logger: CostLogger) -> None: |
159 | 159 | assert restriction.memo(0) == Program.to([None]) |
160 | 160 |
|
161 | 161 |
|
| 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 | + |
162 | 233 | @dataclass(frozen=True) |
163 | 234 | class SelfDestructRestriction: |
164 | 235 | @property |
|
0 commit comments