Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release 1.2.14 #268

Merged
merged 5 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
11 changes: 11 additions & 0 deletions contracts/components/staking/rewards/RewardsDistributor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ contract RewardsDistributor is BaseComponentUpgradeable, SubjectTypeValidator, I
uint256 public delegationParamsEpochDelay;
uint256 public defaultFeeBps;

// subject => epoch => claimedByPoolOwner
mapping(uint256 => mapping(uint256 => bool)) public poolRewardsAtEpochClaimedByOwner;

event DidAccumulateRate(uint8 indexed subjectType, uint256 indexed subject, address indexed staker, uint256 stakeAmount, uint256 sharesAmount);
event DidReduceRate(uint8 indexed subjectType, uint256 indexed subject, address indexed staker, uint256 stakeAmount, uint256 sharesAmount);
event Rewarded(uint8 indexed subjectType, uint256 indexed subject, uint256 amount, uint256 epochNumber);
Expand All @@ -63,6 +66,7 @@ contract RewardsDistributor is BaseComponentUpgradeable, SubjectTypeValidator, I
event TokensSwept(address indexed token, address to, uint256 amount);

error RewardingNonRegisteredSubject(uint8 subjectType, uint256 subject);
error AlreadyClaimedByOwner();
error AlreadyClaimed();
error AlreadyRewarded(uint256 epochNumber);
error SetDelegationFeeNotReady();
Expand Down Expand Up @@ -153,6 +157,9 @@ contract RewardsDistributor is BaseComponentUpgradeable, SubjectTypeValidator, I

