def test_unknown_ops_last_bits(self): # The last byte is ignored for no-op unknown ops for suffix in [b"\x3f", b"\x0f", b"\x00", b"\x2c"]: # the cost is unchanged by the last byte self.assertEqual(default_unknown_op(b"\x3c" + suffix, SExp.null()), (61, SExp.null()))
def test_listp(self): self.assertEqual(SExp.to(42).listp(), False) self.assertEqual(SExp.to(b"").listp(), False) self.assertEqual(SExp.to(b"1337").listp(), False) self.assertEqual(SExp.to((1337, 42)).listp(), True) self.assertEqual(SExp.to([1337, 42]).listp(), True)
def test_long_list(self): d = [1337] * 1000 v = SExp.to(d) for i in range(1000 - 1): self.assertEqual(v.as_pair()[0].as_int(), d[i]) v = v.as_pair()[1] self.assertEqual(v.as_atom(), SExp.null())
def test_cons(self): # list self.assertEqual( SExp.to(H01).cons(SExp.to(H02).cons(SExp.null())).as_python(), [H01, H02], ) # cons-box of two values self.assertEqual(SExp.to(H01).cons(SExp.to(H02).as_python()), (H01, H02))
def parse_aggsig(args: SExp) -> List[bytes]: pubkey = args.first().atom args = args.rest() message = args.first().atom if len(pubkey) != 48: raise ValidationError(Err.INVALID_CONDITION) if len(message) > 1024: raise ValidationError(Err.INVALID_CONDITION) return [pubkey, message]
def test_invalid_tuple(self): with self.assertRaises(ValueError): s = SExp.to((dummy_class, dummy_class)) # conversions are deferred, this is where it will fail: b = list(s.as_iter()) print(b) with self.assertRaises(ValueError): s = SExp.to((dummy_class, dummy_class, dummy_class))
def test_unknown_op(self): self.assertRaises( EvalError, lambda: OPERATOR_LOOKUP(b'\xff\xff1337', SExp.to(1337))) od = OperatorDict(OPERATOR_LOOKUP, unknown_op_handler=lambda name, args: self. unknown_handler(name, args)) cost, ret = od(b'\xff\xff1337', SExp.to(1337)) self.assertTrue(self.handler_called) self.assertEqual(cost, 42) self.assertEqual(ret, SExp.to(b'foobar'))
def spend_bundle_to_serialized_coin_solution_entry_list( bundle: SpendBundle) -> bytes: r = b"" for coin_solution in bundle.coin_solutions: r += b"\xff" r += b"\xff" + SExp.to(coin_solution.coin.parent_coin_info).as_bin() r += b"\xff" + bytes(coin_solution.puzzle_reveal) r += b"\xff" + SExp.to(coin_solution.coin.amount).as_bin() r += b"\xff" + bytes(coin_solution.solution) r += b"\x80" r += b"\x80" return r
def compress_clvm_spend_bundle(sb): # print(sb) compressed_cses = [] for cse in sb.as_iter(): a = cse.first() b = cse.rest().first().first() c = cse.rest().first().rest() s = b.rest().rest().first().rest().first().rest() p = SExp.to([a, [bytes(s), c.first()]]) compressed_cses.append(p) # print(p) return SExp.to([compressed_cses])
def test_long_linked_list(self): d = b"" for i in range(1000): d = (b"2", d) v = SExp.to(d) for i in range(1000): self.assertEqual(v.as_pair()[0].as_atom(), d[0]) v = v.as_pair()[1] d = d[1] self.assertEqual(v.as_atom(), SExp.null()) self.assertEqual(d, b"")
def test_deep_recursion(self): d = b"2" for i in range(1000): d = [d] v = SExp.to(d) for i in range(1000): self.assertEqual(v.as_pair()[1].as_atom(), SExp.null()) v = v.as_pair()[0] d = d[0] self.assertEqual(v.as_atom(), b"2") self.assertEqual(d, b"2")
def parse_condition(cond: SExp, safe_mode: bool) -> Tuple[int, Optional[ConditionWithArgs]]: condition = cond.first().as_atom() if condition in CONDITION_OPCODES: opcode: ConditionOpcode = ConditionOpcode(condition) cost, args = parse_condition_args(cond.rest(), opcode, safe_mode) cvl = ConditionWithArgs(opcode, args) if args is not None else None elif not safe_mode: opcode = ConditionOpcode.UNKNOWN cvl = ConditionWithArgs(opcode, cond.rest().as_atom_list()) cost = 0 else: raise ValidationError(Err.INVALID_CONDITION) return cost, cvl
def parse_create_coin(args: SExp, safe_mode: bool) -> List[bytes]: puzzle_hash = args.first().atom args = args.rest() if len(puzzle_hash) != 32: raise ValidationError(Err.INVALID_CONDITION) amount_int = sanitize_int(args.first(), safe_mode) if amount_int >= 2**64: raise ValidationError(Err.COIN_AMOUNT_EXCEEDS_MAXIMUM) if amount_int < 0: raise ValidationError(Err.COIN_AMOUNT_NEGATIVE) # note that this may change the representation of amount. If the original # buffer had redundant leading zeroes, they will be stripped return [puzzle_hash, int_to_bytes(amount_int)]
def test_unknown_op_reserved(self): # any op that starts with ffff is reserved, and results in a hard # failure with self.assertRaises(EvalError): default_unknown_op(b"\xff\xff", SExp.null()) for suffix in [b"\xff", b"0", b"\x00", b"\xcc\xcc\xfe\xed\xfa\xce"]: with self.assertRaises(EvalError): default_unknown_op(b"\xff\xff" + suffix, SExp.null()) with self.assertRaises(EvalError): # an empty atom is not a valid opcode self.assertEqual(default_unknown_op(b"", SExp.null()), (1, SExp.null())) # a single ff is not sufficient to be treated as a reserved opcode self.assertEqual(default_unknown_op(b"\xff", SExp.null()), (CONCAT_BASE_COST, SExp.null())) # leading zeroes count, and this does not count as a ffff-prefix # the cost is 0xffff00 = 16776960 self.assertEqual( default_unknown_op(b"\x00\xff\xff\x00\x00", SExp.null()), (16776961, SExp.null()))
def _tree_hash(node: SExp, precalculated: Set[bytes32]) -> bytes32: """ Hash values in `precalculated` are presumed to have been hashed already. """ if node.listp(): left = _tree_hash(node.first(), precalculated) right = _tree_hash(node.rest(), precalculated) s = b"\2" + left + right else: atom = node.as_atom() if atom in precalculated: return bytes32(atom) s = b"\1" + atom return bytes32(std_hash(s))
def test_as_iter(self): val = list(SExp.to((1, (2, (3, (4, b""))))).as_iter()) self.assertEqual(val, [1, 2, 3, 4]) val = list(SExp.to(b"").as_iter()) self.assertEqual(val, []) val = list(SExp.to((1, b"")).as_iter()) self.assertEqual(val, [1]) # these fail because the lists are not null-terminated self.assertRaises(EvalError, lambda: list(SExp.to(1).as_iter())) self.assertRaises( EvalError, lambda: list(SExp.to((1, (2, (3, (4, 5))))).as_iter()) )
def parse_condition( cond: SExp, safe_mode: bool) -> Tuple[int, Optional[ConditionWithArgs]]: condition = cond.first().as_atom() if condition in CONDITION_OPCODES: opcode: ConditionOpcode = ConditionOpcode(condition) cost, args = parse_condition_args(cond.rest(), opcode, safe_mode) cvl = ConditionWithArgs(opcode, args) if args is not None else None elif not safe_mode: # we don't need to save unknown conditions. We can't do anything with them anyway # safe_mode just tells us whether we can tolerate them or not return 0, None else: raise ValidationError(Err.INVALID_CONDITION) return cost, cvl
def parse_aggsig(args: SExp) -> List[bytes]: pubkey = args.first().atom args = args.rest() message = args.first().atom if len(pubkey) != 48: raise ValidationError(Err.INVALID_CONDITION) if len(message) > 1024: raise ValidationError(Err.INVALID_CONDITION) # agg sig conditions only take 2 parameters args = args.rest() # the list is terminated by having a right-element that's not another pair, # just like as_atom_list() (see chia/types/blockchain_format/program.py) if args.pair is not None: raise ValidationError(Err.INVALID_CONDITION) return [pubkey, message]
def test_spend_byndle_coin_solution(self): for i in range(0, 10): sb: SpendBundle = make_spend_bundle(i) cs1 = SExp.to( spend_bundle_to_coin_solution_entry_list(sb)).as_bin() cs2 = spend_bundle_to_serialized_coin_solution_entry_list(sb) assert cs1 == cs2
def ir_offset(ir_sexp: SExp) -> int: the_offset = ir_sexp.first() if the_offset.listp(): the_offset = the_offset.rest().as_atom() else: the_offset = b"\xff" return casts.int_from_bytes(the_offset)
def test_g1element(self): b = fh( "b3b8ac537f4fd6bde9b26221d49b54b17a506be147347dae5" "d081c0a6572b611d8484e338f3432971a9823976c6a232b" ) v = SExp.to(G1Element(b)) self.assertEqual(v.atom, b)
def parse_fee(args: SExp, safe_mode: bool) -> List[bytes]: fee_int = sanitize_int(args.first(), safe_mode) if fee_int >= 2**64 or fee_int < 0: raise ValidationError(Err.RESERVE_FEE_CONDITION_FAILED) # note that this may change the representation of the fee. If the original # buffer had redundant leading zeroes, they will be stripped return [int_to_bytes(fee_int)]
def simple_solution_generator(bundle: SpendBundle) -> BlockGenerator: """ Simply quotes the solutions we know. """ cse_list = spend_bundle_to_coin_solution_entry_list(bundle) block_program = SerializedProgram.from_bytes(SExp.to((binutils.assemble("#q"), cse_list)).as_bin()) generator = BlockGenerator(block_program, []) return generator
def test_list_of_one(self): v = SExp.to([1]) self.assertEqual(type(v.pair[0]), CLVMObject) self.assertEqual(type(v.pair[1]), CLVMObject) self.assertEqual(type(v.as_pair()[0]), SExp) self.assertEqual(type(v.as_pair()[1]), SExp) self.assertEqual(v.pair[0].atom, b"\x01") self.assertEqual(v.pair[1].atom, b"")
def parse_amount(args: SExp, safe_mode: bool) -> List[bytes]: amount_int = sanitize_int(args.first(), safe_mode) if amount_int < 0: raise ValidationError(Err.ASSERT_MY_AMOUNT_FAILED) if amount_int >= 2**64: raise ValidationError(Err.ASSERT_MY_AMOUNT_FAILED) # note that this may change the representation of amount. If the original # buffer had redundant leading zeroes, they will be stripped return [int_to_bytes(amount_int)]
def parse_height(args: SExp, safe_mode: bool, error_code: Err) -> Optional[List[bytes]]: height_int = sanitize_int(args.first(), safe_mode) # this condition is inherently satisified, there is no need to keep it if height_int < 0: return None if height_int >= 2 ** 32: raise ValidationError(error_code) # note that this may change the representation of the height. If the original # buffer had redundant leading zeroes, they will be stripped return [int_to_bytes(height_int)]
def test_eq(self): val = SExp.to(1) self.assertTrue(val == 1) self.assertFalse(val == 2) # mismatching types self.assertFalse(val == [1]) self.assertFalse(val == [1, 2]) self.assertFalse(val == (1, 2)) self.assertFalse(val == (dummy_class, dummy_class))
def best_solution_program(bundle: SpendBundle) -> SerializedProgram: """ This could potentially do a lot of clever and complicated compression optimizations in conjunction with choosing the set of SpendBundles to include. For now, we just quote the solutions we know. """ r = [] for coin_solution in bundle.coin_solutions: entry = [coin_solution.coin.name(), coin_solution.solution] r.append(entry) return SerializedProgram.from_bytes(SExp.to((binutils.assemble("#q"), r)).as_bin())
def simple_solution_generator(bundle: SpendBundle) -> BlockGenerator: """ Simply quotes the solutions we know. """ cse_list = spend_bundle_to_serialized_coin_solution_entry_list(bundle) block_program = b"\xff" block_program += SExp.to(binutils.assemble("#q")).as_bin() block_program += b"\xff" + cse_list + b"\x80" return BlockGenerator(SerializedProgram.from_bytes(block_program), [])
def pre_eval_f(sexp, args): sexp, args = [SExp.to(_) for _ in [sexp, args]] if symbol_table: h = sha256tree(sexp).hex() if h not in symbol_table: return None log_entry = [sexp, args, None] log_entries.append(log_entry) def callback_f(r): log_entry[-1] = SExp.to(r) return callback_f