Skip to content

Commit

Permalink
TACoApplication: draft of reward only penalty
Browse files Browse the repository at this point in the history
  • Loading branch information
vzotova committed Apr 12, 2024
1 parent f01d2c5 commit c47b4c6
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 11 deletions.
113 changes: 102 additions & 11 deletions contracts/contracts/TACoApplication.sol
Original file line number Diff line number Diff line change
Expand Up @@ -167,25 +167,45 @@ contract TACoApplication is
address operator
);

/**
* @notice Signals that the staking provider was penalized
* @param stakingProvider Staking provider address
* @param penaltyPercent Percent of reward that was penalized
* @param endPenalty End of penalty
*/
event Penalized(address indexed stakingProvider, uint256 penaltyPercent, uint256 endPenalty);

/**
* @notice Signals that reward was reset after penalty
* @param stakingProvider Staking provider address
*/
event RewardReset(address indexed stakingProvider);

struct StakingProviderInfo {
address operator;
bool operatorConfirmed;
uint64 operatorStartTimestamp;
uint96 authorized;
uint96 deauthorizing; // TODO real usage only in getActiveStakingProviders, maybe remove?
uint96 deauthorizing;
uint64 endDeauthorization;
uint96 tReward;
uint160 rewardPerTokenPaid;
uint64 endCommitment;
uint256 stub;
uint192 penaltyPercent;
uint64 endPenalty;
}

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;

uint96 public immutable minimumAuthorization;
uint256 public immutable minOperatorSeconds;
uint256 public immutable rewardDuration;
uint256 public immutable deauthorizationDuration;
uint192 public immutable penaltyDefault;
uint256 public immutable penaltyDuration;

uint64 public immutable commitmentDurationOption1;
uint64 public immutable commitmentDurationOption2;
Expand Down Expand Up @@ -222,6 +242,8 @@ contract TACoApplication is
* @param _deauthorizationDuration Duration of decreasing authorization in seconds
* @param _commitmentDurationOptions Options for commitment duration
* @param _commitmentDeadline Last date to make a commitment
* @param _penaltyDefault Default penalty percentage
* @param _penaltyDuration Duration of penalty
*/
constructor(
IERC20 _token,
Expand All @@ -231,15 +253,20 @@ contract TACoApplication is
uint256 _rewardDuration,
uint256 _deauthorizationDuration,
uint64[] memory _commitmentDurationOptions,
uint64 _commitmentDeadline
uint64 _commitmentDeadline,
uint192 _penaltyDefault,
uint256 _penaltyDuration
) {
uint256 totalSupply = _token.totalSupply();
require(
_rewardDuration != 0 &&
_tStaking.authorizedStake(address(this), address(this)) == 0 &&
totalSupply > 0 &&
_commitmentDurationOptions.length >= 1 &&
_commitmentDurationOptions.length <= 4,
_commitmentDurationOptions.length <= 4 &&
_penaltyDefault > 0 &&
_penaltyDefault < PENALTY_BASE &&
_penaltyDuration > 0,
"Wrong input parameters"
);
// This require is only to check potential overflow for 10% reward
Expand All @@ -266,6 +293,8 @@ contract TACoApplication is
? _commitmentDurationOptions[3]
: 0;
commitmentDeadline = _commitmentDeadline;
penaltyDefault = _penaltyDefault;
penaltyDuration = _penaltyDuration;
_disableInitializers();
}

Expand Down Expand Up @@ -420,12 +449,46 @@ contract TACoApplication is
if (!info.operatorConfirmed) {
return info.tReward;
}
uint256 result = (uint256(info.authorized) * (rewardPerToken() - info.rewardPerTokenPaid)) /
uint96 authorized = effectiveAuthorized(info.authorized, info);
uint256 result = (uint256(authorized) * (rewardPerToken() - info.rewardPerTokenPaid)) /
FLOATING_POINT_DIVISOR +
info.tReward;
return result.toUint96();
}

// FIXME
function effectiveAuthorized(
uint96 _authorized,
uint192 _penaltyPercent
) internal view returns (uint96) {
return uint96((_authorized * (PENALTY_BASE - _penaltyPercent)) / PENALTY_BASE);
}

function effectiveAuthorized(
uint96 _authorized,
StakingProviderInfo storage _info
) internal view returns (uint96) {
if (_info.endPenalty != 0 && _info.endPenalty > block.timestamp) {
return effectiveAuthorized(_authorized, _info.penaltyPercent);
} else {
return _info.authorized;
}
}

function effectiveDifference(
uint96 _from,
uint96 _to,
StakingProviderInfo storage _info
) internal view returns (uint96) {
if (_info.endPenalty != 0 && _info.endPenalty > block.timestamp) {
uint96 effectiveFrom = effectiveAuthorized(_from, _info.penaltyPercent);
uint96 effectiveTo = effectiveAuthorized(_to, _info.penaltyPercent);
return effectiveFrom - effectiveTo;
} else {
return _from - _to;
}
}

