diff --git a/contracts/contracts/TACoApplication.sol b/contracts/contracts/TACoApplication.sol index 4959016b..cc25b04b 100644 --- a/contracts/contracts/TACoApplication.sol +++ b/contracts/contracts/TACoApplication.sol @@ -198,7 +198,7 @@ contract TACoApplication is uint256 public constant REWARD_PER_TOKEN_MULTIPLIER = 10 ** 3; uint256 internal constant FLOATING_POINT_DIVISOR = REWARD_PER_TOKEN_MULTIPLIER * 10 ** 18; - uint256 public constant PENALTY_BASE = 10000; + uint192 public constant PENALTY_BASE = 10000; uint96 public immutable minimumAuthorization; uint256 public immutable minOperatorSeconds; @@ -206,6 +206,7 @@ contract TACoApplication is uint256 public immutable deauthorizationDuration; uint192 public immutable penaltyDefault; uint256 public immutable penaltyDuration; + uint192 public immutable penaltyIncrement; uint64 public immutable commitmentDurationOption1; uint64 public immutable commitmentDurationOption2; @@ -244,6 +245,7 @@ contract TACoApplication is * @param _commitmentDeadline Last date to make a commitment * @param _penaltyDefault Default penalty percentage (as a value out of 10000) * @param _penaltyDuration Duration of penalty + * @param _penaltyIncrement Increment of penalty if violation occurs during an existing penalty period */ constructor( IERC20 _token, @@ -255,7 +257,8 @@ contract TACoApplication is uint64[] memory _commitmentDurationOptions, uint64 _commitmentDeadline, uint192 _penaltyDefault, - uint256 _penaltyDuration + uint256 _penaltyDuration, + uint192 _penaltyIncrement ) { uint256 totalSupply = _token.totalSupply(); require( @@ -265,8 +268,9 @@ contract TACoApplication is _commitmentDurationOptions.length >= 1 && _commitmentDurationOptions.length <= 4 && _penaltyDefault > 0 && - _penaltyDefault < PENALTY_BASE && - _penaltyDuration > 0, + _penaltyDefault <= PENALTY_BASE && + _penaltyDuration > 0 && + _penaltyDefault + _penaltyIncrement <= PENALTY_BASE, "Wrong input parameters" ); // This require is only to check potential overflow for 10% reward @@ -295,6 +299,7 @@ contract TACoApplication is commitmentDeadline = _commitmentDeadline; penaltyDefault = _penaltyDefault; penaltyDuration = _penaltyDuration; + penaltyIncrement = _penaltyIncrement; _disableInitializers(); } @@ -1073,7 +1078,12 @@ contract TACoApplication is StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider]; uint96 before = effectiveAuthorized(info.authorized, info.penaltyPercent); info.endPenalty = uint64(block.timestamp + penaltyDuration); - info.penaltyPercent = penaltyDefault; + info.penaltyPercent = info.penaltyPercent == 0 + ? penaltyDefault + : info.penaltyPercent + penaltyIncrement; + if (info.penaltyPercent > PENALTY_BASE) { + info.penaltyPercent = PENALTY_BASE; + } if (info.operatorConfirmed) { authorizedOverall -= before - effectiveAuthorized(info.authorized, info.penaltyPercent); } diff --git a/tests/application/conftest.py b/tests/application/conftest.py index 26c70f9e..8b5efc12 100644 --- a/tests/application/conftest.py +++ b/tests/application/conftest.py @@ -32,6 +32,7 @@ COMMITMENT_DEADLINE = 60 * 60 * 24 * 200 # 200 days after deploymwent PENALTY_DEFAULT = 1000 # 10% penalty +PENALTY_INCREMENT = 2500 # 25% penalty increment PENALTY_DURATION = 60 * 60 * 24 # 1 day in seconds @@ -83,6 +84,7 @@ def taco_application(project, creator, token, threshold_staking, oz_dependency, now + COMMITMENT_DEADLINE, PENALTY_DEFAULT, PENALTY_DURATION, + PENALTY_INCREMENT, ) encoded_initializer_function = encode_function_data() diff --git a/tests/application/test_operator.py b/tests/application/test_operator.py index d5b9d250..8f5fe2fb 100644 --- a/tests/application/test_operator.py +++ b/tests/application/test_operator.py @@ -24,6 +24,7 @@ MIN_OPERATOR_SECONDS = 24 * 60 * 60 PENALTY_DEFAULT = 1000 # 10% penalty PENALTY_DURATION = 60 * 60 * 24 # 1 day in seconds +PENALTY_INCREMENT = 2500 def test_bond_operator(accounts, threshold_staking, taco_application, child_application, chain): @@ -497,12 +498,35 @@ def test_penalize(accounts, threshold_staking, taco_application, child_applicati tx = child_application.penalize(staking_provider, sender=staking_provider) timestamp = tx.timestamp end_of_penalty = timestamp + PENALTY_DURATION - assert taco_application.getPenalty(staking_provider) == [PENALTY_DEFAULT, end_of_penalty] - assert taco_application.authorizedOverall() == min_authorization * 9 / 10 + assert taco_application.getPenalty(staking_provider) == [ + PENALTY_DEFAULT + PENALTY_INCREMENT, + end_of_penalty, + ] + assert taco_application.authorizedOverall() == min_authorization * 65 / 100 # 65% assert tx.events == [ taco_application.Penalized( stakingProvider=staking_provider, - penaltyPercent=PENALTY_DEFAULT, + penaltyPercent=PENALTY_DEFAULT + PENALTY_INCREMENT, + endPenalty=end_of_penalty, + ) + ] + + # Penalize several times in a row + chain.pending_timestamp += PENALTY_DURATION + child_application.penalize(staking_provider, sender=staking_provider) # 90% + child_application.penalize(staking_provider, sender=staking_provider) # 65% + child_application.penalize(staking_provider, sender=staking_provider) # 40% + child_application.penalize(staking_provider, sender=staking_provider) # 15% + tx = child_application.penalize(staking_provider, sender=staking_provider) # 0% + timestamp = tx.timestamp + end_of_penalty = timestamp + PENALTY_DURATION + penalty_base = taco_application.PENALTY_BASE() + assert taco_application.getPenalty(staking_provider) == [penalty_base, end_of_penalty] + assert taco_application.authorizedOverall() == 0 + assert tx.events == [ + taco_application.Penalized( + stakingProvider=staking_provider, + penaltyPercent=penalty_base, endPenalty=end_of_penalty, ) ]