Skip to content

Follow-up PR for Compensation with Penalties#471

Draft
vzotova wants to merge 17 commits into
nucypher:infractionsfrom
vzotova:penalties-v2
Draft

Follow-up PR for Compensation with Penalties#471
vzotova wants to merge 17 commits into
nucypher:infractionsfrom
vzotova:penalties-v2

Conversation

@vzotova

@vzotova vzotova commented Apr 1, 2026

Copy link
Copy Markdown
Member

Type of PR:

  • Bugfix
  • Feature
  • Documentation
  • Other

Required reviews:

  • 1
  • 2
  • 3

What this does:

This PR extends PenaltyBoard with a fixed compensation model for stakers:

  • a constant amount per period in a configurable ERC20
  • withdrawable by the staking provider, owner, or beneficiary. Tokens are always sent to the beneficiary .
  • Compensation is disabled for stakeless stakers.
  • Penalties in the “last 4 periods” (configurable window) reduce or zero out compensation for affected periods via a simple factor (no penalties → full; any penalty in window → none for that period). This will be tuned in later PRs
  • Compensation logic lives in the same contract as penalty data.
  • Penalty storage is staker-centric: setPenalizedProvidersForPeriod appends the period index to each provider’s list; there is no on-chain period→providers mapping (subgraph can provide that view). This is a departure from the original PR Periods and PenaltyBoard contracts #456, where storage was period-centric.
  • The contract depends on TACoApplication for roles and beneficiary.
  • Accrual algorithm is designed to be efficient and allows for accumulated withdrawals.
    • Accrual is lazy (computed on getAccruedBalance and persisted on withdraw).
    • For a staker we take the accrual window from the last settled period (or period 0 if never withdrawn) to the current period.
    • We fetch penalized periods in the range [startPeriod - PENALTY_WINDOW_PERIODS, current].
    • If there are none, we add numPeriods × fixedCompensationPerPeriod.
    • Otherwise, for each period P in the window we check whether any penalty k in that range falls in [P - window, P]; if so, that period gets zero, else full.

Issues fixed/closed:

  • Fixes #...

Why it's needed:

Explain how this PR fits in the greater context of the NuCypher Network.
E.g., if this PR address a nucypher/productdev issue, let reviewers know!

Notes for reviewers:

What should reviewers focus on?
Is there a particular commit/function/section of your PR that requires more attention from reviewers?

@derekpierre derekpierre left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work. Still making my way through. Some comments in the meantime.

address _fundHolder,
uint256 _startPeriodForRewards
) Periods(genesisTime, periodDuration) {
require(admin != address(0), "Admin required");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also add some other require statements...?:

Suggested change
require(admin != address(0), "Admin required");
require(admin != address(0), "Admin required");
require(_fixedCompensationPerPeriod2Penalties <= _fixedCompensationPerPeriod, "Invalid 2 penalty compensation");
require(_fixedCompensationPerPeriod3Penalties <= _fixedCompensationPerPeriod2Penalties, "Invalid 3 penalty compensation");

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well it's not breaking any math there so not really necessary. I'll add it if you would like

Comment thread contracts/contracts/TACoApplication.sol
Comment thread deployment/constructor_params/mainnet/infraction.yml
infraction = deployer.deploy(project.InfractionCollector)
deployments = [infraction]
deployer.finalize(deployments=deployments)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to merge the registry (similar to what is done in the lynx script)?:

Suggested change
merge_registries(
registry_1_filepath=TAPIR_REGISTRY,
registry_2_filepath=deployer.registry_filepath,
output_filepath=TAPIR_REGISTRY,
)

CONSTRUCTOR_PARAMS_DIR, ARTIFACTS_DIR,
)
from deployment.params import Deployer

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
from deployment.registry import merge_registries

@pytest.fixture()
def penalty_board(project, creator, taco_application):
contract = project.PenaltyBoardForTACoApplicationMock.deploy(sender=creator)
taco_application.setPenaltyBoard(contract.address, sender=creator)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason you don't want to include the setting of the penalty board in the taco_application fixture itself? Then you wouldn't have to include the penalty_board fixture in tests where you aren't making assertions on the penalty board contract? For example, test_request_unstake, test_release, test_child_sync.

i.e. you can potentially do the following instead:
def taco_application(..., pentalty_board):
...
taco_application.setPenaltyBoard(contract.address, sender=creator)
def penalty_board(project, creator):
contract = project.PenaltyBoardForTACoApplicationMock.deploy(sender=creator)
return contract

penalty_board.addPenalizedProvidersForPeriod(provs, current, sender=informer)
assert penalty_board.getPenalizedPeriodsByStaker(other_account.address) == [current]

# Advance into period 1; then both 2 and 3 are valid.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it period 1 or 3 for the comment?

Suggested change
# Advance into period 1; then both 2 and 3 are valid.
# Advance into period 3; then both 2 and 3 are valid.

mock_taco_app.setRoles(
stakeless_provider.address,
owner.address,
beneficiary.address,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can a stakeless node have a beneficiary?

@cygnusv cygnusv changed the base branch from main to infractions April 29, 2026 17:11
@cygnusv cygnusv mentioned this pull request Apr 29, 2026
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants