Skip to content

Commit

Permalink
reflectEraUpdate for cancelUnbond (#403)
Browse files Browse the repository at this point in the history
* reflectEraUpdate for cancelUnbond

* fix tests
  • Loading branch information
ianhe8x authored Apr 23, 2024
1 parent 0a89afd commit 3e0dd3d
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 40 deletions.
6 changes: 6 additions & 0 deletions contracts/Staking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,9 @@ contract Staking is IStaking, Initializable, OwnableUpgradeable {
'G008'
);
require(_amount > 0, 'S003');

reflectEraUpdate(_source, _runner);

if (this.isEmptyDelegation(_source, _runner)) {
stakingIndexerNos[_source][_runner] = stakingIndexerLengths[_source];
stakingIndexers[_source][stakingIndexerLengths[_source]] = _runner;
Expand Down Expand Up @@ -338,6 +341,9 @@ contract Staking is IStaking, Initializable, OwnableUpgradeable {
msg.sender == address(this),
'G008'
);

reflectEraUpdate(_source, _runner);

require(delegation[_source][_runner].valueAfter >= _amount && _amount > 0, 'S005');

delegation[_source][_runner].valueAfter -= _amount;
Expand Down
6 changes: 0 additions & 6 deletions contracts/StakingManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ contract StakingManager is IStakingManager, Initializable, OwnableUpgradeable {
*/
function stake(address _runner, uint256 _amount) external override {
Staking staking = Staking(settings.getContractAddress(SQContracts.Staking));
staking.reflectEraUpdate(_runner, _runner);
if (staking.isEmptyDelegation(_runner, _runner)) {
require(msg.sender == settings.getContractAddress(SQContracts.IndexerRegistry), 'G001');
staking.addRunner(_runner);
Expand All @@ -59,7 +58,6 @@ contract StakingManager is IStakingManager, Initializable, OwnableUpgradeable {
function delegate(address _runner, uint256 _amount) external {
require(msg.sender != _runner, 'G004');
Staking staking = Staking(settings.getContractAddress(SQContracts.Staking));
staking.reflectEraUpdate(msg.sender, _runner);
// delegation limit should not exceed
staking.checkDelegateLimitation(_runner, _amount);
staking.delegateToIndexer(msg.sender, _runner, _amount);
Expand All @@ -72,7 +70,6 @@ contract StakingManager is IStakingManager, Initializable, OwnableUpgradeable {
*/
function unstake(address _runner, uint256 _amount) external {
Staking staking = Staking(settings.getContractAddress(SQContracts.Staking));
staking.reflectEraUpdate(_runner, _runner);
if (msg.sender == settings.getContractAddress(SQContracts.IndexerRegistry)) {
staking.removeRunner(_runner);
} else {
Expand All @@ -97,7 +94,6 @@ contract StakingManager is IStakingManager, Initializable, OwnableUpgradeable {
// check if called by an indexer
require(_runner != msg.sender, 'G004');
Staking staking = Staking(settings.getContractAddress(SQContracts.Staking));
staking.reflectEraUpdate(msg.sender, _runner);
staking.startUnbond(msg.sender, _runner, _amount, UnbondType.Undelegation);
}

Expand All @@ -112,9 +108,7 @@ contract StakingManager is IStakingManager, Initializable, OwnableUpgradeable {
// delegation limit should not exceed
staking.checkDelegateLimitation(_toRunner, _amount);

staking.reflectEraUpdate(_source, _fromRunner);
staking.removeDelegation(_source, _fromRunner, _amount);
staking.reflectEraUpdate(_source, _toRunner);
staking.addDelegation(_source, _toRunner, _amount);
}

Expand Down
2 changes: 1 addition & 1 deletion test/Maintenance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe('Maintenance Mode Test', () => {
await token.connect(wallet_2).increaseAllowance(staking.address, etherParse('10000'));
await token.increaseAllowance(staking.address, etherParse('10000'));
await token.increaseAllowance(purchaseOfferMarket.address, etherParse('10000'));
await indexerRegistry.registerIndexer(etherParse('1000'), METADATA_HASH, 0);
await indexerRegistry.registerIndexer(etherParse('1001'), METADATA_HASH, 0);
await indexerRegistry.connect(wallet_2).registerIndexer(etherParse('1000'), METADATA_HASH, 0);

await planManager.createPlanTemplate(time.duration.days(3).toString(), 1000, 100, token.address, METADATA_HASH);
Expand Down
7 changes: 5 additions & 2 deletions test/RewardsDistributer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,12 +584,13 @@ describe('RewardsDistributor Contract', () => {
await stakingManager.connect(delegator).undelegate(runner.address, etherParse('1'));
// 3. start new era -> delegator -> `applyStakeChange`
await startNewEra(eraManager);
await rewardsHelper.indexerCatchup(runner.address);
await rewardsHelper.connect(runner).indexerCatchup(runner.address);
// 4. check totalStakingAmount equal 0, era reward equal 0
await checkValues(etherParse('2.697302697'), etherParse('9.002697302697'), 0, 0);
// 5. indexer register successfully
await stakingManager.connect(runner).widthdraw();
await token.connect(runner).increaseAllowance(staking.address, etherParse('1000'));
await staking.reflectEraUpdate(runner.address, runner.address);
await indexerRegistry
.connect(runner)
.registerIndexer(etherParse('1000'), METADATA_HASH, 100, { gasLimit: '2000000' });
Expand All @@ -604,6 +605,7 @@ describe('RewardsDistributor Contract', () => {
await stakingManager.connect(runner).widthdraw();
await stakingManager.connect(delegator).widthdraw();
// 3. indexer reregister -> add previous delegator and a new delegator
await staking.reflectEraUpdate(runner.address, runner.address);
await token.connect(runner).increaseAllowance(staking.address, etherParse('1000'));
await indexerRegistry
.connect(runner)
Expand Down Expand Up @@ -633,7 +635,7 @@ describe('RewardsDistributor Contract', () => {
await checkValues(etherParse('4.996702697'), etherParse('9.001697302697'), etherParse('1002'), 0);
});

it('reward distribution should work after indexer reregister few more ears later', async () => {
it.only('reward distribution should work after indexer reregister few more ears later', async () => {
// 1. delegator undelegate all the tokens
await stakingManager.connect(delegator).undelegate(runner.address, etherParse('1'));
// 2. start new era -> indexer `collectAndDistributeRewards` -> `applyStakeChange | delegator -> `applyStakeChange`
Expand All @@ -645,6 +647,7 @@ describe('RewardsDistributor Contract', () => {
await startNewEra(eraManager);
await startNewEra(eraManager);
// 3. indexer reregister -> add previous delegator and a new delegator
await staking.reflectEraUpdate(runner.address, runner.address);
await token.connect(runner).increaseAllowance(staking.address, etherParse('1000'));
await indexerRegistry
.connect(runner)
Expand Down
94 changes: 63 additions & 31 deletions test/Staking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,19 @@ import {
StakingManager,
RewardsHelper,
} from '../src';
import { etherParse, lastestBlockTime, registerRunner, revertMsg, startNewEra, timeTravel } from './helper';
import {
addInstantRewards,
etherParse,
lastestBlockTime,
registerRunner,
revertMsg,
startNewEra,
timeTravel,
} from './helper';
import { deployContracts } from './setup';

describe('Staking Contract', () => {
let runner, runner2, delegator, delegator2;
let root, runner, runner2, delegator, delegator2;
let token: ERC20;
let staking: Staking;
let stakingManager: StakingManager;
Expand All @@ -30,7 +38,7 @@ describe('Staking Contract', () => {
let rewardsStaking: RewardsStaking;
let rewardsHelper: RewardsHelper;

const amount = etherParse('2002');
const selfStake = etherParse('2002');

const checkDelegation = async (_delegator: string, indexerAddress: string, valueAfter: BigNumber, era: number) => {
const stakingAmount = await staking.delegation(_delegator, indexerAddress);
Expand All @@ -53,21 +61,21 @@ describe('Staking Contract', () => {
};

const configWallet = async () => {
await registerRunner(token, indexerRegistry, staking, runner, runner, etherParse('2002'));
await registerRunner(token, indexerRegistry, staking, runner, runner2, etherParse('2002'));
await token.connect(runner).transfer(delegator.address, amount);
await token.connect(runner).transfer(delegator2.address, amount);
await token.connect(delegator).increaseAllowance(staking.address, amount);
await registerRunner(token, indexerRegistry, staking, root, runner, etherParse('2002'));
await registerRunner(token, indexerRegistry, staking, root, runner2, etherParse('2002'), 1e5);
await token.connect(root).transfer(runner.address, selfStake);
await token.connect(root).transfer(delegator.address, selfStake);
await token.connect(root).transfer(delegator2.address, selfStake);
await token.connect(delegator).increaseAllowance(staking.address, selfStake);
};

const deployer = () => deployContracts(runner, runner2);
const deployer = () => deployContracts(root, runner2);
before(async () => {
[runner, runner2, delegator] = await ethers.getSigners();
[root, runner, runner2, delegator, delegator2] = await ethers.getSigners();
});

beforeEach(async () => {
const deployment = await waffle.loadFixture(deployer);
[runner, runner2, delegator, delegator2] = await ethers.getSigners();
token = deployment.token;
staking = deployment.staking;
stakingManager = deployment.stakingManager;
Expand Down Expand Up @@ -118,10 +126,10 @@ describe('Staking Contract', () => {

// first stake from indexer should be effective immediately
const stakingAmount = await staking.delegation(runner.address, runner.address);
expect(stakingAmount.valueAt).to.equal(amount);
expect(stakingAmount.valueAfter).to.equal(amount);
await checkStakingAmount(runner.address, amount, 2);
expect(await token.balanceOf(staking.address)).to.equal(amount.mul(2));
expect(stakingAmount.valueAt).to.equal(selfStake);
expect(stakingAmount.valueAfter).to.equal(selfStake);
await checkStakingAmount(runner.address, selfStake, 2);
expect(await token.balanceOf(staking.address)).to.equal(selfStake.mul(2));
});

it('staking by indexer should work', async () => {
Expand All @@ -130,7 +138,7 @@ describe('Staking Contract', () => {
await stakingManager.connect(runner).stake(runner.address, moreStakingAmount);
// check staking changes
expect(await stakingManager.getAfterDelegationAmount(runner.address, runner.address)).to.equal(
amount.add(moreStakingAmount)
selfStake.add(moreStakingAmount)
);
});

Expand All @@ -139,7 +147,7 @@ describe('Staking Contract', () => {
await stakingManager.connect(runner).unstake(runner.address, unstakeAmount);
// check staking changes
expect(await stakingManager.getAfterDelegationAmount(runner.address, runner.address)).to.equal(
amount.sub(unstakeAmount)
selfStake.sub(unstakeAmount)
);
});

Expand Down Expand Up @@ -190,9 +198,9 @@ describe('Staking Contract', () => {

it('redelegate with invalid params should fail', async () => {
// self delegation
await expect(
stakingManager.redelegate(runner.address, runner2.address, etherParse('1'))
).to.be.revertedWith('G004');
await expect(stakingManager.redelegate(root.address, runner2.address, etherParse('1'))).to.be.revertedWith(
'G004'
);
// out of amount
await stakingManager.connect(delegator).delegate(runner.address, etherParse('1'));
await expect(
Expand All @@ -208,7 +216,7 @@ describe('Staking Contract', () => {
await startNewEra(eraManager);
expect(await staking.stakingIndexerLengths(delegator.address)).to.equal(1);
await checkDelegation(delegator.address, runner.address, etherParse('1'), 3);
await checkStakingAmount(runner.address, amount.add(etherParse('1')), 3);
await checkStakingAmount(runner.address, selfStake.add(etherParse('1')), 3);

expect(await token.balanceOf(delegator.address)).to.equal(delegatorBalance.sub(etherParse('1')));
expect(await token.balanceOf(staking.address)).to.equal(contractBalance.add(etherParse('1')));
Expand All @@ -222,9 +230,9 @@ describe('Staking Contract', () => {
await startNewEra(eraManager);
expect(await staking.stakingIndexerLengths(delegator.address)).to.equal(2);
await checkDelegation(delegator.address, from_indexer, etherParse('0'), 3);
await checkStakingAmount(from_indexer, amount, 3);
await checkStakingAmount(from_indexer, selfStake, 3);
await checkDelegation(delegator.address, to_indexer, etherParse('1'), 3);
await checkStakingAmount(to_indexer, amount.add(etherParse('1')), 3);
await checkStakingAmount(to_indexer, selfStake.add(etherParse('1')), 3);
});

it('delegate by indexer should fail', async () => {
Expand All @@ -236,7 +244,7 @@ describe('Staking Contract', () => {
it('delegation excess max limitation should fail', async () => {
const indexerLeverageLimit = await staking.indexerLeverageLimit();
const indexerStakingAmount = await stakingManager.getAfterDelegationAmount(runner.address, runner.address);
await token.connect(runner).transfer(delegator.address, indexerStakingAmount.mul(indexerLeverageLimit));
await token.connect(root).transfer(delegator.address, indexerStakingAmount.mul(indexerLeverageLimit));

await expect(
stakingManager
Expand Down Expand Up @@ -268,8 +276,8 @@ describe('Staking Contract', () => {

// check changes of staking storage
await startNewEra(eraManager);
await checkDelegation(runner.address, runner.address, amount.sub(etherParse('0.5')), 3);
await checkStakingAmount(runner.address, amount.add(etherParse('1.5')), 3);
await checkDelegation(runner.address, runner.address, selfStake.sub(etherParse('0.5')), 3);
await checkStakingAmount(runner.address, selfStake.add(etherParse('1.5')), 3);
await checkUnbondingAmount(runner.address, 0, startTime, etherParse('0.5'));

// check changes of unbonding storage
Expand All @@ -279,7 +287,7 @@ describe('Staking Contract', () => {
});

it('request unregister to unstaking all by indexer registry should work', async () => {
await indexerRegistry.unregisterIndexer({ gasLimit: '1000000' });
await indexerRegistry.connect(runner).unregisterIndexer({ gasLimit: '1000000' });

// check changes of indexer storage
await startNewEra(eraManager);
Expand All @@ -299,7 +307,7 @@ describe('Staking Contract', () => {
// check changes of staking storage
await startNewEra(eraManager);
await checkDelegation(delegator.address, runner.address, etherParse('1'), 3);
await checkStakingAmount(runner.address, amount.add(etherParse('1')), 3);
await checkStakingAmount(runner.address, selfStake.add(etherParse('1')), 3);
await checkUnbondingAmount(delegator.address, 0, startTime, etherParse('1'));

// check changes of unbonding storage
Expand All @@ -315,7 +323,7 @@ describe('Staking Contract', () => {
// check changes of staking storage
await startNewEra(eraManager);
await checkDelegation(delegator.address, runner.address, etherParse('0.5'), 3);
await checkStakingAmount(runner.address, amount.add(etherParse('0.5')), 3);
await checkStakingAmount(runner.address, selfStake.add(etherParse('0.5')), 3);

// check all unbondingAmounts
const unbondingAmounts = await stakingManager.getUnbondingAmounts(delegator.address);
Expand All @@ -339,7 +347,7 @@ describe('Staking Contract', () => {
await rewardsHelper.batchCollectAndDistributeRewards(runner.address, 10);
await rewardsHelper.batchApplyStakeChange(runner.address, [delegator.address, delegator2.address]);
// deregister from network
await expect(indexerRegistry.unregisterIndexer({ gasLimit: '1000000' }))
await expect(indexerRegistry.connect(runner).unregisterIndexer({ gasLimit: '1000000' }))
.to.be.emit(indexerRegistry, 'UnregisterIndexer')
.withArgs(runner.address);

Expand All @@ -352,7 +360,9 @@ describe('Staking Contract', () => {

it('request undelegate with invlaid params should fail', async () => {
// indexer unstake out of balance
await expect(stakingManager.undelegate(runner.address, amount)).to.be.revertedWith('G004');
await expect(stakingManager.connect(runner).undelegate(runner.address, selfStake)).to.be.revertedWith(
'G004'
);
// amount should be positive
await expect(
stakingManager.connect(delegator).undelegate(runner.address, etherParse('0'))
Expand Down Expand Up @@ -398,6 +408,28 @@ describe('Staking Contract', () => {
await expect(stakingManager.connect(delegator).widthdraw()).to.be.revertedWith('S009');
});

it('cancel commission unbond should work', async () => {
// commission rate: 10%
// reward 1000 SQT
// commission = 100 SQT
const era = await eraManager.eraNumber();
let stake = await staking.delegation(runner2.address, runner2.address);
expect(stake.era).to.eq(era);
expect(stake.valueAt).to.eq(selfStake);
expect(stake.valueAfter).to.eq(selfStake);
// receive rewards
await addInstantRewards(token, rewardsDistributor, root, runner2.address, era, etherParse('1000'));
await startNewEra(eraManager);
await rewardsDistributor.collectAndDistributeRewards(runner2.address);
// cancel commission
expect(await staking.unbondingLength(runner2.address)).to.eq(1);
await stakingManager.connect(runner2).cancelUnbonding(0);
stake = await staking.delegation(runner2.address, runner2.address);
expect(stake.era).to.eq(era.add(1));
expect(stake.valueAt).to.eq(selfStake);
expect(stake.valueAfter).to.eq(selfStake.add(etherParse('100')));
});

it('cancelUnbonding resize should work', async () => {
// use unbonding length to 6, max undelegate is 5
await staking.setMaxUnbondingRequest(6);
Expand Down
16 changes: 16 additions & 0 deletions test/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {
ProjectRegistry,
RewardsBooster,
StateChannel,
SQToken,
RewardsDistributor,
} from '../src';
import { METADATA_HASH } from './constants';
import { expect } from 'chai';
Expand Down Expand Up @@ -295,3 +297,17 @@ export const revertMsg = {
insufficientBalance: 'ERC20: transfer amount exceeds balance',
insufficientAllowance: 'ERC20: insufficient allowance',
};

export async function addInstantRewards(
sqtoken: ERC20,
rewardsDistributor: RewardsDistributor,
signer: SignerWithAddress,
runner: string,
era: BigNumberish,
amount: BigNumberish
) {
await wrapTxs(async () => {
await sqtoken.connect(signer).approve(rewardsDistributor.address, amount);
await rewardsDistributor.connect(signer).addInstantRewards(runner, signer.address, amount, era);
});
}

0 comments on commit 3e0dd3d

Please sign in to comment.