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

Create altruistic allocations and close stale allocations #549

Merged
merged 13 commits into from
Jul 13, 2022
Merged
Show file tree
Hide file tree
Changes from 12 commits
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
Binary file not shown.
4 changes: 3 additions & 1 deletion contracts/epochs/EpochManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ contract EpochManager is EpochManagerV1Storage, GraphUpgradeable, IEpochManager

Managed._initialize(_controller);

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

Expand Down
95 changes: 56 additions & 39 deletions contracts/staking/Staking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ contract Staking is StakingV2Storage, GraphUpgradeable, IStaking, Multicall {
* An amount of `tokens` get unallocated from `subgraphDeploymentID`.
* The `effectiveAllocation` are the tokens allocated from creation to closing.
* This event also emits the POI (proof of indexing) submitted by the indexer.
* `isDelegator` is true if the sender was one of the indexer's delegators.
* `isPublic` is true if the sender was someone other than the indexer.
*/
event AllocationClosed(
address indexed indexer,
Expand All @@ -146,7 +146,7 @@ contract Staking is StakingV2Storage, GraphUpgradeable, IStaking, Multicall {
uint256 effectiveAllocation,
address sender,
bytes32 poi,
bool isDelegator
bool isPublic
);

/**
Expand Down Expand Up @@ -1106,9 +1106,6 @@ contract Staking is StakingV2Storage, GraphUpgradeable, IStaking, Multicall {
) private {
require(_isAuth(_indexer), "!auth");

// Only allocations with a non-zero token amount are allowed
require(_tokens > 0, "!tokens");

// Check allocation
require(_allocationID != address(0), "!alloc");
require(_getAllocationState(_allocationID) == AllocationState.Null, "!null");
Expand All @@ -1119,8 +1116,16 @@ contract Staking is StakingV2Storage, GraphUpgradeable, IStaking, Multicall {
bytes32 digest = ECDSA.toEthSignedMessageHash(messageHash);
require(ECDSA.recover(digest, _proof) == _allocationID, "!proof");

// Needs to have free capacity not used for other purposes to allocate
require(getIndexerCapacity(_indexer) >= _tokens, "!capacity");
if (_tokens > 0) {
// Needs to have free capacity not used for other purposes to allocate
require(getIndexerCapacity(_indexer) >= _tokens, "!capacity");
} else {
// Allocating zero-tokens still needs to comply with stake requirements
require(
stakes[_indexer].tokensSecureStake() >= minimumIndexerStake,
"!minimumIndexerStake"
);
}

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

// Mark allocated tokens as used
stakes[_indexer].allocate(alloc.tokens);
// -- Rewards Distribution --

// Track total allocations per subgraph
// Used for rewards calculations
subgraphAllocations[alloc.subgraphDeploymentID] = subgraphAllocations[
alloc.subgraphDeploymentID
].add(alloc.tokens);
// Process non-zero-allocation rewards tracking
if (_tokens > 0) {
// Mark allocated tokens as used
stakes[_indexer].allocate(alloc.tokens);

// Track total allocations per subgraph
// Used for rewards calculations
subgraphAllocations[alloc.subgraphDeploymentID] = subgraphAllocations[
alloc.subgraphDeploymentID
].add(alloc.tokens);
}

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

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

// Close the allocation and start counting a period to settle remaining payments from
// state channels.
allocations[_allocationID].closedAtEpoch = alloc.closedAtEpoch;
pcarranzav marked this conversation as resolved.
Show resolved Hide resolved

// -- Rebate Pool --

// Calculate effective allocation for the amount of epochs it remained allocated
alloc.effectiveAllocation = _getEffectiveAllocation(
maxAllocationEpochs,
alloc.tokens,
epochs
);

// Close the allocation and start counting a period to settle remaining payments from
// state channels.
allocations[_allocationID].closedAtEpoch = alloc.closedAtEpoch;
allocations[_allocationID].effectiveAllocation = alloc.effectiveAllocation;

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

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

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

// Track total allocations per subgraph
// Used for rewards calculations
subgraphAllocations[alloc.subgraphDeploymentID] = subgraphAllocations[
alloc.subgraphDeploymentID
].sub(alloc.tokens);
// Free allocated tokens from use
stakes[alloc.indexer].unallocate(alloc.tokens);

// Track total allocations per subgraph
// Used for rewards calculations
subgraphAllocations[alloc.subgraphDeploymentID] = subgraphAllocations[
alloc.subgraphDeploymentID
].sub(alloc.tokens);
}

emit AllocationClosed(
alloc.indexer,
Expand Down Expand Up @@ -1258,8 +1275,8 @@ contract Staking is StakingV2Storage, GraphUpgradeable, IStaking, Multicall {
// Purge allocation data except for:
// - indexer: used in disputes and to avoid reusing an allocationID
// - subgraphDeploymentID: used in disputes
allocations[_allocationID].tokens = 0; // This avoid collect(), close() and claim() to be called
allocations[_allocationID].createdAtEpoch = 0;
allocations[_allocationID].tokens = 0;
allocations[_allocationID].createdAtEpoch = 0; // This avoid collect(), close() and claim() to be called
allocations[_allocationID].closedAtEpoch = 0;
allocations[_allocationID].collectedFees = 0;
allocations[_allocationID].effectiveAllocation = 0;
Expand Down Expand Up @@ -1529,7 +1546,7 @@ contract Staking is StakingV2Storage, GraphUpgradeable, IStaking, Multicall {
if (alloc.indexer == address(0)) {
return AllocationState.Null;
}
if (alloc.tokens == 0) {
if (alloc.createdAtEpoch == 0) {
return AllocationState.Claimed;
}

Expand Down
5 changes: 3 additions & 2 deletions contracts/staking/libs/Rebates.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import "./Cobbs.sol";
*/
library Rebates {
using SafeMath for uint256;
using Rebates for Rebates.Pool;

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

// Calculate the rebate rewards for the indexer
if (pool.fees > 0) {
if (pool.fees > 0 && pool.effectiveAllocatedStake > 0) {
pcarranzav marked this conversation as resolved.
Show resolved Hide resolved
rebateReward = LibCobbDouglas.cobbDouglas(
pool.fees, // totalRewards
_indexerFees,
Expand All @@ -98,7 +99,7 @@ library Rebates {
);

// Under NO circumstance we will reward more than total fees in the pool
uint256 _unclaimedFees = pool.fees.sub(pool.claimedRewards);
uint256 _unclaimedFees = pool.unclaimedFees();
if (rebateReward > _unclaimedFees) {
rebateReward = _unclaimedFees;
}
Expand Down
2 changes: 1 addition & 1 deletion contracts/tests/RebatePoolMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ contract RebatePoolMock {
uint32 _alphaNumerator,
uint32 _alphaDenominator
) external pure returns (uint256) {
if (_totalFees == 0) {
if (_totalFees == 0 || _totalStake == 0) {
return 0;
}

Expand Down
5 changes: 5 additions & 0 deletions test/epochs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ describe('EpochManager', () => {
})

describe('calculations', () => {
it('first epoch should be 1', async function () {
const currentEpoch = await epochManager.currentEpoch()
expect(currentEpoch).eq(1)
})

it('should return correct block number', async function () {
const currentBlock = await latestBlock()
expect(await epochManager.blockNum()).eq(currentBlock)
Expand Down
6 changes: 6 additions & 0 deletions test/lib/testHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ export const advanceToNextEpoch = async (epochManager: EpochManager): Promise<vo
return advanceBlocks(epochLen.sub(blocksSinceEpoch))
}

export const advanceEpochs = async (epochManager: EpochManager, n: number): Promise<void> => {
for (let i = 0; i < n + 1; i++) {
pcarranzav marked this conversation as resolved.
Show resolved Hide resolved
await advanceToNextEpoch(epochManager)
}
}

export const evmSnapshot = async (): Promise<number> => provider().send('evm_snapshot', [])
export const evmRevert = async (id: number): Promise<boolean> => provider().send('evm_revert', [id])

Expand Down
1 change: 0 additions & 1 deletion test/rewards/rewards.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,6 @@ describe('Rewards', () => {
}

it('should distribute rewards on closed allocation and stake', async function () {

// Align with the epoch boundary
await advanceToNextEpoch(epochManager)
// Setup
Expand Down
Loading