Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions hathor/nanocontracts/runner/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ def call_public_method_with_nc_args(
try:
ret = self._unsafe_call_public_method(contract_id, method_name, ctx, nc_args)
finally:
self._reset_all_change_trackers()
self._reset_all_changes()
return ret

def _unsafe_call_public_method(
Expand Down Expand Up @@ -306,9 +306,6 @@ def _unsafe_call_public_method(
self._validate_balances(ctx)
self._commit_all_changes_to_storage()

# Reset the tokens counters so this Runner can be reused (in blueprint tests, for example).
self._updated_tokens_totals = defaultdict(int)
self._paid_actions_fees = defaultdict(int)
return ret

def _check_all_field_initialized(self, blueprint: Blueprint) -> None:
Expand Down Expand Up @@ -506,16 +503,21 @@ def _unsafe_call_another_contract_public_method(

return result

def _reset_all_change_trackers(self) -> None:
def _reset_all_changes(self) -> None:
"""Reset all changes and prepare for next call."""
assert self._call_info is not None
for change_trackers in self._call_info.change_trackers.values():
for change_tracker in change_trackers:
if not change_tracker.has_been_commited:
change_tracker.block()

self._last_call_info = self._call_info
self._call_info = None

# Reset the tokens counters so this Runner can be reused (in blueprint tests, for example).
self._updated_tokens_totals = defaultdict(int)
self._paid_actions_fees = defaultdict(int)

def _validate_balances(self, ctx: Context) -> None:
"""
Validate that all balances are non-negative and assert that
Expand Down Expand Up @@ -729,7 +731,7 @@ def call_view_method(self, contract_id: ContractId, method_name: str, *args: Any
kwargs=kwargs,
)
finally:
self._reset_all_change_trackers()
self._reset_all_changes()

def _handle_index_update(self, action: NCAction) -> None:
"""For each action in a public method call, create the appropriate index update records."""
Expand Down Expand Up @@ -940,7 +942,7 @@ def create_contract_with_nc_args(
try:
ret = self._unsafe_call_public_method(contract_id, NC_INITIALIZE_METHOD, ctx, nc_args)
finally:
self._reset_all_change_trackers()
self._reset_all_changes()
return ret

@_forbid_syscall_from_view('create_contract')
Expand Down
69 changes: 69 additions & 0 deletions hathor_tests/nanocontracts/test_reset_counters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Copyright 2026 Hathor Labs
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import pytest

from hathor import HATHOR_TOKEN_UID, Blueprint, Context, public
from hathor_tests.nanocontracts.blueprints.unittest import BlueprintTestCase
from hathorlib.nanocontracts.exception import NCFail
from hathorlib.nanocontracts.types import ContractId, NCDepositAction


class MyBlueprint(Blueprint):
@public(allow_deposit=True)
def initialize(self, ctx: Context) -> None:
pass

@public
def create_token(self, ctx: Context, fail: bool) -> None:
self.syscall.create_deposit_token(token_name='TokenA', token_symbol='TKA', amount=100)
if fail:
raise NCFail('fail!')

@public
def call_another_create_token(self, ctx: Context, contract_id: ContractId, fail: bool) -> None:
contract = self.syscall.get_contract(contract_id, blueprint_id=self.syscall.get_blueprint_id())
contract.public().create_token(fail=fail)

@public
def nop(self, ctx: Context) -> None:
pass


class TestResetCounters(BlueprintTestCase):
def setUp(self) -> None:
super().setUp()

self.blueprint_id = self._register_blueprint_class(MyBlueprint)
self.contract_id1 = self.gen_random_contract_id()
self.contract_id2 = self.gen_random_contract_id()

ctx = self.create_context(actions=[NCDepositAction(token_uid=HATHOR_TOKEN_UID, amount=100)])
self.runner.create_contract(self.contract_id1, self.blueprint_id, ctx)
self.runner.create_contract(self.contract_id2, self.blueprint_id, ctx)

def test_reset_counters_after_success(self) -> None:
ctx = self.create_context()
self.runner.call_public_method(
self.contract_id1, 'call_another_create_token', ctx, self.contract_id2, fail=False
)
self.runner.call_public_method(self.contract_id1, 'nop', ctx)

def test_reset_counters_after_failure(self) -> None:
ctx = self.create_context()
with pytest.raises(NCFail, match='fail!'):
self.runner.call_public_method(
self.contract_id1, 'call_another_create_token', ctx, self.contract_id2, fail=True
)
self.runner.call_public_method(self.contract_id1, 'nop', ctx)
Loading