Skip to content

Commit a84e854

Browse files
authored
staking: burn protocol fee when collecting query fees (#241)
1 parent e15b8c4 commit a84e854

File tree

4 files changed

+98
-13
lines changed

4 files changed

+98
-13
lines changed

contracts/IStaking.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ interface IStaking {
4646

4747
function setCuration(address _curation) external;
4848

49-
function setCurationPercentage(uint256 _percentage) external;
49+
function setCurationPercentage(uint32 _percentage) external;
50+
51+
function setProtocolPercentage(uint32 _percentage) external;
5052

5153
function setChannelDisputeEpochs(uint256 _channelDisputeEpochs) external;
5254

contracts/Staking.sol

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ contract Staking is IStaking, Governed {
3030

3131
// Percentage of fees going to curators
3232
// Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%)
33-
uint256 public curationPercentage;
33+
uint32 public curationPercentage;
34+
35+
// Percentage of fees burned as protocol fee
36+
// Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%)
37+
uint32 public protocolPercentage;
3438

3539
// Need to pass this period for channel to be finalized
3640
uint256 public channelDisputeEpochs;
@@ -223,16 +227,27 @@ contract Staking is IStaking, Governed {
223227
}
224228

225229
/**
226-
* @dev Set the curation percentage of indexer fees sent to curators.
227-
* @param _percentage Percentage of indexer fees sent to curators
230+
* @dev Set the curation percentage of query fees sent to curators.
231+
* @param _percentage Percentage of query fees sent to curators
228232
*/
229-
function setCurationPercentage(uint256 _percentage) external override onlyGovernor {
233+
function setCurationPercentage(uint32 _percentage) external override onlyGovernor {
230234
// Must be within 0% to 100% (inclusive)
231235
require(_percentage <= MAX_PPM, "Curation percentage must be below or equal to MAX_PPM");
232236
curationPercentage = _percentage;
233237
emit ParameterUpdated("curationPercentage");
234238
}
235239

240+
/**
241+
* @dev Set a protocol percentage to burn when collecting query fees.
242+
* @param _percentage Percentage of query fees to burn as protocol fee
243+
*/
244+
function setProtocolPercentage(uint32 _percentage) external override onlyGovernor {
245+
// Must be within 0% to 100% (inclusive)
246+
require(_percentage <= MAX_PPM, "Protocol percentage must be below or equal to MAX_PPM");
247+
protocolPercentage = _percentage;
248+
emit ParameterUpdated("protocolPercentage");
249+
}
250+
236251
/**
237252
* @dev Set the period in epochs that need to pass before fees in rebate pool can be claimed.
238253
* @param _channelDisputeEpochs Period in epochs
@@ -867,6 +882,8 @@ contract Staking is IStaking, Governed {
867882
address _from,
868883
uint256 _tokens
869884
) private {
885+
uint256 rebateFees = _tokens;
886+
870887
// Get allocation related to the channel identifier
871888
Allocation storage alloc = allocations[_channelID];
872889
AllocationState allocState = _getAllocationState(_channelID);
@@ -877,11 +894,13 @@ contract Staking is IStaking, Governed {
877894
"Collect: channel must be active or settled"
878895
);
879896

880-
// Calculate curation fees only if the subgraph deployment is curated
881-
uint256 curationFees = _collectCurationFees(alloc.subgraphDeploymentID, _tokens);
897+
// Collect protocol fees to be burned
898+
uint256 protocolFees = _collectProtocolFees(rebateFees);
899+
rebateFees = rebateFees.sub(protocolFees);
882900

883-
// Calculate rebate fees
884-
uint256 rebateFees = _tokens.sub(curationFees);
901+
// Calculate curation fees only if the subgraph deployment is curated
902+
uint256 curationFees = _collectCurationFees(alloc.subgraphDeploymentID, rebateFees);
903+
rebateFees = rebateFees.sub(curationFees);
885904

886905
// Collect funds in the allocated channel
887906
alloc.collectedFees = alloc.collectedFees.add(rebateFees);
@@ -1013,11 +1032,27 @@ contract Staking is IStaking, Governed {
10131032
{
10141033
bool isCurationEnabled = curationPercentage > 0 && address(curation) != address(0);
10151034
if (isCurationEnabled && curation.isCurated(_subgraphDeploymentID)) {
1016-
return curationPercentage.mul(_tokens).div(MAX_PPM);
1035+
return uint256(curationPercentage).mul(_tokens).div(MAX_PPM);
10171036
}
10181037
return 0;
10191038
}
10201039

1040+
/**
1041+
* @dev Collect and burn the protocol fees for an amount of tokens.
1042+
* @param _tokens Total tokens received used to calculate the amount of fees to collect
1043+
* @return Amount of protocol fees
1044+
*/
1045+
function _collectProtocolFees(uint256 _tokens) private returns (uint256) {
1046+
if (protocolPercentage == 0) {
1047+
return 0;
1048+
}
1049+
uint256 protocolFees = uint256(protocolPercentage).mul(_tokens).div(MAX_PPM);
1050+
if (protocolFees > 0) {
1051+
token.burn(protocolFees);
1052+
}
1053+
return protocolFees;
1054+
}
1055+
10211056
/**
10221057
* @dev Return the current state of an allocation
10231058
* @param _channelID Address used as the allocation channel identifier

test/staking/allocation.test.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,16 +165,22 @@ describe('Staking:Allocation', () => {
165165
// This function tests collect with state updates
166166
const shouldCollect = async (tokensToCollect: BigNumber) => {
167167
// Before state
168+
const beforeTokenSupply = await grt.totalSupply()
168169
const beforePool = await curation.pools(subgraphDeploymentID)
169170
const beforeAlloc = await staking.getAllocation(channelID)
170171

171172
// Advance blocks to get the channel in epoch where it can be settled
172173
await advanceToNextEpoch(epochManager)
173174

174-
// Calculate expected results
175+
// Collect fees and calculate expected results
176+
let rebateFees = tokensToCollect
177+
const protocolPercentage = await staking.protocolPercentage()
178+
const protocolFees = rebateFees.mul(protocolPercentage).div(MAX_PPM)
179+
rebateFees = rebateFees.sub(protocolFees)
180+
175181
const curationPercentage = await staking.curationPercentage()
176-
const curationFees = tokensToCollect.mul(curationPercentage).div(MAX_PPM)
177-
const rebateFees = tokensToCollect.sub(curationFees) // calculate expected fees
182+
const curationFees = rebateFees.mul(curationPercentage).div(MAX_PPM)
183+
rebateFees = rebateFees.sub(curationFees)
178184

179185
// Collect tokens from channel
180186
const tx = staking.connect(channelProxy).collect(tokensToCollect, channelID)
@@ -192,9 +198,12 @@ describe('Staking:Allocation', () => {
192198
)
193199

194200
// After state
201+
const afterTokenSupply = await grt.totalSupply()
195202
const afterPool = await curation.pools(subgraphDeploymentID)
196203
const afterAlloc = await staking.getAllocation(channelID)
197204

205+
// Check that protocol fees are burnt
206+
expect(afterTokenSupply).eq(beforeTokenSupply.sub(protocolFees))
198207
// Check that curation reserves increased for the SubgraphDeployment
199208
expect(afterPool.tokens).eq(beforePool.tokens.add(curationFees))
200209
// Verify allocation is updated and channel closed
@@ -253,6 +262,25 @@ describe('Staking:Allocation', () => {
253262
await shouldCollect(tokensToCollect)
254263
})
255264

265+
it('should collect funds from channel + protocol fee + curation fees', async function () {
266+
// Curate the subgraph from where we collect fees to get curation fees distributed
267+
const tokensToSignal = toGRT('100')
268+
await grt.connect(governor).mint(me.address, tokensToSignal)
269+
await grt.connect(me).approve(curation.address, tokensToSignal)
270+
await curation.connect(me).stake(subgraphDeploymentID, tokensToSignal)
271+
272+
// Set a protocol fee percentage
273+
const protocolPercentage = toBN('100000') // 10%
274+
await staking.connect(governor).setProtocolPercentage(protocolPercentage)
275+
276+
// Set a curation fee percentage
277+
const curationPercentage = toBN('200000') // 20%
278+
await staking.connect(governor).setCurationPercentage(curationPercentage)
279+
280+
// Collect
281+
await shouldCollect(tokensToCollect)
282+
})
283+
256284
it('should collect zero tokens', async function () {
257285
await shouldCollect(toGRT('0'))
258286
})

test/staking/configuration.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,26 @@ describe('Staking:Config', () => {
9797
})
9898
})
9999

100+
describe('protocolPercentage', function () {
101+
it('should set `protocolPercentage`', async function () {
102+
for (const newValue of [toBN('0'), toBN('5'), MAX_PPM]) {
103+
await staking.connect(governor).setProtocolPercentage(newValue)
104+
expect(await staking.protocolPercentage()).eq(newValue)
105+
}
106+
})
107+
108+
it('reject set `protocolPercentage` if out of bounds', async function () {
109+
const newValue = MAX_PPM.add(toBN('1'))
110+
const tx = staking.connect(governor).setProtocolPercentage(newValue)
111+
await expect(tx).revertedWith('Protocol percentage must be below or equal to MAX_PPM')
112+
})
113+
114+
it('reject set `protocolPercentage` if not allowed', async function () {
115+
const tx = staking.connect(other).setProtocolPercentage(50)
116+
await expect(tx).revertedWith('Only Governor can call')
117+
})
118+
})
119+
100120
describe('maxAllocationEpochs', function () {
101121
it('should set `maxAllocationEpochs`', async function () {
102122
const newValue = toBN('5')

0 commit comments

Comments
 (0)