Skip to content

Commit

Permalink
fix payment and testcase
Browse files Browse the repository at this point in the history
  • Loading branch information
shadowv0vshadow committed Jun 15, 2024
1 parent 7b8f33d commit 5dd0162
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 59 deletions.
54 changes: 10 additions & 44 deletions electrumx/lib/atomicals_blueprint_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def build_reverse_output_to_atomical_id_exponent_map(atomical_id_to_output_index

def calculate_outputs_to_color_for_ft_atomical_ids(
tx, ft_atomicals, sort_by_fifo, is_custom_coloring_activated
) -> Optional[FtColoringSummary]:
) -> FtColoringSummary | None:
num_fts = len(ft_atomicals.keys())
if num_fts == 0:
return None
Expand Down Expand Up @@ -785,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 @@ -924,7 +918,7 @@ def get_atomical_id_for_payment_marker_if_found(cls, tx):

return found_atomical_id, None, None

def are_payments_satisfied(self, expected_payment_outputs, atomicals_spent_at_inputs):
def are_payments_satisfied(self, expected_payment_outputs):
if not isinstance(expected_payment_outputs, dict) or len(expected_payment_outputs.keys()) < 1:
return False

Expand Down Expand Up @@ -958,35 +952,6 @@ def are_payments_satisfied(self, expected_payment_outputs, atomicals_spent_at_in
# 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
)

atomicals_inputs_values = {}
for atomicals_input in atomicals_spent_at_inputs.values():
for atomical_entry in atomicals_input:
atomical_id = atomical_entry["atomical_id"]
sat_value = atomical_entry["data_value"]["sat_value"]
atomical_value = atomical_entry["data_value"]["atomical_value"]
if atomical_id not in atomicals_inputs_values:
atomicals_inputs_values[atomical_id] = {
"sat_value": 0,
"atomical_value": 0,
}
atomicals_inputs_values[atomical_id] = {
"sat_value": atomicals_inputs_values[atomical_id]["sat_value"] + sat_value,
"atomical_value": atomicals_inputs_values[atomical_id]["atomical_value"] + atomical_value,
}

# 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 @@ -1016,15 +981,16 @@ def are_payments_satisfied(self, expected_payment_outputs, atomicals_spent_at_in
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 = atomicals_inputs_values.get(expected_output_payment_id_type_long_form, {}).get(
"atomical_value", 0
)
if atomical_value >= (expected_output_payment_value or 0):
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()
expected_output_keys_satisfied[key] = True
Expand Down
110 changes: 95 additions & 15 deletions tests/lib/test_atomicals_blueprint_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,30 +537,30 @@ def mock_mint_fetcher(self, atomical_id):

# Empty rules
rules = {}
payment_valid = blueprint_builder.are_payments_satisfied(rules, atomicals_spent_at_inputs)
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert not payment_valid
# Valid payment to 2 outputs
rules = {
"51208a586070907d75b89f1b7bcbe8dd5c623e0143e9b62d5d6759da06a59b749679": {"v": 25000},
"5120ed2ec645d1749c9b2dba88b1346899c60c82f7a57e6359964393a2bba31450f2": {"v": 25000},
}
payment_valid = blueprint_builder.are_payments_satisfied(rules, atomicals_spent_at_inputs)
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert payment_valid
# Valid payment to one output
rules = {"51208a586070907d75b89f1b7bcbe8dd5c623e0143e9b62d5d6759da06a59b749679": {"v": 25000}}
payment_valid = blueprint_builder.are_payments_satisfied(rules, atomicals_spent_at_inputs)
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert payment_valid
# Invalid payment insufficient amount
rules = {"51208a586070907d75b89f1b7bcbe8dd5c623e0143e9b62d5d6759da06a59b749679": {"v": 25001}}
payment_valid = blueprint_builder.are_payments_satisfied(rules, atomicals_spent_at_inputs)
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert not payment_valid
# Valid payment higher amount
rules = {"51208a586070907d75b89f1b7bcbe8dd5c623e0143e9b62d5d6759da06a59b749679": {"v": 24999}}
payment_valid = blueprint_builder.are_payments_satisfied(rules, atomicals_spent_at_inputs)
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert payment_valid
# Invalid payment to wrong address
rules = {"51208a586070907d75b89f1b7bcbe8dd5c623e0143e9b62d5d6759da06a59b749678": {"v": 25000}}
payment_valid = blueprint_builder.are_payments_satisfied(rules, atomicals_spent_at_inputs)
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert not payment_valid


Expand Down 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 All @@ -612,7 +609,7 @@ def mock_mint_fetcher(self, atomical_id):
"v": 25000,
}
}
payment_valid = blueprint_builder.are_payments_satisfied(rules, atomicals_spent_at_inputs)
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert not payment_valid

# Valid with a valid atomical id ft token
Expand All @@ -624,7 +621,7 @@ def mock_mint_fetcher(self, atomical_id):
}
}
#
payment_valid = blueprint_builder.are_payments_satisfied(rules, atomicals_spent_at_inputs)
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert payment_valid

# Invalid due to insufficient units
Expand All @@ -635,8 +632,8 @@ def mock_mint_fetcher(self, atomical_id):
"v": 25001,
}
}
payment_valid = blueprint_builder.are_payments_satisfied(rules, atomicals_spent_at_inputs)
assert payment_valid
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert not payment_valid
# Valid with a valid atomical id ft token higher than needed
subject_atomical_id_compact = location_id_bytes_to_compact(subject_atomical_id)
rules = {
Expand All @@ -646,7 +643,7 @@ def mock_mint_fetcher(self, atomical_id):
}
}
#
payment_valid = blueprint_builder.are_payments_satisfied(rules, atomicals_spent_at_inputs)
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert payment_valid

# Valid with a valid atomical id ft token higher than needed
Expand All @@ -657,7 +654,7 @@ def mock_mint_fetcher(self, atomical_id):
"v": 0,
}
}
payment_valid = blueprint_builder.are_payments_satisfied(rules, atomicals_spent_at_inputs)
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert payment_valid


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 5dd0162

Please sign in to comment.