Skip to content

Commit

Permalink
Fix Atomical value recognition in payments (#203) (#205)
Browse files Browse the repository at this point in the history
  • Loading branch information
wizz-wallet-dev authored Jun 16, 2024
2 parents 42cf424 + 552b504 commit 1f4b859
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 31 deletions.
34 changes: 7 additions & 27 deletions electrumx/lib/atomicals_blueprint_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,6 @@ def build_reverse_output_to_atomical_id_exponent_map(atomical_id_to_output_index
return reverse_mapped


def get_nominal_atomical_value(value):
return value


def calculate_outputs_to_color_for_ft_atomical_ids(
tx, ft_atomicals, sort_by_fifo, is_custom_coloring_activated
) -> Optional[FtColoringSummary]:
Expand Down Expand Up @@ -789,12 +785,6 @@ def build_atomical_input_summaries(
# For each input atomical spent at the current input...
for atomicals_entry in atomicals_entry_list:
atomical_id = atomicals_entry["atomical_id"]
# value, = unpack_le_uint64(
# atomicals_entry['data'][HASHX_LEN + SCRIPTHASH_LEN : HASHX_LEN + SCRIPTHASH_LEN + 8]
# )
# exponent, = unpack_le_uint16_from(
# atomicals_entry['data'][HASHX_LEN + SCRIPTHASH_LEN + 8: HASHX_LEN + SCRIPTHASH_LEN + 8 + 8]
# )
sat_value = atomicals_entry["data_value"]["sat_value"]
atomical_value = atomicals_entry["data_value"]["atomical_value"]
# Perform a cache lookup for the mint information since we do not want to query multiple times
Expand Down Expand Up @@ -962,19 +952,6 @@ def are_payments_satisfied(self, expected_payment_outputs):
# Map the output script hex only
expected_output_keys_satisfied[output_script_key] = False

# Prepare the mapping of which ARC20 is paid at which output
ft_coloring_summary = calculate_outputs_to_color_for_ft_atomical_ids(
self.tx,
self.ft_atomicals,
self.sort_fifo,
self.is_custom_coloring_activated,
)
output_idx_to_atomical_id_map = {}
if ft_coloring_summary:
output_idx_to_atomical_id_map = build_reverse_output_to_atomical_id_exponent_map(
ft_coloring_summary.atomical_id_to_expected_outs_map
)

# For each of the outputs, assess whether it matches any of the required payment output expectations
for idx, txout in enumerate(self.tx.outputs):
output_script_hex = txout.pk_script.hex()
Expand Down Expand Up @@ -1004,12 +981,15 @@ def are_payments_satisfied(self, expected_payment_outputs):
expected_output_payment_id_type
)
# Check in the reverse map if the current output idx is colored with the expected color
output_summary = output_idx_to_atomical_id_map.get(idx)
if output_summary and output_summary.get(expected_output_payment_id_type_long_form, None) is not None:
output_summary = (
self.ft_output_blueprint.outputs.get(idx, {})
.get("atomicals", {})
.get(expected_output_payment_id_type_long_form, None)
)
if output_summary:
# Ensure the normalized atomical_value is greater than
# or equal to the expected payment amount in that token type.
# exponent_for_for_atomical_id = output_summary.get(expected_output_payment_id_type_long_form)
atomical_value = get_nominal_atomical_value(txout.value)
atomical_value = output_summary.atomical_value
if atomical_value >= expected_output_payment_value:
# Mark that the output was matched at least once
key = output_script_hex + expected_output_payment_id_type_long_form.hex()
Expand Down
5 changes: 4 additions & 1 deletion electrumx/server/block_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3897,7 +3897,10 @@ def create_or_delete_subname_payment_output_if_valid(
f"create_or_delete_subname_payment_output_if_valid: valid pattern failed. DeveloperError request_subname={request_subname}, regex={regex}"
)

if not blueprint_builder.are_payments_satisfied(matched_price_point["matched_rule"].get("o")):
if not blueprint_builder.are_payments_satisfied(
matched_price_point["matched_rule"].get("o"),
atomicals_spent_at_inputs,
):
self.logger.warning(
f"create_or_delete_subname_payment_output_if_valid: payments not satisfied. request_subname={request_subname}, regex={regex} atomical_id_for_payment={location_id_bytes_to_compact(atomical_id_for_payment)}"
)
Expand Down
86 changes: 83 additions & 3 deletions tests/lib/test_atomicals_blueprint_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,9 +599,6 @@ def mock_mint_fetcher(self, atomical_id):
nft_output_blueprint = blueprint_builder.get_nft_output_blueprint()
assert len(nft_output_blueprint.outputs) == 0
ft_output_blueprint = blueprint_builder.get_ft_output_blueprint()
for k, v in ft_output_blueprint.outputs.items():
for i, ii in v["atomicals"].items():
print(ii.__dict__)
assert ft_output_blueprint.cleanly_assigned == True
assert blueprint_builder.get_are_fts_burned() == False

Expand Down Expand Up @@ -1693,3 +1690,86 @@ def mock_mint_fetcher(self, atomical_id):
assert len(ft_output_blueprint.outputs) == 0
assert ft_output_blueprint.fts_burned == {}
assert blueprint_builder.get_are_fts_burned() == False


def test_partially_colored_spends_are_payments_satisfied_checks():
raw_tx_str = "02000000000101647760b13086a2f2e77395e474305237afa65ec638dda01132c8c48c8b891fd00000000000ffffffff03a8610000000000002251208a586070907d75b89f1b7bcbe8dd5c623e0143e9b62d5d6759da06a59b749679a861000000000000225120ed2ec645d1749c9b2dba88b1346899c60c82f7a57e6359964393a2bba31450f200000000000000002d6a0461746f6d017024921bd27146f57d42565b373214ae7f6d05fa85c3f73eeb5dd876c4c81be58888000000000140d94db131ec889cb33fc258bc3bb5ace3656597cde88cf51494ae864f171915d262a50af24e3699560116450c4244a99b7d84602b8be1fe4c640250d2202330c800000000"
raw_tx = bytes.fromhex(raw_tx_str)
subject_atomical_id = (
b"A\x03\x8f'\xe7\x85`l\xa0\xcc\x1e\xfd\x8e:\xa9\x12\xa1\\r\xd0o5\x9a\xeb\x05$=\xab+p\xa8V\x01\x00\x00\x00"
)

tx, tx_hash = coin.DESERIALIZER(raw_tx, 0).read_tx_and_hash()
atomicals_spent_at_inputs = {
1: [
{
"atomical_id": subject_atomical_id,
"location_id": b"not_used",
"data": b"not_used",
"data_value": {"sat_value": 50000, "atomical_value": 2},
},
],
}

def mock_mint_fetcher(self, atomical_id):
return {"atomical_id": atomical_id, "type": "FT"}

operations_at_inputs = {}
blueprint_builder = AtomicalsTransferBlueprintBuilder(
MockLogger(),
atomicals_spent_at_inputs,
operations_at_inputs,
tx_hash,
tx,
mock_mint_fetcher,
True,
True,
)
nft_output_blueprint = blueprint_builder.get_nft_output_blueprint()
assert len(nft_output_blueprint.outputs) == 0
ft_output_blueprint = blueprint_builder.get_ft_output_blueprint()
assert len(ft_output_blueprint.outputs) == 1
assert ft_output_blueprint.cleanly_assigned == False
assert blueprint_builder.get_are_fts_burned() == False

subject_atomical_id_compact = location_id_bytes_to_compact(subject_atomical_id)
# Empty rules
rules = {}
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert not payment_valid
# Valid payment to one output
rules = {
"51208a586070907d75b89f1b7bcbe8dd5c623e0143e9b62d5d6759da06a59b749679": {
"id": subject_atomical_id_compact,
"v": 2,
}
}
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert payment_valid
# Invalid payment insufficient amount
rules = {
"51208a586070907d75b89f1b7bcbe8dd5c623e0143e9b62d5d6759da06a59b749679": {
"id": subject_atomical_id_compact,
"v": 3,
}
}
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert not payment_valid
# Valid payment higher amount
rules = {
"51208a586070907d75b89f1b7bcbe8dd5c623e0143e9b62d5d6759da06a59b749679": {
"id": subject_atomical_id_compact,
"v": 1,
}
}
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert payment_valid
# Invalid payment to wrong address
rules = {
"51208a586070907d75b89f1b7bcbe8dd5c623e0143e9b62d5d6759da06a59b749678": {
"id": subject_atomical_id_compact,
"v": 2,
}
}
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert not payment_valid

0 comments on commit 1f4b859

Please sign in to comment.