Skip to content

Commit 6bfec1a

Browse files
authored
Merge pull request #549 from graphprotocol/ariel/zero-alloc
Create altruistic allocations and close stale allocations
2 parents 9d3e5e6 + 97df72b commit 6bfec1a

File tree

9 files changed

+582
-490
lines changed

9 files changed

+582
-490
lines changed

contracts/epochs/EpochManager.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ contract EpochManager is EpochManagerV1Storage, GraphUpgradeable, IEpochManager
2929

3030
Managed._initialize(_controller);
3131

32-
lastLengthUpdateEpoch = 0;
32+
// NOTE: We make the first epoch to be one instead of zero to avoid any issue
33+
// with composing contracts that may use zero as an empty value
34+
lastLengthUpdateEpoch = 1;
3335
lastLengthUpdateBlock = blockNum();
3436
epochLength = _epochLength;
3537

contracts/staking/Staking.sol

Lines changed: 56 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ contract Staking is StakingV2Storage, GraphUpgradeable, IStaking, Multicall {
135135
* An amount of `tokens` get unallocated from `subgraphDeploymentID`.
136136
* The `effectiveAllocation` are the tokens allocated from creation to closing.
137137
* This event also emits the POI (proof of indexing) submitted by the indexer.
138-
* `isDelegator` is true if the sender was one of the indexer's delegators.
138+
* `isPublic` is true if the sender was someone other than the indexer.
139139
*/
140140
event AllocationClosed(
141141
address indexed indexer,
@@ -146,7 +146,7 @@ contract Staking is StakingV2Storage, GraphUpgradeable, IStaking, Multicall {
146146
uint256 effectiveAllocation,
147147
address sender,
148148
bytes32 poi,
149-
bool isDelegator
149+
bool isPublic
150150
);
151151

152152
/**
@@ -1106,9 +1106,6 @@ contract Staking is StakingV2Storage, GraphUpgradeable, IStaking, Multicall {
11061106
) private {
11071107
require(_isAuth(_indexer), "!auth");
11081108

1109-
// Only allocations with a non-zero token amount are allowed
1110-
require(_tokens > 0, "!tokens");
1111-
11121109
// Check allocation
11131110
require(_allocationID != address(0), "!alloc");
11141111
require(_getAllocationState(_allocationID) == AllocationState.Null, "!null");
@@ -1119,8 +1116,16 @@ contract Staking is StakingV2Storage, GraphUpgradeable, IStaking, Multicall {
11191116
bytes32 digest = ECDSA.toEthSignedMessageHash(messageHash);
11201117
require(ECDSA.recover(digest, _proof) == _allocationID, "!proof");
11211118

1122-
// Needs to have free capacity not used for other purposes to allocate
1123-
require(getIndexerCapacity(_indexer) >= _tokens, "!capacity");
1119+
if (_tokens > 0) {
1120+
// Needs to have free capacity not used for other purposes to allocate
1121+
require(getIndexerCapacity(_indexer) >= _tokens, "!capacity");
1122+
} else {
1123+
// Allocating zero-tokens still needs to comply with stake requirements
1124+
require(
1125+
stakes[_indexer].tokensSecureStake() >= minimumIndexerStake,
1126+
"!minimumIndexerStake"
1127+
);
1128+
}
11241129

11251130
// Creates an allocation
11261131
// Allocation identifiers are not reused
@@ -1133,18 +1138,23 @@ contract Staking is StakingV2Storage, GraphUpgradeable, IStaking, Multicall {
11331138
0, // closedAtEpoch
11341139
0, // Initialize collected fees
11351140
0, // Initialize effective allocation
1136-
_updateRewards(_subgraphDeploymentID) // Initialize accumulated rewards per stake allocated
1141+
(_tokens > 0) ? _updateRewards(_subgraphDeploymentID) : 0 // Initialize accumulated rewards per stake allocated
11371142
);
11381143
allocations[_allocationID] = alloc;
11391144

1140-
// Mark allocated tokens as used
1141-
stakes[_indexer].allocate(alloc.tokens);
1145+
// -- Rewards Distribution --
11421146

1143-
// Track total allocations per subgraph
1144-
// Used for rewards calculations
1145-
subgraphAllocations[alloc.subgraphDeploymentID] = subgraphAllocations[
1146-
alloc.subgraphDeploymentID
1147-
].add(alloc.tokens);
1147+
// Process non-zero-allocation rewards tracking
1148+
if (_tokens > 0) {
1149+
// Mark allocated tokens as used
1150+
stakes[_indexer].allocate(alloc.tokens);
1151+
1152+
// Track total allocations per subgraph
1153+
// Used for rewards calculations
1154+
subgraphAllocations[alloc.subgraphDeploymentID] = subgraphAllocations[
1155+
alloc.subgraphDeploymentID
1156+
].add(alloc.tokens);
1157+
}
11481158

11491159
emit AllocationCreated(
11501160
_indexer,
@@ -1175,24 +1185,26 @@ contract Staking is StakingV2Storage, GraphUpgradeable, IStaking, Multicall {
11751185
require(epochs > 0, "<epochs");
11761186

11771187
// Indexer or operator can close an allocation
1178-
// Delegators are also allowed but only after maxAllocationEpochs passed
1188+
// Anyone is allowed to close ONLY under two concurrent conditions
1189+
// - After maxAllocationEpochs passed
1190+
// - When the allocation is for non-zero amount of tokens
11791191
bool isIndexer = _isAuth(alloc.indexer);
1180-
if (epochs > maxAllocationEpochs) {
1181-
require(isIndexer || isDelegator(alloc.indexer, msg.sender), "!auth-or-del");
1182-
} else {
1192+
if (epochs <= maxAllocationEpochs || alloc.tokens == 0) {
11831193
require(isIndexer, "!auth");
11841194
}
11851195

1196+
// Close the allocation and start counting a period to settle remaining payments from
1197+
// state channels.
1198+
allocations[_allocationID].closedAtEpoch = alloc.closedAtEpoch;
1199+
1200+
// -- Rebate Pool --
1201+
11861202
// Calculate effective allocation for the amount of epochs it remained allocated
11871203
alloc.effectiveAllocation = _getEffectiveAllocation(
11881204
maxAllocationEpochs,
11891205
alloc.tokens,
11901206
epochs
11911207
);
1192-
1193-
// Close the allocation and start counting a period to settle remaining payments from
1194-
// state channels.
1195-
allocations[_allocationID].closedAtEpoch = alloc.closedAtEpoch;
11961208
allocations[_allocationID].effectiveAllocation = alloc.effectiveAllocation;
11971209

11981210
// Account collected fees and effective allocation in rebate pool for the epoch
@@ -1202,21 +1214,26 @@ contract Staking is StakingV2Storage, GraphUpgradeable, IStaking, Multicall {
12021214
}
12031215
rebatePool.addToPool(alloc.collectedFees, alloc.effectiveAllocation);
12041216

1205-
// Distribute rewards if proof of indexing was presented by the indexer or operator
1206-
if (isIndexer && _poi != 0) {
1207-
_distributeRewards(_allocationID, alloc.indexer);
1208-
} else {
1209-
_updateRewards(alloc.subgraphDeploymentID);
1210-
}
1217+
// -- Rewards Distribution --
12111218

1212-
// Free allocated tokens from use
1213-
stakes[alloc.indexer].unallocate(alloc.tokens);
1219+
// Process non-zero-allocation rewards tracking
1220+
if (alloc.tokens > 0) {
1221+
// Distribute rewards if proof of indexing was presented by the indexer or operator
1222+
if (isIndexer && _poi != 0) {
1223+
_distributeRewards(_allocationID, alloc.indexer);
1224+
} else {
1225+
_updateRewards(alloc.subgraphDeploymentID);
1226+
}
12141227

1215-
// Track total allocations per subgraph
1216-
// Used for rewards calculations
1217-
subgraphAllocations[alloc.subgraphDeploymentID] = subgraphAllocations[
1218-
alloc.subgraphDeploymentID
1219-
].sub(alloc.tokens);
1228+
// Free allocated tokens from use
1229+
stakes[alloc.indexer].unallocate(alloc.tokens);
1230+
1231+
// Track total allocations per subgraph
1232+
// Used for rewards calculations
1233+
subgraphAllocations[alloc.subgraphDeploymentID] = subgraphAllocations[
1234+
alloc.subgraphDeploymentID
1235+
].sub(alloc.tokens);
1236+
}
12201237

12211238
emit AllocationClosed(
12221239
alloc.indexer,
@@ -1258,8 +1275,8 @@ contract Staking is StakingV2Storage, GraphUpgradeable, IStaking, Multicall {
12581275
// Purge allocation data except for:
12591276
// - indexer: used in disputes and to avoid reusing an allocationID
12601277
// - subgraphDeploymentID: used in disputes
1261-
allocations[_allocationID].tokens = 0; // This avoid collect(), close() and claim() to be called
1262-
allocations[_allocationID].createdAtEpoch = 0;
1278+
allocations[_allocationID].tokens = 0;
1279+
allocations[_allocationID].createdAtEpoch = 0; // This avoid collect(), close() and claim() to be called
12631280
allocations[_allocationID].closedAtEpoch = 0;
12641281
allocations[_allocationID].collectedFees = 0;
12651282
allocations[_allocationID].effectiveAllocation = 0;
@@ -1529,7 +1546,7 @@ contract Staking is StakingV2Storage, GraphUpgradeable, IStaking, Multicall {
15291546
if (alloc.indexer == address(0)) {
15301547
return AllocationState.Null;
15311548
}
1532-
if (alloc.tokens == 0) {
1549+
if (alloc.createdAtEpoch == 0) {
15331550
return AllocationState.Claimed;
15341551
}
15351552

contracts/staking/libs/Rebates.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import "./Cobbs.sol";
1414
*/
1515
library Rebates {
1616
using SafeMath for uint256;
17+
using Rebates for Rebates.Pool;
1718

1819
// Tracks stats for allocations closed on a particular epoch for claiming
1920
// The pool also keeps tracks of total query fees collected and stake used
@@ -86,7 +87,7 @@ library Rebates {
8687
uint256 rebateReward = 0;
8788

8889
// Calculate the rebate rewards for the indexer
89-
if (pool.fees > 0) {
90+
if (pool.fees > 0 && pool.effectiveAllocatedStake > 0) {
9091
rebateReward = LibCobbDouglas.cobbDouglas(
9192
pool.fees, // totalRewards
9293
_indexerFees,
@@ -98,7 +99,7 @@ library Rebates {
9899
);
99100

100101
// Under NO circumstance we will reward more than total fees in the pool
101-
uint256 _unclaimedFees = pool.fees.sub(pool.claimedRewards);
102+
uint256 _unclaimedFees = pool.unclaimedFees();
102103
if (rebateReward > _unclaimedFees) {
103104
rebateReward = _unclaimedFees;
104105
}

contracts/tests/RebatePoolMock.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ contract RebatePoolMock {
4848
uint32 _alphaNumerator,
4949
uint32 _alphaDenominator
5050
) external pure returns (uint256) {
51-
if (_totalFees == 0) {
51+
if (_totalFees == 0 || _totalStake == 0) {
5252
return 0;
5353
}
5454

test/epochs.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ describe('EpochManager', () => {
6868
})
6969

7070
describe('calculations', () => {
71+
it('first epoch should be 1', async function () {
72+
const currentEpoch = await epochManager.currentEpoch()
73+
expect(currentEpoch).eq(1)
74+
})
75+
7176
it('should return correct block number', async function () {
7277
const currentBlock = await latestBlock()
7378
expect(await epochManager.blockNum()).eq(currentBlock)

test/lib/testHelpers.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ export const advanceToNextEpoch = async (epochManager: EpochManager): Promise<vo
8383
return advanceBlocks(epochLen.sub(blocksSinceEpoch))
8484
}
8585

86+
export const advanceEpochs = async (epochManager: EpochManager, n: number): Promise<void> => {
87+
for (let i = 0; i < n; i++) {
88+
await advanceToNextEpoch(epochManager)
89+
}
90+
}
91+
8692
export const evmSnapshot = async (): Promise<number> => provider().send('evm_snapshot', [])
8793
export const evmRevert = async (id: number): Promise<boolean> => provider().send('evm_revert', [id])
8894

0 commit comments

Comments
 (0)