function availableReward(uint8 subjectType, uint256 subjectId, uint256 epochNumber, address staker) public view returns (uint256) {
(uint256 shareId, bool isDelegator) = _getShareId(subjectType, subjectId);
if (!isDelegator && (_subjectGateway.ownerOf(subjectType, subjectId) != staker || poolRewardsAtEpochClaimedByOwner[subjectId][epochNumber])) {
return 0;
}
if (claimedRewardsPerEpoch[shareId][epochNumber][staker]) {
return 0;
}
Expand Down Expand Up @@ -221,7 +228,11 @@ contract RewardsDistributor is BaseComponentUpgradeable, SubjectTypeValidator, I
if (_subjectGateway.ownerOf(subjectType, subjectId) != _msgSender()) revert SenderNotOwner(_msgSender(), subjectId);
}
for (uint256 i = 0; i < epochNumbers.length; i++) {
if (!isDelegator && poolRewardsAtEpochClaimedByOwner[subjectId][epochNumbers[i]]) {
revert AlreadyClaimedByOwner();
}
if (claimedRewardsPerEpoch[shareId][epochNumbers[i]][_msgSender()]) revert AlreadyClaimed();
if (!isDelegator) poolRewardsAtEpochClaimedByOwner[subjectId][epochNumbers[i]] = true;
claimedRewardsPerEpoch[shareId][epochNumbers[i]][_msgSender()] = true;
uint256 epochRewards = _availableReward(shareId, isDelegator, epochNumbers[i], _msgSender());
if (epochRewards == 0) revert ZeroAmount("epochRewards");
Expand Down
72 changes: 71 additions & 1 deletion test/components/staking.rewards.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,83 @@ describe('Staking Rewards', function () {
expect(await this.rewardsDistributor.availableReward(DELEGATOR_SUBJECT_TYPE, SCANNER_POOL_ID, epoch, this.accounts.user2.address)).to.be.equal('0');

await expect(this.rewardsDistributor.connect(this.accounts.user1).claimRewards(SCANNER_POOL_SUBJECT_TYPE, SCANNER_POOL_ID, [epoch])).to.be.revertedWith(
'AlreadyClaimed()'
'AlreadyClaimedByOwner()'
);
await expect(this.rewardsDistributor.connect(this.accounts.user2).claimRewards(DELEGATOR_SUBJECT_TYPE, SCANNER_POOL_ID, [epoch])).to.be.revertedWith(
'AlreadyClaimed()'
);
});

it('should fail to reclaim for same pool and epoch even if pool ownership is transferred', async function () {
// disable automine so deposits are instantaneous to simplify math
await network.provider.send('evm_setAutomine', [false]);
await this.staking.connect(this.accounts.user1).deposit(SCANNER_POOL_SUBJECT_TYPE, SCANNER_POOL_ID, '100');
await this.scannerPools.connect(this.accounts.user1).registerScannerNode(registration, signature);
await network.provider.send('evm_setAutomine', [true]);
await network.provider.send('evm_mine');

expect(await this.stakeAllocator.allocatedManagedStake(SCANNER_POOL_SUBJECT_TYPE, SCANNER_POOL_ID)).to.be.equal('100');

const latestTimestamp = await helpers.time.latest();
const timeToNextEpoch = EPOCH_LENGTH - ((latestTimestamp - OFFSET) % EPOCH_LENGTH);
await helpers.time.increase(Math.floor(timeToNextEpoch / 2));

const epoch = await this.rewardsDistributor.getCurrentEpochNumber();

await helpers.time.increase(1 + EPOCH_LENGTH /* 1 week */);

expect(await this.rewardsDistributor.availableReward(SCANNER_POOL_SUBJECT_TYPE, SCANNER_POOL_ID, epoch, this.accounts.user1.address)).to.be.equal('0');

await this.rewardsDistributor.connect(this.accounts.manager).reward(SCANNER_POOL_SUBJECT_TYPE, SCANNER_POOL_ID, '2000', epoch);

expect(await this.rewardsDistributor.availableReward(SCANNER_POOL_SUBJECT_TYPE, SCANNER_POOL_ID, epoch, this.accounts.user1.address)).to.be.equal('2000');

const balanceBefore1 = await this.token.balanceOf(this.accounts.user1.address);

await this.rewardsDistributor.connect(this.accounts.user1).claimRewards(SCANNER_POOL_SUBJECT_TYPE, SCANNER_POOL_ID, [epoch]);

expect(await this.token.balanceOf(this.accounts.user1.address)).to.eq(balanceBefore1.add('2000'));
expect(await this.rewardsDistributor.availableReward(SCANNER_POOL_SUBJECT_TYPE, SCANNER_POOL_ID, epoch, this.accounts.user1.address)).to.be.equal('0');

await expect(this.rewardsDistributor.connect(this.accounts.user1).claimRewards(SCANNER_POOL_SUBJECT_TYPE, SCANNER_POOL_ID, [epoch])).to.be.revertedWith(
'AlreadyClaimedByOwner()'
);

expect(await this.scannerPools.ownerOf(SCANNER_POOL_ID)).to.eq(this.accounts.user1.address);
await this.scannerPools.connect(this.accounts.user1).transferFrom(this.accounts.user1.address, this.accounts.user2.address, SCANNER_POOL_ID);
expect(await this.scannerPools.ownerOf(SCANNER_POOL_ID)).to.eq(this.accounts.user2.address);

expect(await this.rewardsDistributor.availableReward(SCANNER_POOL_SUBJECT_TYPE, SCANNER_POOL_ID, epoch, this.accounts.user2.address)).to.be.equal('0');

await expect(this.rewardsDistributor.connect(this.accounts.user2).claimRewards(SCANNER_POOL_SUBJECT_TYPE, SCANNER_POOL_ID, [epoch])).to.be.revertedWith(
'AlreadyClaimedByOwner()'
);

const epochTwo = await this.rewardsDistributor.getCurrentEpochNumber();

await helpers.time.increase(1 + EPOCH_LENGTH /* 1 week */);

expect(await this.rewardsDistributor.availableReward(SCANNER_POOL_SUBJECT_TYPE, SCANNER_POOL_ID, epochTwo, this.accounts.user1.address)).to.be.equal('0');
expect(await this.rewardsDistributor.availableReward(SCANNER_POOL_SUBJECT_TYPE, SCANNER_POOL_ID, epochTwo, this.accounts.user2.address)).to.be.equal('0');

await this.rewardsDistributor.connect(this.accounts.manager).reward(SCANNER_POOL_SUBJECT_TYPE, SCANNER_POOL_ID, '2000', epochTwo);

expect(await this.rewardsDistributor.availableReward(SCANNER_POOL_SUBJECT_TYPE, SCANNER_POOL_ID, epochTwo, this.accounts.user1.address)).to.be.equal('0');
expect(await this.rewardsDistributor.availableReward(SCANNER_POOL_SUBJECT_TYPE, SCANNER_POOL_ID, epochTwo, this.accounts.user2.address)).to.be.equal('2000');
await expect(this.rewardsDistributor.connect(this.accounts.user1).claimRewards(SCANNER_POOL_SUBJECT_TYPE, SCANNER_POOL_ID, [epochTwo])).to.be.revertedWith(
`SenderNotOwner("${this.accounts.user1.address}", ${SCANNER_POOL_ID})`
);

const balanceBefore2 = await this.token.balanceOf(this.accounts.user2.address);

await this.rewardsDistributor.connect(this.accounts.user2).claimRewards(SCANNER_POOL_SUBJECT_TYPE, SCANNER_POOL_ID, [epochTwo]);

expect(await this.token.balanceOf(this.accounts.user2.address)).to.eq(balanceBefore2.add('2000'));
await expect(this.rewardsDistributor.connect(this.accounts.user2).claimRewards(SCANNER_POOL_SUBJECT_TYPE, SCANNER_POOL_ID, [epochTwo])).to.be.revertedWith(
'AlreadyClaimedByOwner()'
);
});

it('should fail to reclaim if no rewards available', async function () {
await expect(this.rewardsDistributor.connect(this.accounts.user1).claimRewards(SCANNER_POOL_SUBJECT_TYPE, SCANNER_POOL_ID, [1])).to.be.revertedWith(
'ZeroAmount("epochRewards")'
Expand Down
Loading