Skip to content

Commit ef55d45

Browse files
committed
fix: enforce minimum delegation when receiving from L1
1 parent 22e838f commit ef55d45

File tree

2 files changed

+91
-2
lines changed

2 files changed

+91
-2
lines changed

contracts/l2/staking/L2Staking.sol

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ contract L2Staking is Staking, IL2StakingBase {
1919
using SafeMath for uint256;
2020
using Stakes for Stakes.Indexer;
2121

22+
/// @dev Minimum delegation for the first delegator of an indexer
23+
uint256 private constant MINIMUM_DELEGATION = 1e18;
24+
2225
/**
2326
* @dev Emitted when `delegator` delegated `tokens` to the `indexer`, the delegator
2427
* gets `shares` for the delegation pool proportionally to the tokens staked.
@@ -122,7 +125,14 @@ contract L2Staking is Staking, IL2StakingBase {
122125
Delegation storage delegation = pool.delegators[_delegationData.delegator];
123126

124127
// Calculate shares to issue (without applying any delegation tax)
125-
uint256 shares = (pool.tokens == 0) ? _amount : _amount.mul(pool.shares).div(pool.tokens);
128+
uint256 shares;
129+
if (pool.tokens == 0) {
130+
if (_amount >= MINIMUM_DELEGATION) {
131+
shares = _amount;
132+
}
133+
} else {
134+
shares = _amount.mul(pool.shares).div(pool.tokens);
135+
}
126136

127137
if (shares == 0) {
128138
// If no shares would be issued (probably a rounding issue or attack), return the tokens to the delegator

test/l2/l2Staking.test.ts

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,8 @@ describe('L2Staking', () => {
295295
.setIssuancePerBlock(toGRT('114'))
296296

297297
await staking.connect(me.signer).stake(tokens100k)
298-
await staking.connect(me.signer).delegate(me.address, toBN(1)) // 1 weiGRT == 1 share
298+
// Initialize the delegation pool to allow delegating less than 1 GRT
299+
await staking.connect(me.signer).delegate(me.address, tokens10k)
299300

300301
await staking.connect(me.signer).setDelegationParameters(1000, 1000, 1000)
301302
await grt.connect(me.signer).approve(fixtureContracts.curation.address, tokens10k)
@@ -336,6 +337,84 @@ describe('L2Staking', () => {
336337
const delegatorGRTBalanceAfter = await grt.balanceOf(other.address)
337338
expect(delegatorGRTBalanceAfter.sub(delegatorGRTBalanceBefore)).to.equal(toBN(1))
338339
})
340+
it('returns delegation to the delegator if it initialize the pool with less than the minimum delegation', async function () {
341+
await fixtureContracts.rewardsManager
342+
.connect(governor.signer)
343+
.setIssuancePerBlock(toGRT('114'))
344+
345+
await staking.connect(me.signer).stake(tokens100k)
346+
347+
await staking.connect(me.signer).setDelegationParameters(1000, 1000, 1000)
348+
await grt.connect(me.signer).approve(fixtureContracts.curation.address, tokens10k)
349+
await fixtureContracts.curation.connect(me.signer).mint(subgraphDeploymentID, tokens10k, 0)
350+
351+
await allocate(tokens100k)
352+
await advanceToNextEpoch(fixtureContracts.epochManager)
353+
await advanceToNextEpoch(fixtureContracts.epochManager)
354+
await staking.connect(me.signer).closeAllocation(allocationID, randomHexBytes(32))
355+
// Now there are some rewards sent to delegation pool, so 1 weiGRT is less than 1 share
356+
357+
const functionData = defaultAbiCoder.encode(
358+
['tuple(address,address)'],
359+
[[me.address, other.address]],
360+
)
361+
362+
const callhookData = defaultAbiCoder.encode(
363+
['uint8', 'bytes'],
364+
[toBN(1), functionData], // code = 1 means RECEIVE_DELEGATION_CODE
365+
)
366+
const delegatorGRTBalanceBefore = await grt.balanceOf(other.address)
367+
const tx = gatewayFinalizeTransfer(
368+
mockL1Staking.address,
369+
staking.address,
370+
toGRT('0.1'), // Less than 1 GRT!
371+
callhookData,
372+
)
373+
374+
await expect(tx)
375+
.emit(l2GraphTokenGateway, 'DepositFinalized')
376+
.withArgs(mockL1GRT.address, mockL1Staking.address, staking.address, toGRT('0.1'))
377+
const delegation = await staking.getDelegation(me.address, other.address)
378+
await expect(tx)
379+
.emit(staking, 'TransferredDelegationReturnedToDelegator')
380+
.withArgs(me.address, other.address, toGRT('0.1'))
381+
382+
expect(delegation.shares).to.equal(0)
383+
const delegatorGRTBalanceAfter = await grt.balanceOf(other.address)
384+
expect(delegatorGRTBalanceAfter.sub(delegatorGRTBalanceBefore)).to.equal(toGRT('0.1'))
385+
})
386+
it('adds delegation under the minimum if the pool is initialized', async function () {
387+
await staking.connect(me.signer).stake(tokens100k)
388+
389+
// Initialize the delegation pool to allow delegating less than 1 GRT
390+
await staking.connect(me.signer).delegate(me.address, tokens10k)
391+
392+
const functionData = defaultAbiCoder.encode(
393+
['tuple(address,address)'],
394+
[[me.address, other.address]],
395+
)
396+
397+
const callhookData = defaultAbiCoder.encode(
398+
['uint8', 'bytes'],
399+
[toBN(1), functionData], // code = 1 means RECEIVE_DELEGATION_CODE
400+
)
401+
const tx = gatewayFinalizeTransfer(
402+
mockL1Staking.address,
403+
staking.address,
404+
toGRT('0.1'),
405+
callhookData,
406+
)
407+
408+
await expect(tx)
409+
.emit(l2GraphTokenGateway, 'DepositFinalized')
410+
.withArgs(mockL1GRT.address, mockL1Staking.address, staking.address, toGRT('0.1'))
411+
const expectedShares = toGRT('0.1')
412+
await expect(tx)
413+
.emit(staking, 'StakeDelegated')
414+
.withArgs(me.address, other.address, toGRT('0.1'), expectedShares)
415+
const delegation = await staking.getDelegation(me.address, other.address)
416+
expect(delegation.shares).to.equal(expectedShares)
417+
})
339418
})
340419
describe('onTokenTransfer with invalid messages', function () {
341420
it('reverts if the code is invalid', async function () {

0 commit comments

Comments
 (0)