/**
* @notice Transfer reward for the next period. Can be called only by distributor
* @param _reward Amount of reward
Expand Down Expand Up @@ -477,7 +540,7 @@ contract TACoApplication is
uint96 _properAmount
) internal {
if (_info.authorized != _properAmount) {
authorizedOverall -= _info.authorized - _properAmount;
authorizedOverall -= effectiveDifference(_info.authorized, _properAmount, _info);
}
}

Expand Down Expand Up @@ -507,7 +570,7 @@ contract TACoApplication is

if (info.operatorConfirmed) {
resynchronizeAuthorizedOverall(info, _fromAmount);
authorizedOverall += _toAmount - _fromAmount;
authorizedOverall += effectiveDifference(_toAmount, _fromAmount, info);
}

info.authorized = _toAmount;
Expand All @@ -529,7 +592,7 @@ contract TACoApplication is
StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
if (info.operatorConfirmed) {
resynchronizeAuthorizedOverall(info, _fromAmount);
authorizedOverall -= _fromAmount - _toAmount;
authorizedOverall -= effectiveDifference(_fromAmount, _toAmount, info);
}

info.authorized = _toAmount;
Expand Down Expand Up @@ -593,7 +656,7 @@ contract TACoApplication is
uint96 toAmount = tStaking.approveAuthorizationDecrease(_stakingProvider);

if (info.operatorConfirmed) {
authorizedOverall -= info.authorized - toAmount;
authorizedOverall -= effectiveDifference(info.authorized, toAmount, info);
}

emit AuthorizationDecreaseApproved(_stakingProvider, info.authorized, toAmount);
Expand All @@ -619,7 +682,7 @@ contract TACoApplication is
require(info.authorized > newAuthorized, "Nothing to synchronize");

if (info.operatorConfirmed) {
authorizedOverall -= info.authorized - newAuthorized;
authorizedOverall -= effectiveDifference(info.authorized, newAuthorized, info);
}
emit AuthorizationReSynchronized(_stakingProvider, info.authorized, newAuthorized);

Expand Down Expand Up @@ -860,7 +923,7 @@ contract TACoApplication is
}

if (info.operatorConfirmed) {
authorizedOverall -= info.authorized;
authorizedOverall -= effectiveAuthorized(info.authorized, info);
}

// Bond new operator (or unbond if _operator == address(0))
Expand Down Expand Up @@ -891,7 +954,7 @@ contract TACoApplication is
if (!info.operatorConfirmed) {
updateRewardInternal(stakingProvider);
info.operatorConfirmed = true;
authorizedOverall += info.authorized;
authorizedOverall += effectiveAuthorized(info.authorized, info);
emit OperatorConfirmed(stakingProvider, _operator);
}
}
Expand Down Expand Up @@ -957,4 +1020,32 @@ contract TACoApplication is
stakingProviderWrapper[0] = _stakingProvider;
tStaking.seize(_penalty, 100, _investigator, stakingProviderWrapper);
}

/**
* @notice Penalize the staking provider's future reward
* @param _stakingProvider Staking provider address
*/
function penalize(address _stakingProvider) external updateReward(_stakingProvider) {
require(
msg.sender == address(childApplication),
"Only child application allowed to penalize"
);
StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
info.penaltyPercent = penaltyDefault;
info.endPenalty = uint64(block.timestamp + penaltyDuration);
emit Penalized(_stakingProvider, info.penaltyPercent, info.endPenalty);
}

/**
* @notice Resets future reward back to 100%
* @param _stakingProvider Staking provider address
*/
function resetReward(address _stakingProvider) external updateReward(_stakingProvider) {
StakingProviderInfo storage info = stakingProviderInfo[_stakingProvider];
require(info.endPenalty != 0, "There are no any penalties");
require(info.endPenalty <= block.timestamp, "Penalty is still ongoing");
info.endPenalty = 0;
info.penaltyPercent = 0;
emit RewardReset(_stakingProvider);
}
}
5 changes: 5 additions & 0 deletions tests/application/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
COMMITMENT_DURATION_3 = 3 * COMMITMENT_DURATION_1 # 365 days in seconds
COMMITMENT_DEADLINE = 60 * 60 * 24 * 100 # 100 days after deploymwent

PENALTY_DEFAULT = 1000 # 10% penalty
PENALTY_DURATION = 60 * 60 * 24 # 1 day in seconds


@pytest.fixture()
def token(project, accounts):
Expand Down Expand Up @@ -78,6 +81,8 @@ def taco_application(project, creator, token, threshold_staking, oz_dependency,
DEAUTHORIZATION_DURATION,
[COMMITMENT_DURATION_1, COMMITMENT_DURATION_2, COMMITMENT_DURATION_3],
now + COMMITMENT_DEADLINE,
PENALTY_DEFAULT,
PENALTY_DURATION,
)

encoded_initializer_function = encode_function_data()
Expand Down

0 comments on commit c47b4c6

Please sign in to comment.